#!/usr/bin/perl

use strict;
use IO::Socket;
use Net::hostent;              # for OO version of gethostbyaddr
use FileHandle;
use Glib qw /TRUE FALSE/;

use constant CHECK_UPGRADES => 'LANG=C LC_ALL=C nice -n19 /usr/bin/apt-get -qq update';
use constant GET_PACKAGE_UPGRADEABLE => "echo 'n' | LANG=C LC_ALL=C nice -n19 /usr/bin/apt-get -q -u -V dist-upgrade |";
use constant INSTALL => "LANG=C LC_ALL=C DEBIAN_FRONTEND=noninteractive nice -n19 /usr/bin/apt-get install -y ";
use constant CLEAN => "nice -n19 apt-get clean";
use constant PORT => 9000;

my $program = $0;
my $logfile = '/var/log/dup2date.log';
my @packages;

my $server = IO::Socket::INET->new( Proto     => 'tcp',
				LocalPort => PORT,
				Listen    => SOMAXCONN,
				Reuse     => 1);

die "can't setup server" unless $server;
&log(" : [Server $program accepting clients on port ".PORT."]");

my ($client, $hostinfo);
while ($client = $server->accept()) {
	$client->autoflush(1);
	$hostinfo = gethostbyaddr($client -> peeraddr);
	&log("[Connect from ".$hostinfo -> name."]\n");
	while ( <$client>) {
		next unless /\S/;       # blank line
		my $line = $_;
		chomp($line);
		&got_from_client($hostinfo -> name,$line);
		if	(/quit|exit/i)		{ last; }
		elsif	(/update/i)		{ &update; }
		elsif	(/upgrade/i)		{ &upgrade; }
		elsif	(/dist_upgrade/i)	{ &dist_upgrade; }
		elsif	(/clean/i)		{ system CLEAN; }
	}
	&log("[Connection closed from ".$hostinfo->name."]");
	close $client;
}

sub log
{
	my $fh = new FileHandle "$logfile", O_WRONLY|O_APPEND;
	if(defined $fh)
	{
		foreach my $line (@_)
		{
			my $to_log = $line;
			chomp($to_log);
			print $fh scalar localtime," : $to_log\n";
		}
		undef $fh;
	}
}

sub got_from_client
{
	&log("[".$hostinfo -> name."] => $_");
}

sub send_to_client
{
	my $tosend = $_[0] if defined($_[0]);
	if(defined($tosend) && defined($client))
	{
		&log("[".$hostinfo -> name."] <= $tosend\n");
		print $client "$tosend\n\n";
	}
}

sub update
{
	my $fh = FileHandle -> new;

	&send_to_client("status: update");

	if(defined($fh))
	{
		open($fh,system CHECK_UPGRADES);
		if(eof($fh))
		{
			if($?/256 == 100)
			{
				&send_to_client("update: 0");
				&send_to_client("status: idle");
			}
			if($?/256 == 0) { &get_upgradeable; }
			close($fh);
		}
	}
}

sub get_upgradeable
{
	my $fh = FileHandle -> new;
	my @upgradeable;
	my ($package, $versions, $version_from, $version_to);
	my ($upgrading, $new, $remove);
	my $i;
	if(defined($fh))
	{
		open($fh,GET_PACKAGE_UPGRADEABLE);
		while(<$fh>)
		{
			my $line = $_;
			if($line =~ m/   /)
			{
				$line =~ s/^[\s]*//g;
				chomp($line);
				$package = (split /\ /, $line)[0];
				$versions = (split /\(/, $line)[1];
				$versions =~ s/\)//;
				$versions =~ s/\ //g;
	
				if($upgrading == 1)			
				{
					$version_from = (split /\=\>/, $versions)[0];
					$version_to = (split /\=\>/, $versions)[1];
					push(@upgradeable, "$package $version_from $version_to");
					push(@packages, "$package upgrade");
					$i++;
				}
	
				if($new == 1)
				{
					push(@upgradeable, "$package $versions new");
					push(@packages, "$package new");
					$i++;
				}
	
				if($remove == 1)
				{
					push(@upgradeable, "$package $versions remove");
					push(@packages, "$package remove");
					$i++;
				}
			}
			elsif($line =~ m/kept back/g) { $upgrading = 0; $new = 0; $remove = 0;}
			elsif($line =~ m/upgraded/g) { $upgrading = 1; $new = 0; $remove = 0;}
			elsif($line =~ m/NEW packages will be installed/g) { $upgrading = 0; $new = 1; $remove = 0;}
			elsif($line =~ m/REMOVED/g) { $upgrading = 0, $new = 0; $remove = 1;}
		}
		if($i)
		{
			my $sendstring = join(',',@upgradeable);
			&send_to_client("packages: $sendstring");
		}
		else
		{
			&send_to_client("update 0");
		}
		&send_to_client("status: idle");
		close($fh);
	}

}

sub upgrade
{
	my $last_upg_pkg if(defined($_[0]));
	&send_to_client("status: upgrade");
	if(defined($packages[0]))
	{
		my $pkg = (split / /,$packages[0])[0];
		my $todo = (split / /,$packages[0])[1];
		if(defined($last_upg_pkg))
		{
			if($pkg eq $last_upg_pkg)
			{
				&send_to_client("status: error. Tried to upgrade same package twice in the same run.. Something is wrong and should be fixed.");
				return FALSE;
			}
		}
		if($todo eq 'remove')
		{
			shift(@packages);
			&upgrade($pkg);
		}
		elsif(($todo eq 'upgrade') or ($todo eq 'new'))
		{
			if(&install($pkg))
			{
				shift(@packages);
				&upgrade($pkg);
			}
		}
	}
	else
	{
		undef $last_upg_pkg;
		&send_to_client("update: 0");
		&send_to_client("status: idle");
		return TRUE;
	}
	return TRUE;
}

sub install
{
	my $pkg = $_[0];
	unless(defined($pkg)) { return 0; }
	my $fh = FileHandle -> new;

	if(defined($fh))
	{
		&send_to_client("upgrade: installing $pkg");

		open($fh, INSTALL."$pkg |");
		while(my $line = <$fh>)
		{
			if($line =~ m/Get/)
			{
				my $pakke = (split / /,$line)[3];
				&send_to_client("upgrade: Downloading $pakke\n");
			}
			if($line =~ m/Unpacking/)
			{
				my $pakke = (split / /,$line)[2];
				&send_to_client("upgrade: Unpacking $pakke\n");
			}
			if($line =~ m/Setting/)
			{
				my $pakke = (split / /,$line)[2];
				&clean_package_list($pakke);
				&send_to_client("upgrade: Setup $pakke\n");
			}
			if($line =~ m/Removing/)
			{
				my $pakke = (split / /,$line)[1];
				&send_to_client("upgrade: Removing $pakke\n");
			}
		}
		&send_to_client("upgrade: done ".$pkg);
		close($fh);
	}
	return TRUE;
}

sub clean_package_list # (packagename)
{
	my $pakke;
	if(defined $_[0]) { $pakke = $_[0]; }
	else { return 0; }

	my $ii = 0;
	while($ii < scalar(@packages))
	{
		my $pkg = (split / /,$packages[$ii])[0];
		if($pkg eq $pakke)
		{
			splice(@packages,$ii,1);
			return 1;
		}
		$ii++;
	}
}
