#!/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; 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 %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' ], urgency => 'low', tt => [ N("A new version of Mandriva Linux 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; 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'); my $width = 500; my @common = ( # workaround infamous 6 years old gnome bug #101968: width => $width - 50, ); Gtk2->main; ugtk2::exit(0); sub is_there_a_new_distributions() { $product_id = common::parse_LDAP_namespace_structure(cat_("$root/etc/product.id")); return if $product_id->{product} =~ /Flash/; #- contact the following URL to retrieve the list of released distributions. my $type = lc($product_id->{type}); $type =~ s/\s//g; my $extra_path = $::testing || uc($config{TEST_DISTRO_UPGRADE}) eq 'YES' ? 'testing-' : ''; my $list = "http://api.mandriva.com/distributions/$extra_path$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); # 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; $mdvupdate_returned = 1; } elsif ($checker_pid && $checker_pid == $childpid) { undef $checker_pid; my ($state) = grep { $_->{code} eq $status } values %comm_codes; if ($state) { log::explanations($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 configNetwork() { log::explanations(N_("Launching drakconnect\n")); fork_exec("/usr/sbin/drakconnect") } sub confirm_upgrade() { local $mygtk2::left_padding = 0; my $w = ugtk2->new(N("New version of Mandriva Linux distribution")); my $warn_me = text2bool($local_config{DO_NOT_ASK_FOR_DISTRO_UPGRADE}); gtkadd($w->{window}, gtknew('VBox', children_tight => [ get_banner(), gtknew('Label_Left', text => N("A new version of Mandriva Linux distribution has been released."), @common), 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}), @common), 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 ? really_confirm_upgrade() : 0; } sub really_confirm_upgrade() { local $mygtk2::left_padding = 0; my $w = ugtk2->new(N("New version of Mandriva Linux distribution")); # estimated package size: my $c; foreach (run_program::get_stdout('rpm', '-qa', '--qf', '%{Archivesize}\n')) { $c += $_; } $c = formatXiB($c); gtkadd($w->{window}, gtknew('VBox', children_tight => [ get_banner(), gtknew('Label_Left', text => N("This upgrade requires high bandwidth network connection (cable, xDSL, ...) and may take several hours to complete."), @common), gtknew('Label_Left', text => N("Estimated download data will be %s", $c), @common), gtknew('Label_Left', text => N("You should close all other running applications before continuing.") . (1 || detect_devices::isLaptop() ? ' ' . N("You should put your laptop on AC and favor ethernet connection over wifi, if available.") : ''), @common), create_okcancel($w, N("Yes"), N("No")), ]), ); $w->{ok}->grab_focus; return $w->main; } sub upgrade() { return if $mdv_update_pid; return if !confirm_upgrade(); $mdv_update_pid = fork_exec('mdkapplet-upgrade-helper', "--new_distro_version=$new_distro->{version}", if_($root, "--urpmi-root=$root")); } sub add_media() { return if $media_manager_pid; log::explanations("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() { return if $mdv_update_pid; log::explanations(N_("Launching MandrivaUpdate\n")); $mdv_update_pid = fork_exec('MandrivaUpdate', '--no-media-update', '--no-confirmation', '--no-splash', if_($root, "--urpmi-root=$root")); silentCheck(); gtkflush(); } sub silentCheck() { state $check_time; my $new_time = time(); undef $new_distro; 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; log::explanations(N_("Computing new updates...\n")); log::explanations("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', if_($root, "--urpmi-root=$root"))) { $exit->('error_updating') if $will_not_update_media; } # update inactive backport media: my @inactive_backport_media = Rpmdrake::open_db::get_inactive_backport_media($urpm); log::explanations("updating inactive backport media " . join(', ', @inactive_backport_media)) if @inactive_backport_media; run_program::run('urpmi.update', if_($root, "--urpmi-root=$root"), $_) 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 { log::explanations("cannot fork: %s", "update checker ($!)"); go2State('critical'); } } sub okState() { log::explanations(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)); log::explanations(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 = eval { gtkcreate_pixbuf($state{$state}{colour}[0]) }; $icon->set_from_pixbuf($tmp) if $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("More Information"), \&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_urgency($state{$state}{urgency}) if $state{$state}{urgency}; $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 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; }