The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

##########################################
# This program launches the yote server. #
##########################################


# note, use forks inside of the Yote package should be called before use strict or use warnings.
use strict;
use warnings;
no  warnings 'uninitialized';
use vars qw($VERSION);

use Daemon::Daemonize qw/ :all /;
use Data::Dumper;
use IO::Socket;

use Yote;

$VERSION = '0.00021';

$SIG{ __DIE__ } = sub { 
    Carp::confess( @_ );
};

my $args   = Yote::get_args();
my $config = $args->{ config };
my $cmd    = $args->{ command };
my( $web_socket, $internal_socket );


if( $config->{ profile } ) {
    require Yote::PerfAspect;
}

unshift @INC, "$config->{ yote_root }/lib";

my $pidfile = $config->{ pidfile } || '/var/run/yote.pid';

if( $cmd eq 'stop' ) {
    if( my $pid = check_pidfile( $pidfile ) ) {
        print "Stopping $0 ( $pid )\n";
        kill 'SIGINT', $pid;
        sleep 2;
        print "Done\n";
        exit;
    }    
    print "yote is not running\n";
    exit;
}

if( $cmd eq 'start' ) {
    if( check_pidfile( $pidfile ) ) {
        print "yote is already running\n";
        exit;
    }
    start_yote();
    exit;
}

if( $cmd eq 'restart' ) {
    if( my $pid = check_pidfile( $pidfile ) ) {
        print "Stopping $0\n";
        kill 'SIGINT', $pid;
        sleep 2;
        print "Done\n";
    }    
    start_yote();
    exit;
}

if( $cmd eq 'status' ) {
    print check_pidfile( $pidfile ) ? "yote is running\n" : "yote is not running\n";
    exit;
}

# ------------------------------------------------------------------

my $in_shutdown = 0;
my( $serv_proc, $cron_proc, $eng_proc );

sub start_yote {

    #
    # open internal socket
    #
    until( $internal_socket = new IO::Socket::INET(
               Listen => 10, 
               # TODO : make sure there is an internal_port
               LocalPort => $config->{internal_port}
           ) ) 
    {
        if( $! =~ /Address already in use/i ) {
            print STDERR "Address '$config->{internal_port}' already in use. Retrying.\n";
            sleep( 5 );
        } else {
            die "Unable to start internal socket :  $!";
        }
    } # until connection established
    print STDERR "Connected engine socket.\n";
    print STDERR Data::Dumper->Dump(["ENG SOCK", $internal_socket]);
    $config->{ internal_socket } = $internal_socket;
     

    #
    # open webserver socket
    #
    until( $web_socket ) {
        $web_socket = new IO::Socket::INET(Listen => 10, LocalPort => $config->{port});
        unless( $web_socket ) {
            if( $! =~ /Address already in use/i ) {
                print STDERR "Address already in use. Retrying.\n";
                sleep( 5 );
            } else {
                die "Unable to start webserver socket : $!";
            }
        }
    }
    print STDERR "Connected web socket.\n";
    $config->{ web_socket } = $web_socket;


    daemonize( close => 0 );
    write_pidfile( $pidfile );
    print STDERR Data::Dumper->Dump(["WROTE PIDFILE $pidfile $$"]);
    $SIG{ INT } = $SIG{ TERM } = sub {
        print STDERR "Got term or int signal\n";
        $in_shutdown = 1;
        for my $cpid ($cron_proc, $eng_proc, $serv_proc) {
            next unless $cpid;
            print STDERR Data::Dumper->Dump(["KILLING '$cpid'"]);
            kill 'SIGINT', $cpid;
        }
        sleep 1;
        while( (my $cpid = waitpid( -1, 0 )) > 0 ) {
        }
        print STDERR "cleaned up processes\n";
        $internal_socket->close if $internal_socket;
        $web_socket->close if $web_socket;
        print STDERR "cleaned up sockets\n";

        exit;
    };

    start_engine();
    start_server();
    start_cron(); #TODO - enable this
    
    $0 = 'yote';
    print "Started yote\n";

    while( ! $in_shutdown ) {
        my $cpid = waitpid( -1, 0 );
        if( $cpid > 0 ) {
            if( $cron_proc == $cpid ) {
                start_cron();
            }
            elsif( $eng_proc == $cpid ) {
                # TODO - maybe shut down or wait a bit.
                start_engine( 1 );
            }
            elsif( $serv_proc == $cpid ) {
                start_server( 1 );
            }
            else {
                # TODO - error for unknown pid
            }
        }        
    }
    print "Exiting program\n";

} #start_yote

sub start_server {
    my $reconnect_socket = shift;

    if( $reconnect_socket ) { 
        $web_socket->close if $web_socket;

        until( $web_socket ) {
            $web_socket = new IO::Socket::INET(Listen => 10, LocalPort => $config->{port});
            unless( $web_socket ) {
                if( $! =~ /Address already in use/i ) {
                    print STDERR "Address already in use. Retrying.\n";
                    sleep( 5 );
                } else {
                    print STDERR "Unable to restart webserver socket : $!";
                    exit;
                }
            }
        }
    }

    my $cpid = fork;
    if( $cpid > 0 ) {
        $serv_proc = $cpid;
    } elsif( defined $cpid ) { #child
        $0 = 'yote appserver manager';
        print STDERR Data::Dumper->Dump(["Starting server manager process"]);
        require Yote::WebAppServer;
        my $server = Yote::WebAppServer->new( $config );
        $server->start();
    } else {
        print STDERR "$0 : Unable to fork server process\n";
    }
} #start_server

sub start_cron {
    my $cpid = fork;
    if( $cpid > 0 ) {
        $cron_proc = $cpid;
    } elsif( defined $cpid ) { #child
        $0 = 'yote cron';

        # TODO : handle signals in the cron itself, it will know how to handle wrap up
        $SIG{ TERM } = $SIG{ INT } = $SIG{ PIPE } = sub { print STDERR "$0 $$ got signal. exiting\n";   exit; };
        require Yote::Cron;
        Yote::Cron::start( $config );
    } else {
        print STDERR "$0 : Unable to fork cron process\n";
    }
} #start_cron

#
# The 'engine' in this case is a single process that executes and communicates to the db. The other processes
# communicate via sockets to this one.
#
sub start_engine {
    my $reconnect_socket = shift;

    if( $reconnect_socket ) { 
        $internal_socket->close if $internal_socket;
        until( $internal_socket = new IO::Socket::INT(
                   Listen => 10, 
                   # TODO : make sure there is an internal_port
                   LocalPort => $config->{internal_port}
               ) ) 
        {
            if( $! =~ /Address already in use/i ) {
                print STDERR "Address '$config->{internal_port}' already in use. Retrying.\n";
                sleep( 5 );
            } else {
                print STDERR "Unable to restart internal socket :  $!";
                exit;
            }
        } # until connection established
        print STDERR "Connected engine socket.\n";
        print STDERR Data::Dumper->Dump(["ENG SOCK", $internal_socket]);
        $config->{ internal_socket } = $internal_socket;
    }        



    my $cpid = fork;
    if( $cpid > 0 ) {
        $eng_proc = $cpid;
    } elsif( defined $cpid ) { #child
        $0 = 'yote engine';

        # TODO : handle signals in the engine itself, it will know how to handle wrap up
        $SIG{ TERM } = $SIG{ INT } = $SIG{ PIPE } = sub { print STDERR "$0 $$ got signal. exiting\n";   exit; };
        require Yote::Engine;
        eval {
            Yote::Engine::start( $config );
        };
        if( $@ ) {
            print STDERR Data::Dumper->Dump([$@]);
        }
    } else {
        die "Unable to fork engine\n";
    }
} #start_engine

__END__

=head1 NAME

yote_server - Turn on and off the Yote Server/Webserver

=head1 SYNOPSIS

The Yote server serves up web pages and IO for javascript Yote requests.

yote_server --help
yote_server --show_config
yote_server --generate     # create new configuration and run yote
yote_server start          # run yote


=head1 DESCRIPTION

This program is the Yote server. At the time of writing this is not daemonized 
( there are some issues using the forks module together with daemonization ).

This uses the configuration in the yote.conf file in the yote root directory. 
The yote root directory is set upon installation and is usually /opt/yote.

When yote is run for the first time, it asks a series of configuration questions.
These can be revisited by 

=head1 FILES

yote.conf

=head1 DIAGNOSTICS

Though Yote has unit tests that are run upon install, its web based components are 
written in javascript, and a javascript interpreter has not been created for this test
framework yet. There is a test that can be manually run. To run, start the yote server
and point a browser to http://localhost:yoteport/yote/unit_tests.html. Also included
are tests for file uploads at http://localhost:yoteport/yote/upload_test.html

=head1 CAVEATS

Most systems will require root permissions to run this. 
Since this cannot be run at this time as a daemon, it can be run manually in a screen. 
To stop the server, hit control C.

=head1 BUGS

There are no known bugs, but since this software is Beta or below, bugs are highly likely 
to exist. Please inform the author if bugs are encountered.

=head1 AUTHOR

Eric Wolf
coyocanid@gmail.com
http://madyote.com

=head1 LICENSE AND COPYRIGHT

Copyright (C) 2011-2013 Eric Wolf

This module is free software; it can be used under the same terms as perl
itself.

=cut