#!/usr/bin/perl

use strict;
use warnings;

use lib '../lib/slackget10/lib';

use slackget10::Base ;
use slackget10::PackageList ;
use slackget10::Package ;
use slackget10::SpecialFileContainer;
use slackget10::SpecialFileContainerList ;
use slackget10::File;
use slackget10::Log ;
use slackget10::Network::Connection ;
use slackget10::Config ;
use Proc::Daemon ;
use Getopt::Long;
use IO::Socket::INET ;
use POSIX ":sys_wait_h";
require Time::HiRes;

####################################### GLOBAL VARIABLES #######################################

my $VERSION = '0.01000';
my $NAME = 'slack-getd';
my $config_file = '/etc/slack-get/config.xml';
my $date = {
	current => time(),
	'last-installed-build' => 0,
	'last-update-build' => 0
};
my $no_daemon = undef;
my $cmd_args = join ' ',@ARGV ;
my $check_config = undef;

####################################### END OF GLOBAL VARIABLES #######################################

Getopt::Long::Configure("pass_through");

GetOptions("help" => \&help, "config-file=s" => \$config_file);

die "FATAL: no $config_file found !!\n" unless( -e $config_file);

# Return to default values for Getopt

Getopt::Long::Configure("default");

GetOptions('no-daemon'=>\$no_daemon, 'check-config'=>\$check_config);

####################################### INTERNAL FUNCTIONS #######################################

sub help
{
	printf("\nslack-getd version $VERSION\nUSAGE: slack-getd [OPTIONS] {start|restart|stop}\n\nAvailable options are :\n\n--config-file : specify a configuration file (default: $config_file). YOU MUST GIVE THE ABSOLUTE PATH !\n--check-config : check for errors in configuration file\n--help : print this help
	\n");
	exit(0);
}

####################################### END OF INTERNAL FUNCTIONS #######################################


####################################### DAEMON's CORE #######################################

my $start_time = Time::HiRes::time();
my $config = new slackget10::Config ($config_file) or die "FATAL: error during configuration file parsing\n$!\n";
if($check_config)
{
	my $status = $config->check_config ;
	die "Errors found in configuration file\n" if($status > 0);
}
my $file = new slackget10::File ('no-file','file-encoding' => $config->{common}->{'file-encoding'}) or die "FATAL: Unable to create an empty slackget10::File object !\n\tThis is a code error please report this bug to Arnaud DUPUIS <a.dupuis\@infinityperl.org>";
## take care of the action require by user

die "USAGE: slack-getd {start|restart|stop}, type slack-getd --help for more help\n" unless(defined($ARGV[0]));

if($ARGV[0] eq 'stop')
{
	print "stoping slack-getd daemon\n";
	if(-e $config->{'daemon'}->{'pid-file'})
	{
		$file->Read($config->{'daemon'}->{'pid-file'});
		kill INT => $file->Get_line(0);
	}
	else
	{
		print "It seems that there is no slack-getd running. (file $config->{'daemon'}->{'pid-file'} not found)\n";
	}
	exit(0);
}
elsif($ARGV[0] eq 'restart')
{
	print "restarting slack-getd daemon\n";
	if(-e $config->{'daemon'}->{'pid-file'})
	{
		$file->Read($config->{'daemon'}->{'pid-file'});
		kill INT => $file->Get_line(0);
		$file->Close ;
	}
	else
	{
		print "It seems that there is no slack-getd running. (file $config->{'daemon'}->{'pid-file'} not found), so I just start the daemon.\n";
	}
	
	sleep 1;
}
elsif($ARGV[0] eq 'start')
{
	if(-e $config->{'daemon'}->{'pid-file'} )
	{
		print "It seems that there is already one running copy of slack-getd. If not please delete the file $config->{'daemon'}->{'pid-file'}.\n";
		exit(0);
	}
	print "starting slack-getd daemon\n";
}

my $log = slackget10::Log->new(
	LOG_FORMAT => $config->{common}->{'log'}->{'log-format'},
	NAME => $NAME,
	VERSION => $VERSION,
	LOG_FILE => $config->{common}->{'log'}->{'log-file'},
	LOG_LEVEL => $config->{common}->{'log'}->{'log-level'},
	FILE_ENCODING => $config->{common}->{'file-encoding'}
);

my $sgb = new slackget10::Base ($config);

my $stop_time = Time::HiRes::time();
unless(defined($config->{'common'}->{'server-list-file'}) && -e $config->{'common'}->{'server-list-file'})
{
	$log->Log(1,"cannot find 'server-list-file' in the configuration file $config_file. Maybe your config.xml file is corrupt");
}
my $serverlist;
&build_server_list ;

my $time_diff = $stop_time-$start_time;
print "Daemon started in $time_diff sec.\n" ;
$log->Log(2,"daemon started in $time_diff secondes, with PID $$\n");
$file->Write($config->{'daemon'}->{'pid-file'},$$);



if(!defined($no_daemon))
{
	Proc::Daemon::Init ;
	unlink($config->{'daemon'}->{'pid-file'});
	$file->Write($config->{'daemon'}->{'pid-file'},$$);
	$log->Log(2,"Daemon daemonized with PID=$$\n") ;
}

my $inst_packagelist;
my $upd_packagelist;
my $file_error = 0;
my $xml_error = 0;
my $child_pid=fork();
unless(defined($child_pid))
{
	$log->Log(1,"FATAL: can't fork the network child, system say : $!.\n");
	die "FATAL: can't fork the network child, system say : $!\n";
}
if($child_pid==0) # the child is for the network communication
{
	my $sock;
	require IO::Handle;
	print "[CHILD] ENTER THE FORK\n";
	$log->Log(2,"[CHILD] Child process correctly launched\n");
	$log->Log(1,"[CHILD] starting network listenning with parameters adress=$config->{'daemon'}->{'listenning-adress'}, port=$config->{'daemon'}->{'listenning-port'}\n");
	IO::Handle->autoflush(1);
	$sock = IO::Socket::INET->new(
		Listen    => 10,
		LocalAddr => $config->{'daemon'}->{'listenning-adress'},
		LocalPort => $config->{'daemon'}->{'listenning-port'},
		Proto     => 'tcp') or $log->Log(1,"[CHILD] FATAL: cannot open a listenning socket adress=$config->{'daemon'}->{'listenning-adress'} port=$config->{'daemon'}->{'listenning-port'}\n");
	unless($sock)
	{
		die "can't start network listenning\n";
		$file->Read($config->{'daemon'}->{'pid-file'});
		kill 2 => $file->get(0);
	}
	
	$SIG{INT} = sub {
		while ((my $waitedpid = waitpid(-1,WNOHANG)) > 0) 
		{
			$log->Log(2,"process $waitedpid ripped\n");
		}
		$sock->close ;
		exit(0);
	};
	
# 	while (my $client=$sock->accept())
	while(1)
	{
		$log->Log(2,"[CHILD] starting network listenning loop\n");
		while (my $client=$sock->accept())
		{
			print "new connection\n";
			$log->Log(1,"[NET] New connection from ".$client->peerhost()." port ".$client->peerport()."\n");
			my $child_conn = fork();
			if($child_conn==0)
			{
				while(defined($client))
				{
					my $line = <$client>;
					next if (!defined($line));
					chomp $line;
					if($line=~/^get_installed_list$/)
					{
						my $file2 = new slackget10::File ("$config->{common}->{'update-directory'}/installed.xml",'file-encoding' => $config->{common}->{'file-encoding'},'no-auto-load' => 1);
						while($file2->is_locked)
						{
							print $client "wait: file locked\n";
							sleep 1;
						}
						$file2->Read ;
						print $client "send: installed.xml\n".join('',$file2->Get_file),"\nend: installed.xml\n";
					}
					elsif($line=~/^get_packages_list$/)
					{
						unless(-e "$config->{common}->{'update-directory'}/packages.xml")
						{
							print $client "error: required file does not exists\n";
						}
						else
						{
							my $file2 = new slackget10::File ("$config->{common}->{'update-directory'}/packages.xml",'file-encoding' => $config->{common}->{'file-encoding'},'no-auto-load' => 1);
							while($file2->is_locked)
							{
								print $client "wait: file locked\n";
								sleep 1;
							}
							$file2->Read ;
							print $client "send: packages.xml\n".join('',$file2->Get_file),"\nend: packages.xml\n";
						}
						
					}
					elsif($line=~/^build_packages_list$/)
					{
						&build_update_list ;
					}
					elsif($line=~/^build_installed_list$/)
					{
						&build_installed_list ;
					}
					elsif($line=~/^build_server_list$/)
					{
						&build_server_list ;
					}
					elsif($line=~/^quit:\s*(.*)$/)
					{
						$log->Log(1,"[NET] host ".$client->peerhost()." disconnected\n");
						$client->close;
						last;
					}
					else
					{
						print $client "unknown_said: $line\n";
					}
				}
			}
		}
		sleep 1;
	}
	$sock->close ;
}
else # the parent
{
	print "[DADY] ENTER THE FORK\n";
	$log->Log(1,"child PID=$child_pid\n");
	
	## Handle some signals

	$SIG{INT} = sub {
		unlink($config->{'daemon'}->{'pid-file'});
		waitpid($child_pid,WNOHANG);
		$log->Log(2,"Received a SIGINT, stopping daemon (PID=$$) and cleanup envirronement.\n");
		exit(0);
	};
	
	$SIG{HUP} = sub {
		unlink($config->{'daemon'}->{'pid-file'});
		$log->Log(2,"Received a SIGHUP, restarting daemon.\n");
		exec("$0 $cmd_args");
	};
	while(1)
	{
	# 	$log->Log(1,"[DEBUG] time=".time()." last-installed-build=$date->{'last-installed-build'}\n");
		if((time()-$date->{'last-installed-build'}) >= ($config->{daemon}->{'installed-packages-list'}->{'build-each'}*60) or $date->{'last-installed-build'} == 0)
		{
			&build_installed_list ;
		}
		if((time()-$date->{'last-update-build'}) >= ($config->{daemon}->{'update-list'}->{'build-each'}*60) or ($date->{'last-update-build'} == 0 && $config->{daemon}->{'update-list'}->{'build-on-start'}=~ /yes/i) )
		{
			&build_update_list ;
		}
		
		if($file_error >= 3)
		{
			$log->Log(1,"Too many error during file access, quitting !\n");
			exit(-1);
		}
		if($xml_error>= 3)
		{
			$log->Log(1,"Too many error during XML transformations, quitting !\n");
			exit(-1);
		}
# 		print "[DADDY] HELLO\n";
		sleep 1;
	}
}

## functions which may be call from the main loop, ugly but this works :)

sub build_installed_list
{
	$log->Log(1,"starting the rebuild of $config->{common}->{'update-directory'}/installed.xml\n");
	$inst_packagelist = undef;
	my $file2 = new slackget10::File ("$config->{common}->{'update-directory'}/installed.xml",'file-encoding' => $config->{common}->{'file-encoding'},'no-auto-load' => 1);
	$file2->Lock_file;
	$inst_packagelist = $sgb->compil_packages_directory($config->{common}->{'packages-history-dir'});
	if($inst_packagelist)
	{
		$log->Log(2,"directory '$config->{common}->{'packages-history-dir'}' compiled as a packages list. Now translating data into XML file.\n");
	}
	else
	{
		$log->Log(1,"An error occured during the compilation of directory '$config->{common}->{'packages-history-dir'}'. Maybe this directory doesn't exist.\n")
	}
	if(my $xml = $inst_packagelist->to_XML())
	{
		$file2->encoding($config->{common}->{'file-encoding'});
		$file2->Write("$config->{common}->{'update-directory'}/installed.xml",$xml) or $file_error++;
		$file2->Close ;
		$date->{'last-installed-build'} = time();
		$log->Log(2,"packages history directory successfully compiled in XML data structure.\n");
		if($config->{common}->{kde}->{'enable-kde-notification'}=~ /yes/i)
		{
			`dcop knotify Notify notify sg_success_event slack-getd "\tDaemon successfully build\n\tthe installed packages cache" '' '' 16 0`
		}
	}
	else
	{
		$log->Log(1,"An error occure during the translation of the Packages list to XML. XML cannot be build !\n");
		$xml_error++;
	}
	$file2->Unlock_file ;
}

sub build_update_list
{
	$log->Log(1,"starting the rebuild of $config->{common}->{'update-directory'}/packages.xml\n");
	my $spe_fil_cont_list;
	my $info_serv = '';
	my $file2 = new slackget10::File ("$config->{common}->{'update-directory'}/packages.xml",'file-encoding' => $config->{common}->{'file-encoding'},'no-auto-load' => 1);
	$file2->Lock_file;
	unless($spe_fil_cont_list = slackget10::SpecialFileContainerList->new())
	{
		$log->Log(1,"FATAL: can't create a new slackget10::SpecialFileContainerList object during building of the update packages list.\n");
		return undef;
	}
	foreach my $server (@{$serverlist->get_all})
	{
		print "=> ",$server->shortname,"\n";
		$info_serv .= "- ".$server->shortname."\n";
		$log->Log(2,"cleaning old cache files.\n");
		mkdir "$config->{common}->{'update-directory'}/".$server->shortname unless(-e "$config->{common}->{'update-directory'}/".$server->shortname);
		mkdir "$config->{common}->{'update-directory'}/".$server->shortname."/cache" unless(-e "$config->{common}->{'update-directory'}/".$server->shortname."/cache");
		unlink "$config->{common}->{'update-directory'}/".$server->shortname."/cache/PACKAGES.TXT" if (-e "$config->{common}->{'update-directory'}/".$server->shortname."/cache/PACKAGES.TXT");
		unlink "$config->{common}->{'update-directory'}/".$server->shortname."/cache/FILELIST.TXT" if (-e "$config->{common}->{'update-directory'}/".$server->shortname."/cache/FILELIST.TXT");
		unlink "$config->{common}->{'update-directory'}/".$server->shortname."/cache/CHECKSUMS.md5" if (-e "$config->{common}->{'update-directory'}/".$server->shortname."/cache/CHECKSUMS.md5");
		my $connection = slackget10::Network::Connection->new(
			host => $server->host,
			config => $config,
			mode => 'normal'
		) or warn "can't create connection object\n";
		$log->Log(2,"Downloading file FILELIST.TXT for server ".$server->shortname." on host ".$connection->host."\n");
		$connection->fetch_file('FILELIST.TXT',"$config->{common}->{'update-directory'}/".$server->shortname."/cache/FILELIST.TXT") or warn "fuck dl FILELIST\n";
		$log->Log(2,"Downloading file PACKAGES.TXT for server ".$server->shortname." on host ".$connection->host."\n");
		$connection->fetch_file('PACKAGES.TXT',"$config->{common}->{'update-directory'}/".$server->shortname."/cache/PACKAGES.TXT") or warn "fuck dl PACKAGES\n";
		$log->Log(2,"Downloading file CHECKSUMS.md5 for server ".$server->shortname." on host ".$connection->host."\n");
		$connection->fetch_file('CHECKSUMS.md5',"$config->{common}->{'update-directory'}/".$server->shortname."/cache/CHECKSUMS.md5") or warn "fuck dl CHECK\n";
		$connection = undef;
		my $container = slackget10::SpecialFileContainer->new(
			$server->shortname,
			config => $config,
			FILELIST => "$config->{common}->{'update-directory'}/".$server->shortname."/cache/FILELIST.TXT",
			PACKAGES => "$config->{common}->{'update-directory'}/".$server->shortname."/cache/PACKAGES.TXT",
			CHECKSUMS => "$config->{common}->{'update-directory'}/".$server->shortname."/cache/CHECKSUMS.md5"
		);
		$container->compile or $log->Log(1,"ERROR: can't compile special files for ".$server->shortname."\n");
		$spe_fil_cont_list->add($container);
	}
	my $xml;
	if($xml = $spe_fil_cont_list->to_XML)
	{
		$log->Log(1,"Successfully translate the slackget10::SpecialFileContainerList object to XML.\n");
	}
	else
	{
		$log->Log(1,"An error occured during the translation of the slackget10::SpecialFileContainerList object to XML.\n");
		$xml_error++;
	}
	if($file2->Write("$config->{common}->{'update-directory'}/packages.xml",$xml))
	{
		$file2->Unlock_file ;
		$file2->Close ;
		$log->Log(1,"remote cache files successfully compiled in packages.xml\n");
		if($config->{common}->{kde}->{'enable-kde-notification'}=~ /yes/i)
		{
			`dcop knotify Notify notify sg_success_event slack-getd "\tDaemon successfully build\n\tthe update cache for :\n$info_serv" '' '' 16 0`
		}
	}
	else
	{
		$file_error++;
		$log->Log(1,"Error during writing of $config->{common}->{'update-directory'}/packages.xml\n");
	}
	
	$date->{'last-update-build'} = time();
}

sub build_server_list
{
	$log->Log(1,"Loading the server list\n");
	$serverlist = undef;
	$serverlist = $sgb->load_server_list_from_xml_file($config->{'common'}->{'server-list-file'});
	if($serverlist)
	{
		$log->Log(2,"server list file '$config->{common}->{'server-list-file'}' successfully loaded.\n");
	}
}

####################################### END OF DAEMON's CORE #######################################