#!/usr/bin/perl

# This script queries the job queue and starts jobs running.
# The unix version runs as a daemon singleton.
# -> We should periodically attempt to start one using cron,
# to allow the scheduler to restart if it dies (which happens
# occasionally when network connectivity is lost to the database).
#
# -k => kill daemon
# -r => restart daemon
# -s => single thread
# -t => check status

use strict;
use Data::Dumper;
use Getopt::Std;
use Sys::CpuLoad;
use POSIX qw(:sys_wait_h :signal_h :errno_h setsid);
use POSIX;

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

use lib '/app/tools/job/lib';
use Job::Queue;
use Job::Job;
use Job::Log;
use Job::Config;
use Job::Scheduler::Linux;

use constant PS => 'ps';

my $gVerbosityLevel = 3;
my $gAction         = 'start';

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

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

my $pidFilePath = $gConfig->get('scheduler_pid_file_path') || die;
my $logFilePath = $gConfig->get('scheduler_log_file_path') || die;
my $sleepTime   = $gConfig->get('scheduler_sleep_time')    || die;

my $pid = isRunning();

if ( $gAction eq 'status' ) {

    # Used by init script to determine if the process is running already
    # Exit 0 if the process is running, 6 if it isn't
    exit( $pid == 0 ? 6 : 0 );
} elsif ( $gAction eq 'start' ) {

    # Quit if we are already running
    if ($pid) { exit 0; }
} elsif ( $gAction eq 'restart' ) {
    if ( $pid && !killDaemon() ) {
        exit 3;
    }
} elsif ( $gAction eq 'kill' ) {
    if ( killDaemon() ) {
        exit 0;
    } else {
        exit 4;
    }
} elsif ( $gAction eq 'single' ) {

    # We will not be demonizing
} else {
    print STDERR "Unknown action\n";
    exit 5;
}

if ( $gAction eq 'single' ) {
    Common::Log::Print "-- Running in single thread mode";
} else {
    daemonize( $pidFilePath, $logFilePath );
}

# Don't need to specify a client_id - this operates strictly in RSCOMMON
# JPK - Not using the RS_DBI package, because I don't care about UTF8 conversion.
#
#my $appSingleton = Common::RSApp->new(clientID => 0, useRSDBI => 0);
my $appSingleton = Common::RSApp->new( clientID => 0 );

my $hostname = Common::RSApp::GetHostname();

# Instantiate the Scheduler object.
#
my $scheduler = Job::Scheduler::Linux->new( %options, hostname => $hostname );

# !!! Going to wrap this in an eval block to catch exceptions
#
eval {
    while (1) {
        $scheduler->run($hostname);
        sleep($sleepTime);
    }
};
if ($@) {
    Common::Log::Print("job_scheduler.pl :  $@");
}

# ===========

sub daemonize {
    my ( $pidFilePath, $logFilePath ) = @_;

    # First, call fork and have the parent exit.
    # This will allow whatever shell invoked us to continue on its merry way.
    #
    my $pid = fork();
    if ( $pid < 0 ) {
        die "ERROR - could not fork: $!\n";
    }

    if ( 0 != $pid ) {

        # This is the parent process. Cya l8r.
        #
        # !!! We might wish to write our PID to a file someplace
        #
        open( PIDFILE, "> " . $pidFilePath ) or die "ERROR: Unable to open $pidFilePath : $!\n";
        print PIDFILE "$pid\n";
        close PIDFILE;

        exit(0);
    }

    # Become a 'session leader', which means:
    # This will make this process the head of it's own process group.
    # (pretty standard procedure for a 'daemon' process.
    # It also means that we will not have a controlling terminal - if we had one, then
    # that association is severed.
    #
    setsid();

    # !!! Should probably close STDIN, STDOUT, and STDERR, or at least route them to /dev/null
    open STDIN,  '/dev/null'         or die "CANNOT READ /dev/null: $!";
    open STDOUT, '>>' . $logFilePath or die "CANNOT OPEN $logFilePath : $!";
    open STDERR, '>>' . $logFilePath or die "CANNOT OPEN $logFilePath : $!";

    # Finally, set the umask to 0 and chdir to '/'.
    #
    umask 0;
    chdir '/';    # !!! Might want to chdir someplace else?
}

sub _usage {
    print STDERR "\nusage: $0 [-s <sleep time> -c <conf_file_path>] [-V <verbosity>]\n";
    print STDERR "\n";
    print STDERR "Arguments:\n";
    print STDERR "\t-s \t\tRun in single thread mode\n";
    print STDERR "\t-c <conf_file_path>\t\tPath to the configuration file.\n\t\t Defaults to /app/tools/job/conf/job_scheduler.conf\n";
    print STDERR "\t-V <verbosity>\t\tSet the verbosity level - higher numbers mean more messages\n";
}

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

    my %opt;
    getopts( 'V:c:skrt', \%opt );

    if ( $opt{V} ) {
        $gVerbosityLevel = $opt{V};
    }

    $gAction = 'kill'    if ( $opt{k} );
    $gAction = 'restart' if ( $opt{r} );
    $gAction = 'status'  if ( $opt{t} );

    if ( $opt{s} ) {
        $gAction = 'single' if ( $opt{s} );
        $ENV{DEV_SERVER} = 1;
    }
}

sub report {
    my ( $msg, $level ) = @_;
    $level = 1 unless $level;

    if ( $level <= $gVerbosityLevel ) {
        Common::Log::Print($msg);
    }
}

sub killDaemon {
    my $pid         = isRunning();
    my $gConfig     = Job::Config->new();
    my $pidFilePath = $gConfig->get('scheduler_pid_file_path');

    return unless ($pid);

    if ( kill( 1, $pid ) ) {
        unlink($pidFilePath);
        return 1;
    }

    return;
}

sub isRunning {
    my $gConfig     = Job::Config->new();
    my $pidFilePath = $gConfig->get('scheduler_pid_file_path');
    my $pid;

    if ( -e $pidFilePath ) {
        open( PIDFILE, "< " . $pidFilePath ) or die "ERROR: Unable to open $pidFilePath : $!\n";
        $pid = <PIDFILE>;
        close PIDFILE;
        chomp $pid;
    }

    return $pid;
}

sub cleanStalePID {
    my $gConfig     = Job::Config->new();
    my $pidFilePath = $gConfig->get('scheduler_pid_file_path');
    my $pid         = isRunning() || return;

    my $res = system( PS . " --pid $pid >/dev/null" );

    # Process in pid file doesn't exist.  Clean up now.
    if ($res) {
        unlink($pidFilePath);
    }
}
