#!/usr/bin/perl
################################################################################
# Mandriva Online                                                              # 
#                                                                              #
# Copyright (C) 2003-2005 Mandriva                                             #
#                                                                              #
# Daouda Lo <daouda at mandriva dot com>                                       #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License Version 2 as            #
# published by the Free Software Foundation.                                   #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.   #
################################################################################

use strict;
use POSIX ":sys_wait_h";
use Config;
use lib qw(/usr/lib/libDrakX);
use standalone; # for explanations
use interactive;
use common;
use run_program;

use Gtk2::Pango;

BEGIN { unshift @::textdomains, 'mdkonline' }

use ugtk2 qw(:all);
use lib qw(/usr/lib/libDrakX/drakfirsttime);
use mdkonline;
use Gtk2::TrayIcon;
use Gtk2::NotificationBubble;


# POSIX unmasks the sigprocmask properly
my $sigset = POSIX::SigSet->new;
my $action = POSIX::SigAction->new('restart_applet',
                                   $sigset,
                                   &POSIX::SA_NODEFER);
POSIX::sigaction(&POSIX::SIGHUP, $action);

if (!find { $_ eq '--auto-update' } @ARGV) {
    my $pid = mdkonline::is_running('mdkapplet');
    $pid and die "mdkapplet already running ($pid)\n";
}

my $in = interactive->vnew('');

ugtk2::add_icon_path("/usr/share/mdkonline/pixmaps/");
my $online_site = "https://www.mandrivaonline.com/";
my ($menu, $timeout, $eventbox, $img, $mLog, $buffer, $textview, $wlog, $textvw, $state_global, $MW_vbox);
my ($raisedwindow, $debug, $conf_launched) = (0, 0, 0);
my $applet_window;


my $conffile = '/etc/sysconfig/mdkonline';
my $localdir = "$ENV{HOME}/.MdkOnline";
my $localfile = "$localdir/mdkonline";

my $release = mdkonline::get_release();

sub my_sprintf_fixutf8 {
    mdkonline::get_release() < 2006.0 ? common::sprintf_fixutf8(@_) : @_;
}


my $insensitive_while_running_a_child;

#compatibility
mkdir_p($localdir) if !-d $localdir;
-e "$ENV{HOME}/.mdkonline" and system("mv", "$ENV{HOME}/.mdkonline", $localfile);

my %state = (
	     okay => {
		      colour => [ 'okay' ],
		      changes => [ 'busy', 'critical', 'disconnected' ],
		      menu => [ 'configureApplet', 'check', 'weblink' ],
		      tt => [ N_("Your system is up-to-date") ]
		     },
 	     critical => {
		      colour => [ 'noconf' ],
		      changes => [ 'okay', 'busy', 'critical', 'disconnected' ],
		      menu => [ 'configureApplet', 'check', 'weblink' ],
		      tt => [ N_("Service configuration problem. Please check logs and send mail to support\@mandrivaonline.com") ]
		     },
	     busy => {
		      colour => [ 'busy' ], 
		      changes => [ 'okay', 'critical', 'error', 'disconnected' ],
		      menu => [],
		      tt => [ N_("Please wait, finding available packages...") ]
		     },
	     updates => {
		      colour => [ 'error' ],
		      changes => [ 'okay' ],
		      menu => [ 'update', 'check', 'weblink' ],
		      tt => [ N_("New updates are available for your system") ]
		     },
	     bundles => {
		      colour => [ 'bundle' ],
		      changes => [ 'okay' ],
		      menu => [ 'update', 'check', 'weblink' ],
		      tt => [ N_("New bundles are available for your system") ]
		      },
	     noconfig => {
		      colour => [ 'noconf' ],
		      changes => [ 'okay' ],
		      menu => [ 'weblink', 'register' ],
		      tt => [ N_("Service is not configured. Please click on \"Configure the service\"") ]
		     },
	     disconnected => {
		      colour => [ 'disconnect' ],
		      changes => [ 'okay', 'busy', 'critical', 'error' ],
		      menu => [ 'confNetwork' ],
		      tt => [ N_("Network is down. Please configure your network") ]
		     },
	     disabled => {
		      colour => [ 'disabled' ],
		      changes => [ 'okay', 'busy', 'critical', 'error' ],
		      menu => [ 'configureApplet', 'weblink' ],
		      tt => [ N_("Service is not activated. Please click on \"Online Website\"") ]
		     },
	     notsupported => {
		      colour => [ 'disabled' ],
		      changes => [ 'okay', 'busy', 'critical', 'error' ],
		      menu => [ 'weblink' ],
		      tt => [ N_("Release not supported (too old release, or development release)") ]
		     }
	    );

my %actions = (
	       'update' => { name => N("Install updates"), launch => sub { installUpdates() } },
	       'configureApplet' => { name => N("Configure the service"), launch => sub { configure() } },
	       'check' => { name => N("Check Updates"), launch => sub { 
				my $w = $in->wait_message(N("Please wait"), N("Check updates") . '...');
				checkUpdates(); undef $w } }, 
	       'weblink' => { name => N("Online WebSite"), launch => sub { mdkonline::get_site($online_site, 'info.php') } },
	       'confNetwork' => { name => N("Configure Network"), launch => sub { configNetwork() } },
	       'register' => { name => N("Configure Now!"), launch => sub { configure() } }
	      );

gtkadd(my $icon = Gtk2::TrayIcon->new("MdkApplet"), 
       gtkadd($eventbox = Gtk2::EventBox->new, 
	      gtkpack($img = Gtk2::Image->new)
	     )
      );
#$icon->shape_combine_mask($img, 0, 0);
$eventbox->signal_connect(button_press_event => sub {
			      if (!$raisedwindow) {
				  if ($_[1]->button == 1) {
				      if (-e $conffile) { $raisedwindow = 1; showMainWindow() } else { $raisedwindow = 0; configure() }
				  }
			      } elsif ($_[1]->button == 1) {
                         $applet_window->present;
                     }
			      $_[1]->button == 3 && $menu and $menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time);
			  });
my ($opt) = @ARGV;
if ($opt eq '--force' || $opt eq '-f') { setAutoStart('TRUE') }
if ($opt eq '--debug') { $debug = 1 }

shouldStart() or die "$localfile should be set to TRUE: please use --force or -f option to launch applet";

checkConfig();
checkUpdates();
cronUpdate();

$icon->show_all;

$SIG{USR1} = 'IGNORE';
$SIG{USR2} = 'IGNORE';
$SIG{CHLD} = \&harvester;

Glib::Timeout->add(200, sub { harvester('CHLD', 1); 1 });

Gtk2->main;

ugtk2::exit(0);

# Signal management 
sub harvester {
    my ($_signame, $clean) = @_;
    my $childpid;
    do {
        $childpid = waitpid(-1, &WNOHANG);
        WIFEXITED($?) and refresh_gui(1);
    } while $childpid > 0;
    return if $clean;
}
sub fork_exec {
    my $pid = fork();
    if (defined $pid) {
        !$pid and do { exec(@_) or POSIX::_exit() };
        return $pid;
    } else {
        refresh_gui(1);                                                                        
    }                                                                                       
}
sub refresh_gui {
    my ($sens) = @_;
    !$conf_launched and silentCheck(); $conf_launched = 0;
    my $w = $::main_window ? $::main_window->window : undef;
    $insensitive_while_running_a_child = !$sens;
    $sens ? gtkset_mousecursor_normal($w) : gtkset_mousecursor_wait($w);
    $MW_vbox and $MW_vbox->set_sensitive($sens);
    gtkflush();
}

sub showMainWindow() {
    my $w = $applet_window = Gtk2::Window->new('toplevel');
    $::main_window = $w;
    $w->set_title(N("Mandriva Linux Updates Applet"));
    $w->signal_connect(delete_event => sub { $w->destroy; $raisedwindow = 0 });
    gtkset_size_request($w, 400, 300);
    $w->set_position('center');
    $w->set_icon(Gtk2::Gdk::Pixbuf->new_from_file('/usr/share/icons/mini/mdkonline.png'));
    $textvw = Gtk2::TextView->new;
    $textvw->set_wrap_mode('word');
    gtkadd($w,
           gtkpack_($MW_vbox = Gtk2::VBox->new(0, 5),
		    0, gtkadd(gtkset_shadow_type(Gtk2::Frame->new(N("Actions")), 'etched_in'),
			      gtkpack_(Gtk2::VBox->new(0, 3),
				       1, gtksignal_connect(Gtk2::Button->new(N("Install updates")), clicked => sub { installUpdates() }),
				       1, gtksignal_connect(Gtk2::Button->new(N("Configure")), clicked => sub { configure() }),
				       1, gtksignal_connect(Gtk2::Button->new(N("Check updates")), clicked => sub {
								my $w = $in->wait_message(N("Please wait"), N("Check updates") . '...');
								checkUpdates(); undef $w }),
				       1, gtksignal_connect(Gtk2::Button->new(N("See logs")), clicked => sub { if (defined $wlog) { $wlog->{window}->show } else { $wlog = displayLogs(); $wlog->main } }),
				      )
			     ),
		    1, gtkadd(gtkset_shadow_type(Gtk2::Frame->new(N("Status")), 'etched_in'),
			    gtkpack_(Gtk2::VBox->new(0, 3),
				 1, create_scrolled_window(gtktext_insert($textvw, refresh_contents($state_global))))),
		    0, gtkpack(Gtk2::HSeparator->new),
		    0, gtkpack(gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub {
						     if (defined $wlog) { $wlog->destroy; undef $wlog }
						     $w->destroy; $raisedwindow = 0;
						 })
			      )
		   )
	  );
    $w->show_all;
    gtkflush();
}
sub setLabel {
    my ($widget, $string) = shift;
    $widget->set_label($string) if defined $widget;
    gtkflush();
}
sub refresh_status {
    my $status = shift;
    gtktext_insert($textvw, refresh_contents($status));
}
sub refresh_contents {
    my $status = shift;
    my $color = {};
    my %h = getVarsFromSh($conffile); 
    foreach my $l (['first', 'red'], ['second', 'royalblue3'], ['third', 'green']) { 
	$color->{$l->[0]} = { 'foreground' => $l->[1], 'weight' => Gtk2::Pango->PANGO_WEIGHT_BOLD };
    }
    my $fixed_tag = { 'font' => 'monospace' };
    setLastTime();
    my $last = lastCheck();
    my $contents = [ [ N("Network Connection: "), $color->{second} ], [ isNetwork() ? N("Up") . "\n" : N("Down") . "\n", isNetwork() ? $color->{third} : $color->{first} ],
		     [ N("Last check: "), $color->{second} ], [ "$last\n", $fixed_tag ],
		     [ N("Machine name:"), $color->{second} ], [ $h{HOST_NAME} . "\n", $fixed_tag ],
		     [ N("Updates: "), $color->{second} ], [ my_sprintf_fixutf8($state{$status}{tt}[0]), $status eq 'okay' ? $color->{third} : $color->{first} ],
		   ];
    $contents;
}
sub configNetwork() { logIt(N("Launching drakconnect\n")); refresh_gui(0); fork_exec("/usr/sbin/drakconnect") }

sub restart_applet() {
    logIt(N("Mandriva Online seems to be reinstalled, reloading applet ...."));
    exec($0, '--auto-update');
}

sub installUpdates() {
    my $binfile = $0;
    my $oldmd5 = $release <= 10.2 ? mdkonline::md5file($binfile) : common::md5file($binfile);
    logIt(N("Launching mdkupdate --applet\n"));
    refresh_gui(0);
    fork_exec("/usr/sbin/mdkupdate --applet");
    my $newmd5 = $release <= 10.2 ? mdkonline::md5file($binfile) : common::md5file($binfile);
    restart_applet() if $newmd5 ne $oldmd5;
    #my $w = $in->wait_message(N("Please wait"), N("Check updates"));
    silentCheck(); gtkflush();
    #undef $w;
    my $mdkupdate_status = cat_('/var/tmp/mdkupdate.log');
    if ($mdkupdate_status && $mdkupdate_status !~ /OK/) { logIt($mdkupdate_status); $in->ask_warn(N("Mandriva Linux Updates Applet"), $mdkupdate_status) }
}
sub silentCheck() {
    my $link = $online_site . "online3_RemoteAction.php" . '?action=UpdateList';
    my %h = getVarsFromSh($conffile); 
    my ($u, $ct);
    logIt(N("Computing new updates...\n"));
    $link .= '&log=' . $h{LOGIN} . '&host=' . $h{HOST_NAME};
    logIt(N("Connecting to") . " $link ...\n");
    my $w = $::main_window ? $::main_window->window : undef;
    gtkset_mousecursor_wait($w);
    gtkflush(); go2State('busy'); gtkflush();
    gtkset_mousecursor_normal($w) if !$insensitive_while_running_a_child;
    my $response = mdkonline::soap_get_updates_for_host($h{HOST_ID}, $h{HOST_KEY});
    my $status_err = mdkonline::check_server_response($response);
    if ($status_err eq 'OK') {
        $ct = $response->{data}{updates};
        # are there any updates ?
        if (@$ct) {
            logIt(N("Checking... Updates are available\n") . "\n");
            go2State('updates');
        } else {     # no update
            okState();
        }
    } else {
    #   99 - log or host or action or pass empty, wrong action
    #   98 - wrong pass
    #   97 - host not active
    logIt("Returned value after association (silentCheck)= $u\n") if $debug;
    my $retcode = {
		   94 => sub { logIt(N("Development release not supported by service")); go2State('notsupported') },
		   95 => sub { logIt(N("Too old release not supported by service")); go2State('notsupported') },
		   96 => sub { logIt(N("Unknown state")); go2State('critical') },
		   97 => sub { logIt(N("Online services disabled. Contact Mandriva Online site\n")); go2State('disabled') },
		   98 => sub { logIt(N("Wrong Password.\n")); go2State('critical') },
		   99 => sub { logIt(N("Wrong Action or host or login.\n")); go2State('critical') },
                   500 => sub { logIt(N("Something is wrong with your network settings (check your route, firewall or proxy settings)\n")); go2State('critical') }
		  };
    eval { $retcode->{$response->{code}}->() }; if ($@) { logIt(N("Problem occured while connecting to the server, please contact the support team")); go2State('critical') }
    }
    if ($debug) {
	print "\nCONTENTS = $ct\n" if $debug;
     require Data::Dumper;
	logIt(N("Response from Mandriva Online server\n") . Data::Dumper->Dumper($response));
    }
}
sub okState() { logIt(N("System is up-to-date\n")); go2State('okay') }
sub compareWithInstalled {
    my ($name, $ver, $rel, $t) = @_;
    my $isUpdate = 0;
    foreach my $p (@$t) { 
	my ($n, $v, $r) = $p =~ /(.*)-(.*)-(.*)$/;
	if ($name eq $n) {
	    my ($iu, $ir);
	    if ($debug) { $iu = mdkonline::rpm_ver_cmp($ver, $v); $ir = mdkonline::rpm_ver_cmp($rel, $r) } 
	    if (mdkonline::rpm_ver_cmp($ver, $v) > 0 || mdkonline::rpm_ver_cmp($ver, $v) == 0 && mdkonline::rpm_ver_cmp($rel, $r) > 0) {
		logIt("$name-$ver-$rel $n-$v-$r *** CMPVER=$iu ** CMPREL = $ir \n") if $debug;
		$isUpdate = 1 and last;
	    }
	}
    }
    $isUpdate;
}
sub cronUpdate() {
    Glib::Timeout->add(10*1000, sub {
	checkConfig();
	1;
    });
    $timeout = Glib::Timeout->add(3*60*60*1000, sub {
				      checkUpdates();
				      1;
				  });
}
sub lastCheck() {
    my %h = getVarsFromSh($localfile);
    my $t = $h{LASTCHECK};
    $t =~ s/_/  /g;
    $t || N("No check");
}
sub getTime() {
    my $d = localtime();
    $d =~ s/\s+/_/g;
    $d;
}
sub setLastTime() {
    my $date = getTime();
    setVar($localfile, 'LASTCHECK', $date);
}
sub checkConfig() {
    if (!-e $conffile) {
	logIt(N("Checking config file: Not present\n"));
	go2State('noconfig');
    } elsif (!isNetwork()) {
	logIt(N("Checking Network: seems disabled\n"));
	go2State('disconnected');
    } elsif (member($state_global, qw(disconnected noconfig))) {
	silentCheck(); #- state has changed, update
    }
}
sub checkUpdates() {
    member($state_global, qw(disconnected noconfig)) or silentCheck();
}
sub go2State {
    my $state = shift;
    $menu->destroy if $menu;
    $menu = setState($state);
    $state_global = $state;
    defined $textvw and refresh_status($state);
}
sub isNetwork() {
    my $network;
    if ($release <= 10.0) {
	$network = gethostbyname("mandrivaonline.com") ? 1 : 0;
    } elsif ($release <= 10.2) {
	require network::netconnect;
 	require network::tools;
	my ($netcnx, $netc, $intf) = ({}, {}, {});
    	network::netconnect::read_net_conf($netcnx, $netc, $intf);
	my ($_gw_intf, $_is_up, $gw_address, $_dns_server) = network::tools::get_internet_connection($netc, $intf);
	$network = to_bool($gw_address);
    } else {
	require network::network;
	require network::tools;
	my $net = {};
    	network::network::read_net_conf($net);
	my ($_gw_intf, $_is_up, $gw_address, $_dns_server) = network::tools::get_internet_connection($net);
	$network = to_bool($gw_address);
    }
    $network;
}
sub configure() {
    refresh_gui(0);
    fork_exec("/usr/sbin/mdkonline");
    $conf_launched = 1;
}
sub displayLogs() {
    my $w = ugtk2->new(N("Logs"), center => 1);
    gtkset_size_request($w->{window}, 500, 400);
    $w->{window}->signal_connect(delete_event => sub { $w->destroy; undef $wlog });
    $textview = Gtk2::TextView->new;
    $buffer = $textview->get_buffer;
    gtkadd($w->{window},
	   gtkpack_(Gtk2::VBox->new(0, 2),
		    1, create_scrolled_window(gtktext_insert($textview, $mLog)),
		    0, Gtk2::HSeparator->new,
		    0,  gtkpack_(Gtk2::HBox->new(0, 5),
				 0, gtksignal_connect(Gtk2::Button->new(N("Close")), 
						      clicked => sub { 
							  $w->destroy;
							  undef $wlog;
						      }),
				 1, Gtk2::Label->new(""),
				 0, gtksignal_connect(Gtk2::Button->new(N("Clear")),
						      clicked => sub {
							  $mLog = '';
							  $buffer->set_text($mLog);
						      }),
				)));
    $w;
}
sub shouldStart() {
    my %p = getVarsFromSh($localfile);
    my $ret = $p{AUTOSTART} eq 'FALSE' ? 0 : 1;
    $ret;
}
sub setState {
    my $state_type = shift;
    my $checkme;
    my $arr = $state{$state_type}{menu};
    my $tmp = gtkcreate_pixbuf($state{$state_type}{colour}[0]);
    $img->set_from_pixbuf($tmp);
    #my $tooltip = Gtk2::Tooltips->new;
    gtkset_tip(new Gtk2::Tooltips, $eventbox, formatAlaTeX(my_sprintf_fixutf8(translate($state{$state_type}{tt}[0]))));
    my $menu = Gtk2::Menu->new;
    foreach (@$arr) { 
	$menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label($actions{$_}{name})), activate => $actions{$_}{launch}));
    }
    $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new));
    $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("About..."))), activate => sub {
                                        my $ver = 1; # automatically set from spec file
                                        my $w = gtkset_name(Gtk2::AboutDialog->new, N("Mandriva Online %s"), $ver);
                                        $w->set_version($ver);
                                        $w->set_icon(Gtk2::Gdk::Pixbuf->new_from_file('/usr/share/icons/mini/mdkonline.png'));
                                        $w->set_copyright(N("Copyright (C) %s by Mandriva", '2001-2006'));
                                        $w->set_url_hook(sub {
                                                             my (undef, $url) = @_;
                                                             run_program::raw({ detach => 1 }, 'www-browser', $url);
                                                         });

                                        my $url = $online_site;
                                        $url =~ s/^https:/http:/;
                                        $w->set_website($url);
                                        #$w->set_license(formatAlaTeX(join("\n", cat_('/usr/share/common-licenses/GPL'))));
                                        #$w->set_wrap_license(1);
                                        $w->set_license(join('', cat_('/usr/share/common-licenses/GPL')));
                                        $w->set_comments(N("Mandriva Online gives access to Mandriva web services."));
                                        $w->set_website_label(N("Online WebSite"));
                                        $w->set_authors('Thierry Vignaud <vignaud@mandriva.com>');
                                        $w->show_all;
                                        $w->run;
                                        return 1;

                                        my $dialog = gtkset_border_width(_create_dialog(N("Mandriva Online %s"), $ver), 5);
                                        $dialog->action_area->pack_start(gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub { $dialog->destroy }),
                                                                         0,0,0);

                                        gtkpack_($dialog->box,
                                                 gtktext_insert(Gtk2::TextView->new,
                                                                join("\n",
                                                                     ,
                                                                 )
                                                            )
                                             );
             
                                    }));
    $menu->append(gtksignal_connect(gtkset_active($checkme = Gtk2::CheckMenuItem->new_with_label(N("Always launch on startup")), shouldStart()), toggled => sub { setAutoStart(uc(bool2text($checkme->get_active))) }));
    $checkme->show;
    $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Quit"))), activate => sub { mainQuit() }));
    $menu;
}
sub logIt {
    my $log = shift;
    my ($Second, $Minute, $Hour, undef, undef, undef, undef, undef, undef) = localtime();
    $mLog .= $Hour . ':' . $Minute . ':' . $Second . '  ' . $log;
    if (defined $wlog) { $buffer->insert_at_cursor($log) }
    log::explanations($log);
}
sub setVar {
    my ($file, $var, $st) = @_;
    my %s = getVarsFromSh($file);
    $s{$var} = $st;
    setVarsInSh($file, \%s);
}
sub setAutoStart {
    my $state = shift;
    my $date = getTime();
    if (-f $localfile) {
	setVar($localfile, 'AUTOSTART', $state);
    } else { output_p $localfile, 
	       qq(AUTOSTART=$state
LASTCHECK=$date
);
	 }
}
sub mainQuit() {
#    setAutoStart('FALSE');
    Glib::Source->remove($timeout) if $timeout;
    Gtk2->main_quit;
}