#!/usr/bin/perl

# So, this script wraps around another executable, and provides job control.
# It takes the following arguments:
# - The job_id
# - The command to execute.
#
# If the job contains a job_log_id, then we'll set up STDERR redirection to the path specified.
#

use lib '/app/tools/stats/lib';
use lib '/app/tools/report/lib';
use lib 'd:/app/tools/report/lib';
use lib 'd:/app/tools/appuser/lib';
use lib 'd:/app/tools/common/lib';
use lib 'd:/app/tools/data_classes/lib';
use lib 'd:/app/tools/distribution/lib';
use lib 'd:/app/tools/job/lib';
use lib 'd:/app/tools/mediaserve/lib';
use lib 'd:/app/tools/metadata/lib';
use lib 'd:/app/tools/rps/lib';
use lib 'd:/app/tools/sysadmin/lib';
use lib 'd:/app/tools/bookpub/lib';

use strict;
use Getopt::Std;
use Proc::Background;
use File::Path;

#use POSIX qw(:sys_wait_h :signal_h :errno_h);

use lib '/app/tools/common/lib';
use Common::RSApp;
use Common::Log;

use lib '/app/tools/job/lib';
use Job::Job;
use Job::Config;

# Number of seconds between 'heartbeat' updates.
#
use constant kHeartbeat => 5;

# Parse the command-line options.
#
my %options;
_parseCommandLine( \%options );

my $jobID = $options{jobID};

# Don't need to specify a client_id - this operates strictly in RSCOMMON
#
my $appSingleton = Common::RSApp->new( clientID => 0 );

# Open the config file
#
my $config = Job::Config->new();

# We're going to be dynamically loading the Job's package, so we need
# to make sure our @INC array is set up correctly.  So we pull the paths
# out of the config file.
#
my $libs = $config->get('lib_path');
if ($libs) {
    foreach my $path (@$libs) {
        unshift( @INC, $path );
    }
}

# !!! So, this _could_ fail, if for example we did not set up the correct library paths.
# Fetch the job
#
my $job = Job::Job->CreateFromJobID($jobID);
if ( !$job ) {
    Common::Log::Print("ERROR - Unable to instantiate job $jobID\n");
    exit(0);
}

# Now that we set the start time in the scheduler a job should be in a running
# state by the time it gets here.  See case #12455
if ( $job->getStatus ne Job::Status::kRunning ) {
    Common::Log::Print("ERROR - Job not in a running state\n");
    exit(0);
}

# So, we've got an object.  But there are all sorts of scenarios that could
# cause us to blow up.  So we'll wrap all this initialization code in an eval
# block so we can update the job state if something bad happened.
#
my $commandLine;
my $log;

eval {
    # Derrive the command-line
    #
    $commandLine = $job->getCommandLine();

    # Call a method to generate the base path in which we'll
    # save the log file.
    # Then we'll call another method to _record_ this path in the database.
    # - Why split this out?  In case we want to apply some other default here at
    # the last second...
    #
    my $logBasePath = $job->generateLogBasePath();

    # If path doesn't exist, create it.
    #
    unless ( -d $logBasePath ) {
        mkpath( $logBasePath, { mode => 0775, owner => 'apache', group => 'geeks' } );
    }

    if ( !-d $logBasePath ) {
        die "Failed to create $logBasePath: $!";
    }

    $job->setLogPath($logBasePath);

    # If the job doesn't _have_ a logFile specified, we'll make one up.
    #
    my $logFileName = $job->generateLogFileName() || "job_" . $jobID . "_log";
    $job->setLogFile($logFileName);

    # !!! Is this Windoze safe?
    #
    $log = $logBasePath . '/' . $logFileName;

    my $cmd = _escape($commandLine);
    $commandLine = $cmd;

    open( SAVOUT, ">&STDOUT" ) or die "error: save original STDOUT: $!";
    open( SAVERR, ">&STDOUT" ) or die "error: save original STDOUT: $!";

    # JPK - With our flaky shared filesystem, we have this situation where occasionally
    # the file host is 'down'.  This seems to be a transient condition that resolves itself, but
    # in the meanwhile we've thrown an exception and the job is left in an indeterminate state.
    #
    # I want to clean this up by: a) trying more than once to get the log file created and
    # b) if it truly fails, update the job table state to reflect this correctly.
    #
    my $retries = 3;
    while ($retries) {
        if ( open( STDOUT, '>>', $log ) ) {
            last;
        }
        $retries--;

        # JPK - Let's try sleeping for 5 seconds, see if we can make this happen.
        #
        sleep(5);
        if ( !$retries ) {
            die "error: create '$log': $!";
        }
    }

    open( STDERR, '>&STDOUT' ) or die "error: redirect '$log': $!";
};

if ($@) {
    Common::Log::Print("ERROR - Unable to initialize job $jobID: $@");
    $job->complete( exitCode => Job::Job::kExitCodeJobFailedToStart );
    exit(0);
}

# Spawn a new process that runs the 'real' job.
# We'll pass the 'die_upon_destroy' flag so that if this wrapper
# script exits, the other job will also end.
#
my $process = Proc::Background->new( { die_upon_destroy => 1 }, $commandLine );

# Save the process ID of the child in the job record.
#
$job->startJob( $process->pid() );

# Record the start time.  We'll log the total elasped time later.
# This won't be super exact, but it will be close enough.
#
my $startTime = time();

# Now wait for the child process to complete.
#
while ( $process->alive() ) {

    # Sleep for a few seconds, then send a heartbeat
    #
    sleep(kHeartbeat);
    $job->sendHeartbeat();
}

# Set the exit code of the job.
# This will put the job in the 'finished' state.
#
my $childExitCode = $process->wait() >> 8;
$job->complete( exitCode => $childExitCode );

my $endTime   = time();
my $deltaTime = $endTime - $startTime;

Common::Log::Print("JOB COMPLETE:  Exit Code: $childExitCode.   Total Elapsed Time in Seconds: $deltaTime");

open( STDOUT, ">&SAVOUT" ) or die "error: restore STDOUT: $!";
open( STDERR, ">&SAVERR" ) or die "error: restore STDERR: $!";
close(SAVERR) or die "error: close SAVERR: $!";
close(SAVOUT) or die "error: close SAVOUT: $!";

# all done.
#
exit(0);

sub _usage {
    print STDERR "\nusage: $0 -j <job_id>\n";
    print STDERR "\n";
    print STDERR "Arguments:\n";
    print STDERR "\t-j <job_id>\t\tThe id of the job to execute.\n";
}

sub _parseCommandLine {
    my ($settings) = @_;

    my %opt;
    getopts( 'j:', \%opt );

    if ( !$opt{j} ) {
        _usage();
        exit(1);
    }
    $settings->{jobID} = $opt{j};
}

sub debug {
    my ($msg) = @_;

    open DEBUG, '>> C:\app\data\logs\WRAPPER_DEBUG.txt' or die "cannot open debug log file";
    print DEBUG localtime() . " $msg\n";
    close DEBUG;
}

sub _escape {
    my ($string) = @_;

    # Escape double-quotes (that aren't already escaped...)
    #
    $string =~ s/([^\\])(\")/$1\\\"/g;
    return $string;
}
