#!/usr/bin/perl
################################################################################
# MandrakeOnline                                                               # 
#                                                                              #
# Copyright (C) 2003-2004 Mandrakesoft                                         #
#                                                                              #
# Daouda Lo <daouda@mandrakesoft.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 lib qw(/usr/lib/libDrakX /usr/lib/libDrakX/drakfirsttime);
use common;
use mdkonline;

use Digest::MD5;


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

use Gtk2::TrayIcon;

eval { require ugtk2; ugtk2->import(qw(:all)); require Gtk2::Pango; };
if ($@) {
    print "This program cannot be run in console mode.\n";
    c::_exit(0);  #- skip ugtk2::END
}
my ($eventbox, $img);
add_icon_path("/usr/share/mdkonline/pixmaps/");
my ($menu, $timeout, $refreshtm);
my $nowindow = 1;
my $arch = arch();
my $online_site = "http://www.mandrakeonline.net/";
my ($av_pkgs, $update_label, $lastch, $mLog, $buffer, $textview, $wlog);
my ($need_update, $raisedwindow, $isAvailable) = (0, 0, 0);
my @proctable;
my $conffile = '/etc/sysconfig/mdkonline';
my $localdir = "$ENV{HOME}/.MdkOnline";
my $localfile = "$localdir/mdkonline";

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

#don't launch it twice :)
my @pids = fuzzy_pidofs(qr/\bmdkapplet\b/);
@pids > 1 and die "mdkapplet already running\n";

my %state = (
	     okay => {
		      colour => [ 'okay' ],
		      changes => [ 'busy', 'critical', 'disconnected' ],
		      menu => [ 'update', 'configureApplet', 'check', 'weblink' ],
		      tt => [ N("No updates available for your system") ]
		     },
 	     critical => {
			  colour => [ 'noconf' ],
			  changes => [ 'okay', 'busy', 'critical', 'disconnected' ],
			  menu => [ 'configureApplet', 'check', 'weblink' ],
			  tt => [ N("Config problems. Launch \"configure\" again") ]
			 },
	     busy => {
		      colour => [ 'busy' ], 
		      changes => [ 'okay', 'critical', 'error', 'disconnected' ],
		      menu => [ ],
		      tt => [ N("System is busy. Wait ...") ]
		     },
	     error => {
		       colour => [ 'error' ],
		       changes => [ 'okay' ],
		       menu => [ 'update', 'check', 'weblink' ],
		       tt => [ N("%s packages available for update", $av_pkgs) ]
		      },
	     noconfig => {
			  colour => [ 'noconf' ],
			  changes => [ 'okay' ],
			  menu => [ 'weblink', 'register' ],
			  tt => [ N("Service not available. Click on \"configuration\"") ]
			 },
	     disconnected => {
			      colour => [ 'disconnected' ],
			      changes => [ 'okay', 'busy', 'critical', 'error' ],
			      menu => [ 'confNetwork' ],
			      tt => [ N("Network is down. Click on \"configure Network\"") ]
			     },
	     disabled => {
			  colour => [ 'disabled' ],
			  changes => [ 'okay', 'busy', 'critical', 'error' ],
			  menu => [ 'configureApplet', 'weblink' ],
			  tt => [ N("Service deactivated. Waiting for payment...") ]
			 }
	    );
my %actions = (
	       'update' => { name => N("Launch Mandrake Update"), launch => sub { installUpdates() } },
	       'configureApplet' => { name => N("Configuration"), launch => sub { configure() } },
	       'check' => { name => N("Check Updates"), launch => sub { checkUpdates() } }, 
	       'weblink' => { name => N("Online WebSite"), launch => sub { getSite($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() }
				  }
			      }
			      $_[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') };

shouldStart() or die "$localfile should be set to TRUE";

checkUpdates();
cronUpdate();

$icon->show_all;
Gtk2->main;

ugtk2::exit(0);

sub showMainWindow() {
    my $w = Gtk2::Window->new('toplevel');
    $w->set_title(N("Mandrake Updates Applet"));
    $w->signal_connect(delete_event => sub { $w->destroy; $raisedwindow = 0 });
    $w->set_position('center');
    $w->set_icon(Gtk2::Gdk::Pixbuf->new_from_file('/usr/share/icons/mini/mdkonline.png'));
    my ($choice, $time, $update_label, $lastch);
    my $autocheck;
    gtkadd($w,
           gtkpack__(Gtk2::VBox->new(0,5),
		     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 { $update_label->set_label(N("Checking ...")); gtkflush(); checkUpdates(); $update_label->set_label($isAvailable ? N("Available") : N("Not Available")); setLastTime(); $lastch->set_label(lastCheck()); gtkflush() }),
				     1, gtksignal_connect(Gtk2::Button->new(N("See logs")), clicked => sub { if (defined $wlog) { $wlog->{window}->show } else { $wlog = displayLogs(); $wlog->main }}),
				    )
			   ),
		     gtkadd(gtkset_shadow_type(Gtk2::Frame->new(N("Status")), 'etched_in'),
			    gtkpack_(Gtk2::VBox->new(0, 3),
				     0, gtkpack_(Gtk2::HBox->new(0, 3),
						 0, Gtk2::Label->new(N("Network Connection: ")),
						 0, Gtk2::Label->new(isNetwork() ? N("Up") : N("Down"))
 						),
				     0, gtkpack_(Gtk2::HBox->new(0, 3),
						 0, Gtk2::Label->new(N("New Updates: ")),
						 0, $update_label = Gtk2::Label->new($isAvailable ? N("Available") : N("Not Available"))
						),
				     0, gtkpack_(Gtk2::HBox->new(0, 3),
						 0, Gtk2::Label->new(N("Last check: ")),
						 0, $lastch = Gtk2::Label->new(lastCheck())
						),
				    )
			   ),
		     gtkpack(Gtk2::HSeparator->new),
                     gtkpack(gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub {
						    if (defined $wlog) { $wlog->destroy; undef $wlog }; $w->destroy; $raisedwindow = 0;
					       })
			    )
		    ));
    $w->show_all
}
sub md5file {
    my @md5;
    foreach my $file (@_) {
	open(FILE, $file) or do { print STDERR "Can't open '$file': $!"; push @md5, "" };
	binmode(FILE);
	push @md5, Digest::MD5->new->addfile(*FILE)->hexdigest;
	close(FILE);
    }
    return wantarray() ? @md5 : $md5[0];
}
sub setLabel {
    my ($widget, $string) = shift;
    $widget->set_label($string) if defined $widget;
    gtkflush();
}
sub configNetwork { logIt(N("Launching drakconnect\n")); system "/usr/sbin/drakconnect &" }
sub installUpdates {
    my $binfile = '/usr/bin/mdkapplet';
    my $oldmd5 = md5file($binfile);
    logIt(N("Launching mdkupdate --applet\n")); 
    gtkroot()->set_cursor(Gtk2::Gdk::Cursor->new('watch')); 
    system "/usr/sbin/mdkupdate", "--applet"; 
    my $newmd5 = md5file($binfile);
#    print "NEW($newmd5) = OLD($oldmd5)\n"
    if ($newmd5 ne $oldmd5) { logIt(N("Mdkonline seems to be reinstalled, reloading applet ....")); exec('/usr/bin/mdkapplet') };
    silentCheck(); gtkflush(); 
    gtkroot()->set_cursor(Gtk2::Gdk::Cursor->new('left-ptr')); 
}
sub silentCheck {
    my $link = "http://www.mandrakeonline.net/online3_RemoteAction.php" . '?action=UpdateList';
    my %h = getVarsFromSh($conffile); 
    my $u;
    logIt(N("Computing new updates...\n"));
    my $lnk = $link . '&log=' . $h{LOGIN} . '&host=' . $h{MACHINE};
    logIt(N("Connecting to") . " $lnk ...\n");
    gtkflush(); go2State('busy'); gtkflush();
    my $response = mdkonline::get_from_URL($link, 'MdkAppletAgent/0.1');
    my $ct = $response->content;
#    print "\nCONTENTS = $ct\n";
#    logIt(N("Response from MandrakeOnline server\n") . "$ct");
    if ($ct =~ /rpm/) { $u = 11 } elsif ($ct =~ m/(\d+)/) { $u = sprintf("%d",$1); } else { $u = 10 };
    #   99 - log or host or action or pass empty, wrong action
    #   98 - wrong pass
    #   97 - host not active
#    print "Returned value = $u\n";
    my $retcode = {
		   10 => sub { okState() },
		   11 => sub {
		       my @contents = sort ( split /\n/, $ct);
		       my @t = split /\n/, `rpm -qa`;
		       my $comp = 0;
		       foreach my $cand (@contents) {
			   $cand =~ s/\.(i586|ppc|ia64|noarch|x86_64|amd64|ppc64).rpm//;
			   $cand =~ /(.*)-(.*)-(.*)$/; my ($name, $ver, $rel) = ($1, $2, $3);
			   $comp = compareWithInstalled($name, $ver, $rel, \@t);
			   #print "COMP =  $comp\n";
			   if ($comp) { $isAvailable = 1; logIt(N("Checking... Updates are available\n") . "\n"); go2State('error') and last } else { $isAvailable = 0 }
		       }
		       !$comp and okState();
		   },
		   96 => sub { logIt(N("Unknown state")) ; go2State('okay') },
		   97 => sub { logIt(N("Online services disabled. Contact MandrakeOnline 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') }
		  };
    $retcode->{$u}->();
}
sub okState { $isAvailable = 0; logIt(N("System is up to date\n")); go2State('okay') }
sub compareWithInstalled {
    my ($name, $ver, $rel, $t) = @_;
    my $isUpdate = 0;
    foreach my $p (@{$t}) { 
	$p =~ /(.*)-(.*)-(.*)$/; my ($n, $v, $r) = ($1, $2, $3);
	if ($name eq $n) {
	    #my $iu = rpmvercmp($ver, $v); my $ir = rpmvercmp( $rel,$r); 
	    if (rpmvercmp($ver, $v) > 0 || ( rpmvercmp($ver, $v) == 0 && rpmvercmp( $rel, $r) > 0)) {
		#print "$name-$ver-$rel $n-$v-$r *** CMPVER=$iu ** CMPREL = $ir \n";
		$isUpdate = 1 and last;
	    }
	}
    }
    $isUpdate
}
sub rpmverparse {
    my ($ver) = @_;
    my @verparts = ();
    while ( $ver ne "" ) {
        if ( $ver =~ /^([A-Za-z]+)/ ) {    # leading letters
            push ( @verparts, $1 );
            $ver =~ s/^[A-Za-z]+//;
        }
        elsif ( $ver =~ /^(\d+)/ ) {       # leading digits
            push ( @verparts, $1 );
            $ver =~ s/^\d+//;
        }
        else {                             # remove non-letter, non-digit
            $ver =~ s/^.//;
        }
    }
    return @verparts;
}
sub rpmvercmp {
    my ($a, $b) = @_;
    my @aparts;                            # list of version/release tokens
    my @bparts;
    my $apart;                             # individual token from array
    my $bpart;
    my $result;
    if ( $a eq $b ) {
        return 0;
    }
    @aparts = rpmverparse($a); 
    @bparts = rpmverparse($b); 
    while ( @aparts && @bparts ) {
        $apart = shift (@aparts);
        $bpart = shift (@bparts);
	if ( $apart =~ /^\d+$/ && $bpart =~ /^\d+$/ ) {    # numeric
            if ( $result = ( $apart <=> $bpart ) ) {
                return $result;
            }
        }
        elsif ( $apart =~ /^[A-Za-z]+/ && $bpart =~ /^[A-Za-z]+/ ) {    # alpha
            if ( $result = ( $apart cmp $bpart ) ) {
                return $result;
            }
        }
        else {    # "arbitrary" in original code
	    my $rema = shift(@aparts);
	    my $remb = shift(@bparts);
	    if ($rema && !$remb) { return 1 } elsif (!$rema && $remb) { return -1 }
	    #return -1;
        }
    }
    if (@aparts) {    # left over stuff in a, assume it's newer
	return 1;
    }
    elsif (@bparts) {    # leftover in b, assume it's newer
	return -1;
    }
    else {               # "should never happen"
	return 0;
    }
}
sub cronUpdate {
    my ($to) = shift;
    $timeout = Glib::Timeout->add(60*60*1000, sub {
				      checkUpdates();
				      1;
				  });
}
sub lastCheck {
    my %h = getVarsFromSh($localfile);
    my ($t, $l);
    $t = $h{LASTCHECK} ;
    $t =~ s/\_/  /g;
    $t ? $t : N("No check")
}
sub getTime {
    my $d = localtime();
    $d =~ s/\s+/_/g;
    $d
}
sub setLastTime {
    my $date = getTime() ;
    setVar($localfile, 'LASTCHECK', $date);
}
sub checkUpdates {
    if (!isNetwork()) {
	logIt(N("Checking Network: seems disabled\n"));
	go2State('disconnected')
    } elsif (!-e $conffile) {
	logIt(N("Checking config file: Not present\n"));
	go2State('noconfig')
    } else {
	silentCheck();
    }
    setLabel($update_label, $isAvailable ? N("Available") : N("Not Available"));
}
sub go2State {
    my $state = shift;
    $menu && $menu->destroy;
    $menu = setState($state);
}
sub isNetwork {
    my $network;
    $network = gethostbyname("mandrakeonline.net") ? 1 : 0;
    $network;
}
sub configure {
    system("/usr/sbin/mdkonline &");
    $refreshtm = Glib::Timeout->add(1*10*1000, sub {
					if (-e $conffile) { 
					    Glib::Source->remove($refreshtm);
					    silentCheck();
					}
					1;
				    });
}
sub getSite {
    my $link = shift;
    $link .= join('', @_);
    my $b = browser();
    system("$b " . $link . "&")
}
sub browser {
    require any;
    my $wm = any::running_window_manager();
    member ($wm, 'kwin', 'gnome-session') or $wm = 'other';
    my %Br = (
	      'kwin' => 'konqueror',
	      'gnome-session' => 'mozilla',
	      'other' => $ENV{BROWSER} || find { -x "/usr/bin/$_"} qw(mozilla konqueror galeon)
	     );
    $Br{$wm}
}
sub displayLogs {
    #    system "/usr/sbin/logdrake --explain=drakxtools &"
    #create_dialog(N("Logs"), $mLog, { use_markup => 1, height => 480, width => 580, scroll => 1 })
    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(my $close = Gtk2::Button->new(N("Close")), 
						      clicked => sub { 
							  $w->destroy;
							  undef $wlog
						      }),
				 1, Gtk2::Label->new(""),
				 0, gtksignal_connect(my $clear = 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 $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($state{$state_type}{tt}->[0]));
    my $menu = Gtk2::Menu->new;
    foreach (@$arr) { 
	my $l = $actions{$_}{name};
	$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 { getSite($online_site) }));
    $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Quit"))), activate => sub { mainQuit() }));
    $menu
}
sub logIt {
    my $log = shift;
    $mLog .= $log;
    if (defined $wlog) {
	$buffer->insert_at_cursor($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');
    Gtk2->main_quit
}