#!/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 lib qw(/usr/lib/libDrakX); use standalone; # for explanations use common; use run_program; use feature 'state'; BEGIN { unshift @::textdomains, 'mdkonline' } use mygtk2 qw(gtknew); #- do not import gtkadd which conflicts with ugtk2 version use ugtk2 qw(:all); use lib qw(/usr/lib/libDrakX/drakfirsttime); use mdkonline; use Gtk2::Notify '-init', 'mdkapplet'; use Rpmdrake::open_db; # 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) { if (my $pid = mdkonline::is_running('mdkapplet')) { die "mdkapplet already running ($pid)\n"; } } ugtk2::add_icon_path("/usr/share/mdkonline/pixmaps/"); my $online_site = "http://www.mandrivalinux.com/"; my ($menu, $timeout, $network_timeout, $state_global, $sub_state); 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 $config{FIRST_CHECK_DELAY} ||= 5 * 60 * 1000; # default to 5 minutes $config{DISTRO_CHECK_DELAY} ||= 60*60*24; # default : one day #compatibility mkdir_p($localdir) if !-d $localdir; -e "$ENV{HOME}/.mdkonline" and system("mv", "$ENV{HOME}/.mdkonline", $localfile); my %local_config = getVarsFromSh($localfile); my %state = ( delayed => { colour => [ 'busy' ], changes => [ 'busy', 'critical', 'disconnected' ], menu => [ 'check' ], do_not_use_bubble => 1, tt => [ #-PO: here %s will be replaced by the local time (eg: "Will check updates at 14:03:50" N("Will check updates at %s", POSIX::strftime("%T", localtime(time() + $config{FIRST_CHECK_DELAY}/1000))) ], }, okay => { colour => [ 'okay' ], changes => [ 'busy', 'critical', 'disconnected' ], menu => [ 'check' ], do_not_use_bubble => 1, tt => [ N_("Your system is up-to-date") ] }, critical => { colour => [ 'noconf' ], changes => [ 'okay', 'busy', 'critical', 'disconnected' ], menu => [ 'check' ], tt => [ N_("Service configuration problem. Please check logs and send mail to support\@mandrivaonline.com") ] }, busy => { colour => [ 'busy' ], changes => [ 'okay', 'critical', 'error', 'disconnected' ], menu => [], do_not_use_bubble => 1, tt => [ N_("Please wait, finding available packages...") ] }, updates => { colour => [ 'error' ], changes => [ 'okay' ], menu => [ 'update', 'check' ], tt => [ N_("New updates are available for your system") ] }, new_distribution => { colour => [ 'bundle' ], changes => [ 'okay' ], menu => [ 'upgrade_distro', 'check' ], tt => [ N("A new stable distribution has been released") . "\n\n" . N("Do you want to upgrade?") ] }, 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 => [], tt => [ N_("Service is not activated. Please click on \"Online Website\"") ] }, locked => { colour => [ 'noconf' ], changes => [ 'okay', 'busy', 'critical', 'disconnected' ], menu => [ 'check' ], tt => [ N_("urpmi database locked") ], do_not_use_bubble => 1, }, notsupported => { colour => [ 'disabled' ], changes => [ 'okay', 'busy', 'critical', 'error' ], menu => [], tt => [ N_("Release not supported (too old release, or development release)") ] }, no_update_medium => { colour => [ 'noconf' ], changes => [ 'okay', 'busy', 'critical', 'disconnected' ], menu => [ 'check' ], tt => [ N_("No medium found. You must add some media through 'Software Media Manager'.") ], }, no_enabled_medium => { colour => [ 'noconf' ], changes => [ 'okay', 'busy', 'critical', 'disconnected' ], menu => [ 'check' ], tt => [ N("You already have at least one update medium configured, but all of them are currently disabled. You should run the Software Media Manager to enable at least one (check it in the \"%s\" column). Then, restart \"%s\".", N("Enabled"), 'mdkapplet') ], }, ); my %comm_codes = ( locked => { code => 2, status => 'locked', log => "urpmi database locked, skipping updating urpmi database", }, error_updating => { code => 3, status => 'critical', log => N_("Error updating media"), }, no_update_medium => { code => 4, status => 'no_update_medium', log => "no update media configured", }, no_enabled_medium => { code => 5, status => 'no_enabled_medium', log => "all update media are disabled", }, updates => { code => 6, status => 'updates', log => "Checking... Updates are available\n\n", }, uptodate => { code => 7, status => 'okay', log => "Packages are up to date\n", }, db_not_open => { code => 8, status => 'critical', log => "Failed to open urpmi database\n", }, ); my %actions = ( 'update' => { name => N("Install updates"), launch => sub { installUpdates() } }, 'check' => { name => N("Check Updates"), launch => \&checkUpdates }, 'confNetwork' => { name => N("Configure Network"), launch => sub { configNetwork() } }, 'upgrade_distro' => { name => N("Upgrade the system"), launch => \&upgrade }, ); my $icon = Gtk2::StatusIcon->new; #$icon->shape_combine_mask($img, 0, 0); $icon->signal_connect(popup_menu => sub { my ($_icon, $button, $time) = @_; $menu and $menu->popup(undef, undef, undef, undef, $button, $time); }); $icon->signal_connect(activate => sub { my %actions = ( no_update_medium => \&add_media, no_enabled_medium => \&add_media, updates => \&installUpdates, new_distribution => \&upgrade, ); my $action = $state_global; # default to updates rather than distro upgrade: if ($action eq 'new_distribution' && $sub_state eq 'updates') { $action = 'updates'; } $actions{$action}->() if ref $actions{$action}; }); foreach my $opt (@ARGV) { if ($opt eq '--force' || $opt eq '-f') { setAutoStart('TRUE') } if ($opt =~ /--(rpm-root|urpmi-root)=(.*)/) { $::rpmdrake_options{$1}[0] = $2; } } my $root = Rpmdrake::open_db::fast_open_urpmi_db()->{root}; my $new_distro; my $product_id = common::parse_LDAP_namespace_structure(cat_("$root/etc/product.id")); shouldStart() or die "$localfile should be set to TRUE: please use --force or -f option to launch applet\n"; go2State('delayed'); Glib::Timeout->add($config{FIRST_CHECK_DELAY}, sub { # schedule future checks: setup_cyclic_check(); # perform a test after initial delay: checkNetwork(); checkUpdates(); 0; }); $SIG{USR1} = 'IGNORE'; $SIG{USR2} = 'IGNORE'; $SIG{CHLD} = \&harvester; run_program::raw({ detach => 1 }, 'ionice', '-p', $$, '-n7'); Gtk2->main; ugtk2::exit(0); sub is_there_a_new_distributions() { #- contact the following URL to retrieve the list of released distributions. my $type = lc($product_id->{type}); $type =~ s/\s//g; my $list = "http://api.mandriva.com/distributions/$type.$product_id->{arch}.list?product=$product_id->{product}"; log::explanations("trying distributions list from $list"); my @lines = eval { my $urpm = Rpmdrake::open_db::fast_open_urpmi_db(); # prevent SIGCHILD handler's waitpid to interfere with urpmi waiting # for curl exit code, which broke downloads: local $SIG{CHLD} = 'DEFAULT'; if (member($product_id->{version}, qw(2007.1 2008.0 2008.1))) { require mdkapplet_urpm; mdkapplet_urpm::ensure_valid_cachedir($urpm); mdkapplet_urpm::get_content($urpm, $list); } else { urpm::ensure_valid_cachedir($urpm); urpm::download::get_content($urpm, $list); } }; if (my $err = $@) { log::explanations("failed to download distribution list:\n$err"); return; # not a fatal error } if (!@lines) { log::explanations("empty distribution list"); return; } my @distros = map { common::parse_LDAP_namespace_structure(chomp_($_)) } @lines; # only compare first distro: if it's not the same as the currently installed one, # then it's the most recent release: my $new_distribution = $distros[0]; return if !member($product_id->{version}, map { $_->{version} } @distros); if ($new_distribution && $new_distribution->{version} ne $product_id->{version}) { $new_distro = $new_distribution; log::explanations(sprintf("new '%s' distribution was released on %s", $new_distro->{version}, $new_distro->{release_date})); return 1; } } my ($mdv_update_pid, $checker_pid, $media_manager_pid, $refork_gurpmi); # Signal management sub harvester { my ($_signame, $_clean) = @_; my ($childpid, @pids); my $mdvupdate_returned; do { $childpid = waitpid(-1, &WNOHANG); my $status = $? >> 8; if ($mdv_update_pid && $mdv_update_pid == $childpid) { undef $mdv_update_pid; if ($refork_gurpmi && $status) { $mdv_update_pid = fork_gurpmi(); } else { $mdvupdate_returned = 1; } undef $refork_gurpmi; if ($status) { ugtk2::ask_warn(N("Error"), N("Installation failed"), ); } else { ugtk2::ask_warn(N("Congratulations"), N("Upgrade to Mandriva %s release was successfull.", $new_distro->{version}), N("You should restart your system.") ); } } elsif ($checker_pid && $checker_pid == $childpid) { undef $checker_pid; my ($state) = grep { $_->{code} eq $status } values %comm_codes; if ($state) { logIt($state->{log}); $sub_state = $state->{status}; go2State($new_distro && $config{DO_NOT_ASK_FOR_DISTRO_UPGRADE} !~ /^true$/i && $local_config{DO_NOT_ASK_FOR_DISTRO_UPGRADE} !~ /^true$/i ? 'new_distribution' : $sub_state); } } elsif ($media_manager_pid && $media_manager_pid == $childpid) { undef $media_manager_pid; } push @pids, $childpid; } while $childpid > 0; Glib::Timeout->add(200, sub { silentCheck(); 0 }) if $mdvupdate_returned; return @pids; } sub fork_exec { my $pid = run_program::raw({ detach => 1 }, @_); return $pid; } sub configNetwork() { logIt(N_("Launching drakconnect\n")); fork_exec("/usr/sbin/drakconnect") } sub restart_applet() { logIt(N_("Mandriva Online seems to be reinstalled, reloading applet ....")); exec($0, '--auto-update'); } sub confirm_upgrade() { local $mygtk2::left_padding = 0; my $width = 500; my $w = ugtk2->new(N("A new stable distribution has been released")); my $warn_me = text2bool($local_config{DO_NOT_ASK_FOR_DISTRO_UPGRADE}); gtkadd($w->{window}, gtknew('VBox', children_tight => [ Gtk2::Banner->new( (find { -e $_ } qw(/usr/share/mcc/themes/default/rpmdrake-mdk.png /usr/share/icons/large/mdkonline.png)), N("Distribution Upgrade") ), gtknew('Label_Left', text => N("A new stable distribution has been released."), # workaround infamous 6 years old gnome bug #101968: width => $width -50), gtknew('HButtonBox', layout => 'start', children_tight => [ my $link = Gtk2::LinkButton->new($new_distro->{url}, N("More info about this new version")), ]), gtknew('Label_Left', text => N("Do you want to upgrade to the '\%s' distribution?", $new_distro->{name} || $new_distro->{version}), # workaround infamous 6 years old gnome bug #101968: width => $width -50), gtknew('CheckButton', text => N("Do not ask me next time"), active_ref => \$warn_me), create_okcancel($w, N("Yes"), N("No")), ]), ); $link->set_uri_hook(sub { my (undef, $url) = @_; run_program::raw({ detach => 1, setuid => get_parent_uid() }, 'www-browser', $url); }); $w->{ok}->grab_focus; my $res = $w->main; setVar('DO_NOT_ASK_FOR_DISTRO_UPGRADE', bool2text($warn_me)); $local_config{DO_NOT_ASK_FOR_DISTRO_UPGRADE} = bool2text($warn_me); $res; } sub upgrade() { return if !confirm_upgrade(); logIt("backuping urpmi configuration"); cp_af("$root/etc/urpmi/urpmi.cfg", "$ENV{HOME}/urpmi.cfg.backup." . int(rand 100000)); my $urpm = Rpmdrake::open_db::fast_open_urpmi_db(); my $urpmi_lock = eval { local $urpm->{fatal} = sub { die @_ }; urpm::lock::urpmi_db($urpm, 'exclusive'); }; if (my $err = $@) { logIt(sprintf("locking urpmi database failed: %s"), $err); return; } undef $urpmi_lock; logIt("removing all existing urpmi media for upgrade"); run_program::run('urpmi.removemedia', if_($root, '--urpmi-root', $root), '-a'); logIt("adding urpmi media for new distribution"); my $product_type = lc($product_id->{type}); $product_type =~ s/\s//g; my $mirror_list = "http://api.mandriva.com/mirrors/$product_type.$new_distro->{version}.$product_id->{arch}.list"; system('gurpmi.addmedia', '--silent-success', if_($root, "--urpmi-root=$root"), '--distrib', '--mirrorlist', $mirror_list) and return; logIt("upgrading urpmi and rpmdrake"); logIt("upgrading the whole system"); # rerun gurpmi in case there was an issue (with eg: big transactions: $refork_gurpmi = 1; # we cannot use installUpdates() as MandrivaUpdate needs the media # flaged as update (or else, we need to add a new option to MandrivaUpdate): fork_gurpmi('--replacefiles'); } sub fork_gurpmi { $mdv_update_pid = fork_exec('gurpmi', '--auto', '--auto-select', if_($root, "--urpmi-root=$root"), @_); } sub add_media() { return if $media_manager_pid; logIt("Launching 'Software Media Manager'"); $media_manager_pid = fork_exec('/usr/sbin/edit-urpm-sources.pl', '--no-splash', if_($root, "--urpmi-root=$root")); silentCheck(); gtkflush(); } sub installUpdates() { my $binfile = $0; return if $mdv_update_pid; my $oldmd5 = $release <= 10.2 ? mdkonline::md5file($binfile) : common::md5file($binfile); logIt(N_("Launching MandrivaUpdate\n")); $mdv_update_pid = fork_exec('MandrivaUpdate', '--no-media-update', '--no-confirmation', '--no-splash', if_($root, "--urpmi-root=$root")); my $newmd5 = $release <= 10.2 ? mdkonline::md5file($binfile) : common::md5file($binfile); restart_applet() if $newmd5 ne $oldmd5; silentCheck(); gtkflush(); } sub silentCheck() { state $check_time; my $new_time = time(); if (!$check_time || $new_time - $check_time > $config{DISTRO_CHECK_DELAY}) { $check_time = $new_time; is_there_a_new_distributions(); } return if $mdv_update_pid || $checker_pid; logIt(N_("Computing new updates...\n")); logIt("Connecting to ...\n"); # i18n bug to fix in cooker my $w = $::main_window ? $::main_window->window : undef; gtkset_mousecursor_wait($w); go2State('busy'); gtkset_mousecursor_normal($w); # are there any updates ? $checker_pid = fork(); if (defined $checker_pid) { return if $checker_pid; # parent # immediate exit, else forked gtk+ object destructors will badly catch up parent applet my $_safe = before_leaving { POSIX::_exit(0) }; # be nice with other processes: setpriority(0, $$, 7); # 0 is PRIO_PROCESS my $exit = sub { my ($state) = @_; POSIX::_exit($comm_codes{$state}{code}); }; my $will_not_update_media; require urpm; require urpm::lock; # so that get_inactive_backport_media() doesn't vivify $urpm->{media}: my $urpm = Rpmdrake::open_db::fast_open_urpmi_db(); { local $urpm->{fatal} = sub { print "Fatal: @_\n"; $will_not_update_media = 1; }; local $urpm->{error} = $urpm->{fatal}; urpm::lock::urpmi_db($urpm, 'exclusive', 1); } $exit->('locked') if $will_not_update_media; if (!run_program::raw({ sensitive_arguments => 1 }, 'urpmi.update', '--update')) { $exit->('error_updating') if $will_not_update_media; } # update inactive backport media: my @inactive_backport_media = Rpmdrake::open_db::get_inactive_backport_media($urpm); logIt("updating inactive backport media " . join(', ', @inactive_backport_media)) if @inactive_backport_media; run_program::run('urpmi.update', $_) foreach @inactive_backport_media; require urpm::select; require urpm::media; urpm::media::configure($urpm, update => 1); my @update_medias = grep { $_->{update} } @{$urpm->{media}}; if (!@update_medias) { $exit->('no_update_medium'); } elsif (!any { ! $_->{ignore} } @update_medias) { $exit->('no_enabled_medium'); } if (my $db = urpm::db_open_or_die($urpm)) { my $requested = {}; my $state = {}; my $need_restart = urpm::select::resolve_dependencies( $urpm, $state, $requested, callback_choices => sub { 0 }, priority_upgrade => $urpm->{options}{'priority-upgrade'}, auto_select => 1, ); my @requested_strict = map { scalar $_->fullname } @{$urpm->{depslist}}[keys %{$state->{selected}}]; if ($need_restart || @requested_strict) { # FIXME: log first found pkgs? $exit->('updates'); } else { $exit->('uptodate'); } } else { $exit->('db_not_open'); } $exit->('updates'); } else { logIt("cannot fork: %s", "update checker ($!)"); go2State('critical'); } } sub okState() { logIt(N_("System is up-to-date\n")); go2State('okay') } sub setup_cyclic_check() { $network_timeout = Glib::Timeout->add(2000, sub { checkNetwork(); 1 }); $timeout = Glib::Timeout->add($config{UPDATE_FREQUENCY}*1000, sub { checkUpdates(); 1; }); } sub getTime() { my $d = localtime(); $d =~ s/\s+/_/g; $d; } sub setLastTime() { my $date = getTime(); setVar('LASTCHECK', $date); } sub checkNetwork() { return if $checker_pid; require network::tools; if (!network::tools::has_network_connection()) { # do not notify if already done: return if member($state_global, qw(disconnected)); logIt(N_("Checking Network: seems disabled\n")); go2State('disconnected'); } elsif (member($state_global, qw(disconnected))) { silentCheck(); #- state has changed, update } } sub checkUpdates() { member($state_global, qw(disconnected)) or silentCheck(); } sub go2State { my $state = shift; $menu->destroy if $menu; $menu = setState($state); $state_global = $state; gtkflush(); } sub shouldStart() { to_bool($local_config{AUTOSTART} ne 'FALSE'); } sub setState { my ($state) = @_; my $checkme; state $previous_state; my @arr = @{$state{$state}{menu}}; my $tmp = gtkcreate_pixbuf($state{$state}{colour}[0]); $icon->set_from_pixbuf($tmp); $icon->set_tooltip(formatAlaTeX(translate($state{$state}{tt}[0]))); my @invisible_states = qw(delayed okay disconnected locked); $icon->set_visible(!member($state, @invisible_states)); # do not show icon while checking if previously hidden: $icon->set_visible(0) if $state eq 'busy' && member($previous_state, @invisible_states); $previous_state = $state; gtkflush(); # so that bubbles are displayed on right icon if ($state{$state}{tt}[0] && $icon->isa('Gtk2::StatusIcon') && !$state{$state}{do_not_use_bubble}) { my $bubble = Gtk2::Notify->new_with_status_icon(N("Warning"), formatAlaTeX(translate($state{$state}{tt}[0])) . "\n", '/usr/share/icons/mdkonline.png', $icon); if ($state eq 'new_distribution') { $bubble->add_action('clicked', N("Upgrade the system"), \&upgrade); if ($sub_state eq 'updates') { push @arr, 'update'; } } elsif ($state eq 'updates') { unshift @arr, 'upgrade_distro' if $new_distro; $bubble->add_action('clicked', N("Install updates"), \&installUpdates); } elsif (member($state, qw(no_enabled_medium no_update_medium))) { $bubble->add_action('clicked', N("Add media"), \&add_media); } $bubble->set_timeout(5000); eval { $bubble->show }; } 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 $url = $online_site; $url =~ s/^https:/http:/; my $w = gtknew('AboutDialog', name => N("Mandriva Online %s", $ver), copyright => N("Copyright (C) %s by Mandriva", '2001-2008'), license => join('', cat_('/usr/share/common-licenses/GPL')), icon => '/usr/share/icons/mini/mdkonline.png', comments => N("Mandriva Online gives access to Mandriva web services."), website => $url, website_label => N("Online WebSite"), authors => 'Thierry Vignaud ', artists => 'Hélène Durosini', translator_credits => #-PO: put here name(s) and email(s) of translator(s) (eg: "John Smith ") N("_: Translator(s) name(s) & email(s)\n"), transient_for => $::main_window, modal => 1, position_policy => 'center-on-parent', ); $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; log::explanations($log); } sub setVar { my ($var, $st) = @_; my %s = getVarsFromSh($localfile); $s{$var} = $st; setVarsInSh($localfile, \%s); } sub setAutoStart { my $state = shift; my $date = getTime(); if (-f $localfile) { setVar('AUTOSTART', $state); } else { output_p($localfile, qq(AUTOSTART=$state LASTCHECK=$date )); } } sub mainQuit() { # setAutoStart('FALSE'); Glib::Source->remove($timeout) if $timeout; Glib::Source->remove($network_timeout) if $network_timeout; Gtk2->main_quit; }