#!/usr/bin/perl ################################################################################ # Mandriva Online # # # # Copyright (C) 2003-2006 Mandriva # # # # Daouda Lo # # Thierry Vignaud # # # # 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(); my %config = getVarsFromSh('/etc/sysconfig/mdkapplet'); $config{UPDATE_FREQUENCY} ||= 3*60*60; # default to 3hours 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(); } }, '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 ($_[1]->button == 1) { installUpdates() if $state_global eq 'updates'; } elsif ($_[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(); setup_cyclic_check(); $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, @pids); do { $childpid = waitpid(-1, &WNOHANG); push @pids, $childpid; WIFEXITED($?) and refresh_gui(1); } while $childpid > 0; return @pids; } sub fork_exec { my $pid = run_program::raw({ detach => 1 }, @_); refresh_gui(1); return $pid; } 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); 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 %h = getVarsFromSh($conffile); my $u; logIt(N("Computing new updates...\n")); logIt(N("Connecting to") . " ...\n"); # i18n bug to fix in cooker 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 = eval { mdkonline::soap_is_new_update_for_distro($h{HOST_ID}, $h{HOST_KEY}) }; $response = { code => 0, data => { isUpdateAvailable => 1 } }; my $status_err = mdkonline::check_server_response($response); if ($status_err eq 'OK') { # are there any updates ? if (my $data = $response->{data}) { if ($data->{isUpdateAvailable}) { my $will_not_update_media; require urpm::lock; my $urpm = urpm->new; { local $urpm->{fatal} = sub { logIt(N("%s database locked", 'urpmi')); logIt("skipping updating urpmi database"); print "Fatal: @_\n"; $will_not_update_media = 1; }; local $urpm->{error} = $urpm->{fatal}; urpm::lock::urpmi_db($urpm, 'exclusive', 1); } if (!$will_not_update_media and my $res = !run_program::raw({}, 'urpmi.update', '--update')) { # shoud wait for it while gtkflus()ing: { detach => 1 } logIt(N("Error updating media") . $res); go2State('critical'); return 0; } use urpm; # to force require require urpm::select; require urpm::media; $urpm = urpm->new; urpm::media::configure($urpm); logIt(N("Computing new updates...\n")); if (my $db = urpm::db_open_or_die($urpm)) { my $h = $urpm->request_packages_to_upgrade($db, {}, {}); if (my @pkgs = grep { !$_->flag_skip } map { $urpm->{depslist}[$_] } keys %$h) { go2State('updates'); logIt(N("Checking... Updates are available\n") . "\n"); } else { go2State('okay'); logIt(N("Packages are up to date") . "\n"); # mesg from urpmi return; } } else { go2State('critical'); logIt(N("Failed to open urpmi database") . "\n"); return; } go2State('updates'); logIt(N("Checking... Updates are available\n") . "\n"); } else { # no update okState(); } } else { logIt(N("An error occurred")); } } 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) { 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 setup_cyclic_check() { Glib::Timeout->add(10*1000, sub { checkConfig(); 1; }); $timeout = Glib::Timeout->add($config{UPDATE_FREQUENCY}*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) { my $logged if 0; logIt(N("Checking config file: Not present\n")) if !$logged; $logged = 1; } 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->signal_connect(response => sub { $_[0]->destroy }); $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-2007')); $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 '); $w->show_all; $w->run; return 1; })); $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; }