#!/usr/bin/perl ################################################################################ # Mandrakeonline # # # # Copyright (C) 2003-2004 Mandrakesoft # # # # Daouda Lo # # # # 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 Gtk2::Pango; use Digest::MD5; BEGIN { unshift @::textdomains, 'mdkonline' } use ugtk2; use Gtk2::TrayIcon; 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, $textvw, $state_global); 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"); my @pids = `ps -u $ENV{USER} | grep mdkapplet`; @pids > 1 and die "mdkapplet already running\n"; #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_("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\@mandrakeonline.net") ] }, busy => { colour => [ 'busy' ], changes => [ 'okay', 'critical', 'error', 'disconnected' ], menu => [ ], tt => [ N_("System is busy. Please wait ...") ] }, error => { colour => [ 'error' ], changes => [ 'okay' ], menu => [ 'update', 'check', 'weblink' ], tt => [ N_("New updates 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 => [ 'disconnected' ], 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\"") ] } ); 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 { 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: please use --force or -f option to launch applet"; 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 }); gtkset_size_request($w, 360, 240); $w->set_position('center'); $w->set_icon(Gtk2::Gdk::Pixbuf->new_from_file('/usr/share/icons/mini/mdkonline.png')); $textvw = Gtk2::TextView->new; my ($choice, $time, $update_label, $lastch); my $autocheck; gtkadd($w, gtkpack_(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 { checkUpdates() }), 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), wrap_mode => 'none')))), 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 } 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 refresh_status { my $status = shift; gtktext_insert($textvw, refresh_contents($status), wrap_mode => 'none'); } sub refresh_contents { my $status = shift; my $color = {}; 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' }; 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("Updates: "), $color->{second} ], [ common::sprintf_fixutf8($state{$status}{tt}[0]), $status eq 'okay' ? $color->{third} : $color->{first} ], ]; $contents } 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); 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) = @_; # list of version/release tokens my @aparts; my @bparts; # individual token from array my ($apart, $bpart, $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; } } # left over stuff in a or b, assume one of the two is newer if (@aparts) { return 1 } elsif (@bparts) { return -1 } else { return 0 } } sub cronUpdate { my ($to) = shift; $timeout = Glib::Timeout->add(3*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(); } } sub go2State { my $state = shift; $menu && $menu->destroy; $menu = setState($state); $state_global = $state; defined $textvw and refresh_status($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' => 'webclient-kde', 'gnome-session' => 'webclient-gnome', 'other' => $ENV{BROWSER} || find { -x "/usr/bin/$_"} qw(epiphany 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 $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(common::sprintf_fixutf8(translate$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(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; $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 }