#!/usr/bin/perl #***************************************************************************** # # Copyright (c) 2002 Guillaume Cottenceau # Copyright (c) 2002-2006 Thierry Vignaud # Copyright (c) 2003, 2004, 2005 MandrakeSoft SA # Copyright (c) 2005, 2006 Mandriva SA # # 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. # #***************************************************************************** # # $Id$ use strict; use MDK::Common 'any'; use lib qw(/usr/lib/libDrakX); use common; use POSIX qw(_exit); use URPM; use utf8; BEGIN { $::no_global_argv_parsing = 1 } use standalone; BEGIN { #- we want to run this code before the Gtk->init of the use-my_gtk my $basename = sub { local $_ = shift; s|/*\s*$||; s|.*/||; $_ }; any { /^--?h/ } @ARGV and do { printf join("\n", N("Usage: %s [OPTION]...", $basename->($0)), N(" --changelog-first display changelog before filelist in the description window"), N(" --media=medium1,.. limit to given media"), N(" --merge-all-rpmnew propose to merge all .rpmnew/.rpmsave files found"), N(" --mode=MODE set mode (install (default), remove, update)"), N(" --no-confirmation don't ask first confirmation question in update mode"), N(" --no-media-update don't update media at startup"), N(" --no-verify-rpm don't verify packages signatures"), N(" --parallel=alias,host be in parallel mode, use \"alias\" group, use \"host\" machine to show needed deps"), N(" --pkg-nosel=pkg1,.. show only these packages"), N(" --pkg-sel=pkg1,.. preselect these packages"), N(" --root force to run as root"), N(" --search=pkg run search for \"pkg\""), "" ); exit 0; }; } BEGIN { #- for mcc if ("@ARGV" =~ /--embedded (\w+)/) { $::XID = $1; $::isEmbedded = 1; } } use rpmdrake; use urpm::lock; use urpm::install; use urpm::signature; use urpm::get_pkgs; use urpm::select; #- This is needed because text printed by Gtk2 will always be encoded #- in UTF-8; we first check if LC_ALL is defined, because if it is, #- changing only LC_COLLATE will have no effect. use POSIX qw(setlocale LC_ALL LC_COLLATE strftime); use locale; my $collation_locale = $ENV{LC_ALL}; if ($collation_locale) { $collation_locale =~ /UTF-8/ or setlocale(LC_ALL, "$collation_locale.UTF-8"); } else { $collation_locale = setlocale(LC_COLLATE); $collation_locale =~ /UTF-8/ or setlocale(LC_COLLATE, "$collation_locale.UTF-8"); } *urpm::msg::translate = \&common::translate; our %options; foreach (@ARGV) { /^-?-(\S+)$/ or next; my $val = $1; if ($val =~ /=/) { my ($name, $values) = split /=/, $val; my @values = split /,/, $values; $options{$name} = \@values if @values; } else { $options{$val} = 1; } } our $MODE = ref $options{mode} ? $options{mode}[0] : undef; unless ($MODE) { $MODE = 'install'; $0 =~ m|remove$| and $MODE = 'remove'; $0 =~ m|update$|i and $MODE = 'update'; } my $default_list_mode; $default_list_mode = 'all' if $MODE eq 'install'; if ($MODE eq 'remove') { $default_list_mode = 'installed'; } elsif ($MODE eq 'update') { $default_list_mode = 'all_updates'; } eval { require mygtk2; require ugtk2; ugtk2->import(qw(:all)); mygtk2->import(qw(gtknew)); #- do not import anything else, especially gtkadd() which conflicts with ugtk2 one require Gtk2::Pango; require Gtk2::Gdk::Keysyms; }; if ($@) { print "This program cannot be run in console mode.\n"; POSIX::_exit(0); #- skip ugtk2::END } $MODE eq 'update' || $options{root} and require_root_capability(); $ugtk2::wm_icon = "title-$MODE"; $::isStandalone = 1; $::noborderWhenEmbedded = 1; package gurpm; ugtk2->import(':all'); mygtk2->import(qw(gtknew)); our ($mainw, $label, $progressbar, $vbox, $cancel, $hbox_cancel); sub init { my ($title, $initializing, %options) = @_; $mainw = ugtk2->new($title, %options); $label = gtknew('Label', text => $initializing); $progressbar = gtknew('ProgressBar', width => 300); gtkadd($mainw->{window}, $vbox = gtknew('VBox', spacing => 5, border_width => 6, children_tight => [ $label, $progressbar ])); $mainw->{rwindow}->set_position('center-on-parent'); $mainw->sync; } sub label { $label->set($_[0]); select(undef, undef, undef, 0.1); #- hackish :-( $mainw->flush; } sub progress { $progressbar->set_fraction($_[0]); $mainw->flush; } sub end() { $mainw and $mainw->destroy; $mainw = undef; $cancel = undef; #- in case we'll do another one later } sub validate_cancel { my ($cancel_msg, $cancel_cb) = @_; if (!$cancel) { gtkpack__( $vbox, $hbox_cancel = gtkpack__( gtknew('HButtonBox'), $cancel = gtknew('Button', text => $cancel_msg, clicked => \&$cancel_cb), ), ); } $cancel->set_sensitive(1); $cancel->show; } sub invalidate_cancel() { $cancel and $cancel->set_sensitive(0); } sub invalidate_cancel_forever() { $hbox_cancel or return; $hbox_cancel->destroy; $mainw->shrink_topwindow; } package main; our $w; my $changelog_first; my $treeview_dialog_run = 0; our $statusbar; sub interactive_msg_ { interactive_msg(@_, if_($::main_window, transient => $::main_window)) } sub interactive_list_ { interactive_list(@_, if_($::main_window, transient => $::main_window)) } sub wait_msg_ { wait_msg(@_, if_($::main_window, transient => $::main_window)) } sub wait_msg_with_banner { push @_, banner => 1 if $::isEmbedded && !$treeview_dialog_run; &statusbar_msg } sub interactive_msg_with_banner { push @_, banner => 1 if $::isEmbedded; &interactive_msg_ } $> and (interactive_msg_(N("Running in user mode"), N("You are launching this program as a normal user. You will not be able to perform modifications on the system, but you may still browse the existing database."), yesno => 1, text => { no => N("Cancel"), yes => N("Ok") }) or myexit(0)); my $dont_show_selections = $> ? 1 : 0; #- /usr/share/rpmlint/config (duplicates are normal, so that we are not too far away from .py) my %group_icons = ( N("Accessibility") => 'accessibility_section', N("Archiving") => 'archiving_section', join('|', N("Archiving"), N("Backup")) => 'backup_section', join('|', N("Archiving"), N("Cd burning")) => 'cd_burning_section', join('|', N("Archiving"), N("Compression")) => 'compression_section', join('|', N("Archiving"), N("Other")) => 'other_archiving', N("Books") => 'documentation_section', join('|', N("Books"), N("Computer books")) => 'documentation_section', join('|', N("Books"), N("Faqs")) => 'documentation_section', join('|', N("Books"), N("Howtos")) => 'documentation_section', join('|', N("Books"), N("Literature")) => 'documentation_section', join('|', N("Books"), N("Other")) => 'education_other_section', N("Cluster") => 'parallel_computing_section', join('|', N("Cluster"), N("Message Passing")) => '', join('|', N("Cluster"), N("Queueing Services")) => '', N("Communications") => 'communications_section', N("Databases") => 'databases_section', N("Development") => 'development_section', join('|', N("Development"), N("C")) => '', join('|', N("Development"), N("C++")) => '', join('|', N("Development"), N("Databases")) => 'databases_section', join('|', N("Development"), N("GNOME and GTK+")) => 'gnome_section', join('|', N("Development"), N("Java")) => '', join('|', N("Development"), N("KDE and Qt")) => 'kde_section', join('|', N("Development"), N("Kernel")) => 'hardware_configuration_section', join('|', N("Development"), N("Other")) => 'development_tools_section', join('|', N("Development"), N("Perl")) => '', join('|', N("Development"), N("PHP")) => '', join('|', N("Development"), N("Python")) => '', N("Editors") => 'emulators_section', N("Education") => 'education_section', N("Emulators") => 'emulators_section', N("File tools") => 'file_tools_section', N("Games") => 'amusement_section', join('|', N("Games"), N("Adventure")) => 'adventure_section', join('|', N("Games"), N("Arcade")) => 'arcade_section', join('|', N("Games"), N("Boards")) => 'boards_section', join('|', N("Games"), N("Cards")) => 'cards_section', join('|', N("Games"), N("Other")) => 'other_amusement', join('|', N("Games"), N("Puzzles")) => 'puzzle_section', join('|', N("Games"), N("Sports")) => 'sport_section', join('|', N("Games"), N("Strategy")) => 'strategy_section', N("Graphical desktop") => 'office_section', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("Enlightenment")) => '', join('|', N("Graphical desktop"), N("FVWM based")) => '', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("GNOME")) => 'gnome_section', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("Icewm")) => '', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("KDE")) => 'kde_section', join('|', N("Graphical desktop"), N("Other")) => 'more_applications_other_section', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("Sawfish")) => '', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("WindowMaker")) => '', join('|', N("Graphical desktop"), #-PO: This is a package/product name. Only translate it if needed: N("Xfce")) => '', N("Graphics") => 'graphics_section', N("Monitoring") => 'monitoring_section', N("Multimedia") => 'multimedia_section', join('|', N("Multimedia"), N("Video")) => 'video_section', N("Networking") => 'networking_section', join('|', N("Networking"), N("Chat")) => 'chat_section', join('|', N("Networking"), N("File transfer")) => 'file_transfer_section', join('|', N("Networking"), N("IRC")) => 'irc_section', join('|', N("Networking"), N("Instant messaging")) => 'instant_messaging_section', join('|', N("Networking"), N("Mail")) => 'mail_section', join('|', N("Networking"), N("News")) => 'news_section', join('|', N("Networking"), N("Other")) => 'other_networking', join('|', N("Networking"), N("Remote access")) => 'remote_access_section', join('|', N("Networking"), N("WWW")) => 'networking_www_section', N("Office") => 'office_section', N("Public Keys") => 'packaging_section', N("Publishing") => 'publishing_section', N("Sciences") => 'sciences_section', join('|', N("Sciences"), N("Astronomy")) => 'astronomy_section', join('|', N("Sciences"), N("Biology")) => 'biology_section', join('|', N("Sciences"), N("Chemistry")) => 'chemistry_section', join('|', N("Sciences"), N("Computer science")) => 'computer_science_section', join('|', N("Sciences"), N("Geosciences")) => 'geosciences_section', join('|', N("Sciences"), N("Mathematics")) => 'mathematics_section', join('|', N("Sciences"), N("Other")) => 'other_sciences', join('|', N("Sciences"), N("Physics")) => 'physics_section', N("Shells") => 'shells_section', N("Sound") => 'sound_section', N("System") => 'system_section', join('|', N("System"), N("Base")) => 'system_section', join('|', N("System"), N("Configuration")) => 'configuration_section', join('|', N("System"), N("Configuration"), N("Boot and Init")) => 'boot_init_section', join('|', N("System"), N("Configuration"), N("Hardware")) => 'hardware_configuration_section', join('|', N("System"), N("Configuration"), N("Networking")) => 'networking_configuration_section', join('|', N("System"), N("Configuration"), N("Other")) => 'system_other_section', join('|', N("System"), N("Configuration"), N("Packaging")) => 'packaging_section', join('|', N("System"), N("Configuration"), N("Printing")) => 'printing_section', join('|', N("System"), N("Deploiement")) => '', join('|', N("System"), N("Deployment")) => '', join('|', N("System"), N("Fonts")) => 'chinese_section', join('|', N("System"), N("Fonts"), N("Console")) => 'interpreters_section', join('|', N("System"), N("Fonts"), N("True type")) => '', join('|', N("System"), N("Fonts"), N("Type1")) => '', join('|', N("System"), N("Fonts"), N("X11 bitmap")) => '', join('|', N("System"), N("Internationalization")) => 'chinese_section', join('|', N("System"), N("Kernel and hardware")) => 'hardware_configuration_section', join('|', N("System"), N("Libraries")) => '', join('|', N("System"), N("Servers")) => '', join('|', N("System"), #-PO: This is a package/product name. Only translate it if needed: N("X11")) => '', N("Terminals") => 'terminals_section', N("Text tools") => 'text_tools_section', N("Toys") => 'toys_section', N("Video") => 'video_section', # for Mandriva Choice: N("Workstation") => 'office_section', join('|', N("Workstation"), N("Configuration")) => 'configuration_section', join('|', N("Workstation"), N("Console Tools")) => 'interpreters_section', join('|', N("Workstation"), N("Documentation")) => 'documentation_section', join('|', N("Workstation"), N("Game station")) => 'amusement_section', join('|', N("Workstation"), N("Internet station")) => 'networking_section', join('|', N("Workstation"), N("Multimedia station")) => 'multimedia_section', join('|', N("Workstation"), N("Network Computer (client)")) => 'other_networking', join('|', N("Workstation"), N("Office Workstation")) => 'office_section', join('|', N("Workstation"), N("Scientific Workstation")) => 'sciences_section', N("Graphical Environment") => 'office_section', join('|', N("Graphical Environment"), N("GNOME Workstation")) => 'gnome_section', join('|', N("Graphical Environment"), N("IceWm Desktop")) => 'icewm', join('|', N("Graphical Environment"), N("KDE Workstation")) => 'kde_section', join('|', N("Graphical Environment"), N("Other Graphical Desktops")) => 'more_applications_other_section', N("Development") => 'development_section', join('|', N("Development"), N("Development")) => 'development_section', join('|', N("Development"), N("Documentation")) => 'documentation_section', N("Server") => 'archiving_section', join('|', N("Server"), N("DNS/NIS")) => 'networking_section', join('|', N("Server"), N("Database")) => 'databases_section', join('|', N("Server"), N("Firewall/Router")) => 'networking_section', join('|', N("Server"), N("Mail")) => 'mail_section', join('|', N("Server"), N("Mail/Groupware/News")) => 'mail_section', join('|', N("Server"), N("Network Computer server")) => 'networking_section', join('|', N("Server"), N("Web/FTP")) => 'networking_www_section', ); sub ctreefy { join('|', map { translate($_) } split m|/|, $_[0]); } sub rpm_summary { my ($summary) = @_; utf8::decode($summary); $summary; } sub rpm_description { my ($description) = @_; utf8::decode($description); my ($t, $tmp); foreach (split "\n", $description) { s/^\s*//; if (/^$/ || /^\s*(-|\*|\+|o)\s/) { $t || $tmp and $t .= "$tmp\n"; $tmp = $_; } else { $tmp = ($tmp ? "$tmp " : ($t && "\n") . $tmp) . $_; } } "$t$tmp\n"; } sub split_fullname { $_[0] =~ /^(.*)-([^-]+-[^-]+)$/ } sub my_fullname { return '?-?-?' unless ref $_[0]; my ($name, $version, $release) = $_[0]->fullname; "$name-$version-$release"; } sub urpm_name { return '?-?-?.?' unless ref $_[0]; my ($name, $version, $release, $arch) = $_[0]->fullname; "$name-$version-$release.$arch"; } sub parse_compssUsers_flat() { my (%compssUsers, $category); my $compss = '/var/lib/urpmi/compssUsers.flat'; -r $compss or $compss = '/usr/share/rpmdrake/compssUsers.flat.default'; -r $compss or do { print STDERR "No compssUsers.flat file found\n"; return undef; }; foreach (cat_($compss)) { s/#.*//; /^\s*$/ and next; if (/^\S/) { if (/^(.+?) \[icon=.+?\] \[path=(.+?)\]/) { $category = translate($2) . '|' . translate($1); } else { print STDERR "Malformed category in compssUsers.flat: <$_>\n"; } } elsif (/^\t(\d) (\S+)\s*$/) { $category or print STDERR "Entry without category <$_>\n"; push @{$compssUsers{$2}}, $category . ($1 <= 3 ? '|' . N("Other") : ''); } } \%compssUsers; } sub pkg2medium { my ($p, $urpm) = @_; foreach (@{$urpm->{media}}) { !$_->{ignore} && $p->id <= $_->{end} and return $_; } undef; } #- strftime returns a string in the locale charset encoding; #- but gtk2 requires UTF-8, so we use to_utf8() to ensure the #- output of localtime2changelog() is always in UTF-8 #- as to_utf8() uses LC_CTYPE for locale encoding and strftime() uses LC_TIME, #- it doesn't work if those two variables have values with different #- encodings; but if a user has a so broken setup we can't do much anyway sub localtime2changelog { to_utf8(strftime("%c", localtime($_[0]))) } sub run_rpm { foreach (qw(LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION LC_ALL)) { local $ENV{$_} = $ENV{$_} . '.UTF-8' if !/UTF-8/; } my @l = map { to_utf8($_) } `@_`; wantarray() ? @l : join('', @l); } sub extract_header { my ($pkg, $urpm) = @_; my $chg_prepro = sub { #- preprocess changelog for faster TextView insert reaction [ map { [ "$_\n", if_(/^\*/, { 'weight' => Gtk2::Pango->PANGO_WEIGHT_BOLD }) ] } split("\n", $_[0]) ]; }; my $name = urpm_name($pkg->{pkg}); if ($pkg->{pkg}->flag_installed && !$pkg->{pkg}->flag_upgrade) { add2hash($pkg, { files => [ split /\n/, chomp_(scalar(run_rpm("rpm -ql $name"))) || N("(none)") ], changelog => $chg_prepro->(scalar(run_rpm("rpm -q --changelog $name"))) }); } else { my ($p, $medium) = ($pkg->{pkg}, pkg2medium($pkg->{pkg}, $urpm)); my $hdlist = $medium->{virtual} ? "$medium->{url}/$medium->{with_hdlist}" : "$urpm->{statedir}/$medium->{hdlist}"; $hdlist =~ s!^file:/+!!; if (-r $hdlist) { my $packer; require MDV::Packdrakeng; eval { $packer = MDV::Packdrakeng->open(archive => $hdlist, quiet => 1) } or do { warn "Warning, hdlist seems corrupted ($@)\n"; goto header_non_available; }; my ($headersdir, $retries); while (!-d $headersdir && $retries < 5) { $headersdir = chomp_(`mktemp -d /tmp/rpmdrake.XXXXXXXX`); $retries++; -d $headersdir or warn qq(Could not create temporary directory "$headersdir"); } -d $headersdir or do { warn "Warning, could not extract header!"; goto header_non_available; }; $packer->extract($headersdir, $p->header_filename); $p->update_header("$headersdir/" . $p->header_filename) or do { warn "Warning, could not extract header!"; goto header_non_available; }; rm_rf($headersdir); add2hash($pkg, { summary => rpm_summary($p->summary), description => rpm_description($p->description) }); add2hash($pkg, { files => scalar($p->files) ? [ $p->files ] : [ N("(none)") ], changelog => $chg_prepro->(join("\n", mapn { "* " . localtime2changelog($_[2]) . " $_[0]\n\n$_[1]\n" } [ $p->changelog_name ], [ $p->changelog_text ], [ $p->changelog_time ])) }); $p->pack_header; } else { header_non_available: add2hash($pkg, { summary => $p->summary || N("(Not available)"), description => undef }); } } } sub open_db { my ($o_force) = @_; my $host; log::explanations("opening the RPM database"); if ($options{parallel} && ((undef, $host) = @{$options{parallel}})) { my $done if 0; my $dblocation = "/var/cache/urpmi/distantdb/$host"; if (!$done || $o_force) { print "syncing db from $host to $dblocation..."; mkdir_p "$dblocation/var/lib/rpm"; system "rsync -Sauz -e ssh $host:/var/lib/rpm/ $dblocation/var/lib/rpm"; $? == 0 or die "Couldn't sync db from $host to $dblocation"; $done = 1; print "done.\n"; } URPM::DB::open($dblocation) or die "Couldn't open RPM DB"; } else { URPM::DB::open or die "Couldn't open RPM DB"; } } my $db = open_db(); sub do_search($$$$$$$) { my ($find_entry, $tree, $tree_model, $options, $current_search_type, $urpm, $pkgs) = @_; my $entry = $find_entry->get_text or return; my $entry_rx = eval { qr/$entry/i } or return; my ($results_ok, $results_none) = (N("Search results"), N("Search results (none)")); $options->{delete_category}->($_) foreach $results_ok, $results_none; $options->{state}{flat} and $options->{delete_all}->(); $tree->collapse_all; my @search_results; if ($current_search_type ne 'normal') { if ($MODE eq 'remove') { if ($current_search_type eq 'descriptions') { @search_results = grep { ($pkgs->{$_}{summary} . $pkgs->{$_}{description}) =~ $entry_rx } keys %$pkgs; } else { slow_func_statusbar( N("Please wait, searching..."), $w->{real_window}, sub { $db->traverse(sub { push @search_results, map { if_($_ =~ $entry_rx, urpm_name($_[0])) } $_[0]->files; }); @search_results = grep { exists $pkgs->{$_} } uniq(@search_results); }, ); } } else { my @hdlists = map { my $h = $_->{virtual} ? "$_->{url}/$_->{with_hdlist}" : "$urpm->{statedir}/$_->{hdlist}"; $h =~ s!^file:/+!!; if_(!$_->{ignore} && ($MODE ne 'update' || $_->{update}) && -r $h, $h); } @{$urpm->{media}}; my $total_size = sum( map { my $pack; eval { require MDV::Packdrakeng; $pack = MDV::Packdrakeng->open(archive => $_, quiet => 1) } ? $pack->{toc_f_count} : 0; } @hdlists ); my $searchstop; my $searchw = ugtk2->new(N("Software Management"), grab => 1, transient => $w->{real_window}); gtkadd( $searchw->{window}, gtkpack__( gtknew('VBox', spacing => 5), gtknew('Label', text => N("Please wait, searching...")), my $searchprogress = gtknew('ProgressBar', width => 300), gtkpack__( gtknew('HButtonBox', layout => 'spread'), gtksignal_connect( Gtk2::Button->new(but(N("Stop"))), clicked => sub { $searchstop = 1 }, ), ), ), ); $searchw->sync; open my $sf, 'parsehdlist --fileswinfo --description --summary ' . join(' ', map { "'$_'" } @hdlists) . ' |'; my ($pkg, $progresscount); local $_; while (<$sf>) { $searchstop and last; if (/^NAME<([^>]+)> VERSION<([^>]+)> RELEASE<([^>]+)> ARCH<([^>]+)>/) { $pkg = "$1-$2-$3.$4"; $progresscount++; $progresscount <= $total_size and $searchprogress->set_fraction($progresscount/$total_size); $searchw->flush; next; } $pkg or next; my (undef, $key, $value) = split ':', $_; if ($current_search_type eq 'descriptions') { $key =~ /^summary|description$/ or next; } else { $key eq 'files' or next; } if ($value =~ $entry_rx) { exists $pkgs->{$pkg} and push @search_results, $pkg; $pkg = ''; } } close $sf; @search_results = uniq(@search_results); #- there can be multiple packages with same version/release for different arch's $searchw->destroy; } } else { @search_results = grep { $_ =~ $entry_rx } keys %$pkgs; } if (@search_results) { $options->{add_nodes}->(map { [ $_, N("Search results") . ($options->{tree_mode} eq 'by_presence' ? '|' . ($pkgs->{$_}{pkg}->flag_installed ? N("Upgradable") : N("Addable")) : ($options->{tree_mode} eq 'by_selection' ? '|' . ($pkgs->{$_}{selected} ? N("Selected") : N("Not selected")) : '')) ] } sort { uc($a) cmp uc($b) } @search_results); my $last_iter = $tree_model->iter_nth_child(undef, $tree_model->iter_n_children(undef) - 1); my $path = $tree_model->get_path($last_iter); $tree->expand_row($path, 0); $tree->scroll_to_cell($path, undef, 1, 0.5, 0); } else { $options->{add_nodes}->([ '', $results_none, { nochild => 1 } ]); } } sub find_installed_version { my ($p) = @_; my @version; $db->traverse_tag('name', [ $p->name ], sub { push @version, $_[0]->version . '-' . $_[0]->release }); @version ? join(',', sort @version) : N("(none)"); } sub formatlistpkg { join("\n", sort { uc($a) cmp uc($b) } @_) } sub format_header { my ($str) = @_; '' . escape_text_for_TextView_markup_format($str) . ''; } sub format_field { my ($str) = @_; '' . escape_text_for_TextView_markup_format($str) . ''; } package Gtk2::Mdv::TextView; sub new { my ($_class) = @_; my $w = mygtk2::gtknew('TextView'); my $time if 0; require Time::HiRes; $w->signal_connect(size_allocate => sub { my ($w, $requisition) = @_; return if !ref($w->{anchors}); return if Time::HiRes::clock_gettime() - $time < 0.200; $time = Time::HiRes::clock_gettime(); foreach my $anchor (@{$w->{anchors}}) { $_->set_size_request($requisition->width-30, -1) foreach $anchor->get_widgets; } 1; }); $w; } 1; package main; sub format_pkg_simplifiedinfo { my ($pkgs, $key, $urpm, $descriptions) = @_; my ($name, $_version) = split_fullname($key); my $medium = pkg2medium($pkgs->{$key}{pkg}, $urpm)->{name}; my $update_descr = $pkgs->{$key}{pkg}->flag_upgrade && $descriptions->{$name}{pre} && $descriptions->{$name}{medium} eq $medium; my $s = ugtk2::markup_to_TextView_format(join("\n", format_header($name . ' - ' . $pkgs->{$key}{summary}) . # workaround gtk+ bug where GtkTextView wronly limit embedded widget size to bigger line's width (#25533): "\x{200b} \x{feff}" . ' ' x 120, if_($update_descr, # is it an update? format_field(N("Importance: ")) . escape_text_for_TextView_markup_format($descriptions->{$name}{importance}), format_field(N("Reason for update: ")) . escape_text_for_TextView_markup_format(rpm_description($descriptions->{$name}{pre})), ), '')); # extra empty line if ($update_descr) { push @$s, [ my $link = gtkshow(Gtk2::LinkButton->new($descriptions->{$name}{URL}, N("Security advisory"))) ]; $link->set_uri_hook(sub { my (undef, $url) = @_; run_program::raw({ detach => 1 }, 'www-browser', $url); }); } push @$s, @{ ugtk2::markup_to_TextView_format(join("\n", (escape_text_for_TextView_markup_format($pkgs->{$key}{description} || $descriptions->{$name}{description}) || '' . N("No description") . '') )) }; push @$s, [ "\n" ]; push @$s, [ gtkadd(gtkshow(my $exp = Gtk2::Expander->new(format_field(N("Files:")))), gtknew('TextView', text => exists $pkgs->{$key}{files} ? ugtk2::markup_to_TextView_format('' . join("\n", map { "\x{200e}$_" } @{$pkgs->{$key}{files}}) . '') #- to highlight information : N("(Not available)"), )) ]; $exp->set_use_markup(1); push @$s, [ "\n\n" ]; push @$s, [ gtkadd(gtkshow(my $exp2 = Gtk2::Expander->new(format_field(N("Changelog:")))), gtknew('TextView', text => $pkgs->{$key}{changelog} || N("(Not available)")) ) ]; $exp2->set_use_markup(1); $s; } sub format_pkg_info { my ($pkgs, $key, $urpm, $descriptions) = @_; my ($name, $version) = split_fullname($key); my @files = ( format_field(N("Files:\n")), exists $pkgs->{$key}{files} ? '' . join("\n", map { "\x{200e}$_" } @{$pkgs->{$key}{files}}) . '' #- to highlight information : N("(Not available)"), ); my @chglo = (format_field(N("Changelog:\n")), ($pkgs->{$key}{changelog} ? @{$pkgs->{$key}{changelog}} : N("(Not available)"))); my @source_info = ( $MODE eq 'remove' || !@$max_info_in_descr ? () : ( format_field(N("Medium: ")) . pkg2medium($pkgs->{$key}{pkg}, $urpm)->{name}, format_field(N("Currently installed version: ")) . find_installed_version($pkgs->{$key}{pkg}), ) ); my @max_info = @$max_info_in_descr && $changelog_first ? (@chglo, @files) : (@files, '', @chglo); ugtk2::markup_to_TextView_format(join("\n", format_field(N("Name: ")) . $name, format_field(N("Version: ")) . $version, format_field(N("Architecture: ")) . $pkgs->{$key}{pkg}->arch, format_field(N("Size: ")) . N("%s KB", int($pkgs->{$key}{pkg}->size/1024)), if_( $MODE eq 'update', format_field(N("Importance: ")) . $descriptions->{$name}{importance} ), @source_info, '', # extra empty line format_field(N("Summary: ")) . $pkgs->{$key}{summary}, '', # extra empty line if_( $MODE eq 'update', format_field(N("Reason for update: ")) . rpm_description($descriptions->{$name}{pre}), ), format_field(N("Description: ")), ($pkgs->{$key}{description} || $descriptions->{$name}{description} || N("No description")), @max_info, )); } sub run_treeview_dialog { my ($callback_action) = @_; my ($urpm, $pkgs, $descriptions); my (%filter_methods, $force_displaying_group, @initial_selection, $initial_selection_done); my $switch_pkg_list_mode = sub { my ($mode) = @_; return if !$mode; return if !$filter_methods{$mode}; $force_displaying_group = 1; $filter_methods{$mode}->(); }; my $pkgs_provider = sub { my ($options, $mode) = @_; return if !$mode; my $h = &get_pkgs($urpm, $options); # was given (1, @_) for updates ($urpm, $descriptions) = @$h{qw(urpm update_descr)}; %filter_methods = ( all => sub { $pkgs = { map { %{$h->{$_}} } qw(installed installable updates) } }, installed => sub { $pkgs = $h->{installed} }, non_installed => sub { $pkgs = $h->{installable} }, #mandrake_choices => sub { $pkgs = }, all_updates => sub { my %pkgs = grep { my $p = $h->{installable}{$_}; $p->{pkg} && !$p->{selected} && $p->{pkg}->flag_installed && $p->{pkg}->flag_upgrade } keys %{$h->{installable}}; $pkgs = { (map { $_ => $h->{updates}{$_} } keys %{$h->{updates}}), (map { $_ => $h->{installable}{$_} } keys %pkgs) }; }, #security => sub { $pkgs = }, #normal => sub { $pkgs = } ); if (!$initial_selection_done) { $filter_methods{all}->(); @initial_selection = grep { $pkgs->{$_}{selected} } keys %$pkgs; $initial_selection_done = 1; } foreach my $importance (qw(bugfix security normal)) { $filter_methods{$importance} = sub { $pkgs = $h->{updates}; $pkgs = { map { $_ => $pkgs->{$_} } grep { my ($name, $_version) = split_fullname($_); $descriptions->{$name}{importance} eq $importance } keys %$pkgs }; }; } $filter_methods{mandrake_choices} = $filter_methods{non_installed}; $switch_pkg_list_mode->($mode); }; my ($options, $size_selected, $compssUsers, $tree, $tree_model, $detail_list, $detail_list_model, %elems); my (undef, $size_free) = MDK::Common::System::df('/usr'); $w = ugtk2->new(N("Software Management")); $::main_window = $w->{real_window}; my $is_locale_available = sub { any { $urpm->{depslist}[$_]->flag_selected } keys %{$urpm->{provides}{$_[0]} || {}} and return 1; my $found; print "\n\nBUG: $_\"$_\"\n"; $db->traverse_tag('name', [ $_ ], sub { $found ||= 1 }); return $found; }; my $callback_choices = sub { my (undef, undef, undef, $choices) = @_; foreach my $pkg (@$choices) { foreach ($pkg->requires_nosense) { /locales-/ or next; $is_locale_available->($_) and return $pkg; } } my $callback = sub { interactive_msg_(N("More information on package..."), $options->{get_info}->($_[0]), scroll => 1) }; $choices = [ sort { $a->name cmp $b->name } @$choices ]; my @choices = interactive_list_(N("Please choose"), N("One of the following packages is needed:"), [ map { urpm_name($_) } @$choices ], $callback); $choices->[$choices[0]]; }; my $closure_removal = sub { $urpm->{state} = {}; urpm::select::find_packages_to_remove($urpm, $urpm->{state}, \@_); }; my $force_rebuild; $options = { is_a_package => sub { my ($pkg) = @_; return exists $pkgs->{$pkg}; }, get_icon => sub { my ($group, $parent) = @_; my $pixbuf; my $path = $group =~ /\|/ ? '/usr/share/icons/mini/' : '/usr/share/icons/'; my $create_pixbuf = sub { gtknew('Pixbuf', file => join('', $path, $_[0], '.png')) }; eval { $pixbuf = $create_pixbuf->($group_icons{$group}) }; eval { $pixbuf ||= $create_pixbuf->($group_icons{$parent}) } if $parent; $pixbuf ||= $create_pixbuf->('applications_section'); }, node_state => sub { my $pkg = $pkgs->{$_[0]}; my $urpm_obj = $pkg->{pkg}; $_[0] ? $pkg->{selected} ? ($urpm_obj->flag_installed ? ($urpm_obj->flag_upgrade ? 'to_install' : 'to_remove') : 'to_install') : ($urpm_obj->flag_installed ? ($urpm_obj->flag_upgrade ? 'to_update' : 'installed') : ($urpm_obj->flag_base ? '/usr/share/rpmdrake/icons/base.png' : 'uninstalled')) : 'XXX'; }, #- checks $_[0] -> hack for partial tree displaying build_tree => sub { my ($add_node, $flat, $mode) = @_; my $old_mode if 0; $mode = $options->{rmodes}{$mode} || $mode; return if $old_mode eq $mode && !$force_rebuild; $old_mode = $mode; undef $force_rebuild; my @elems; my $wait; $wait = statusbar_msg(N("Please wait, listing packages...")) if $MODE ne 'update'; gtkflush(); if ($mode eq 'mandrake_choices') { foreach my $pkg (keys %$pkgs) { my ($name) = split_fullname($pkg); push @elems, [ $pkg, $_ ] foreach @{$compssUsers->{$name}}; } } else { my @keys = keys %$pkgs; if (member($mode, qw(all_updates security bugfix normal))) { @keys = grep { my ($name) = split_fullname($_); member($descriptions->{$name}{importance}, @$mandrakeupdate_wanted_categories) || ! $descriptions->{$name}{importance}; } @keys; if (@keys == 0) { $add_node->('', N("(none)"), { nochild => 1 }); my $explanation_only_once if 0; $explanation_only_once or interactive_msg_(N("No update"), N("The list of updates is empty. This means that either there is no available update for the packages installed on your computer, or you already installed all of them.")); $explanation_only_once = 1; } } @elems = map { [ $_, !$flat && ctreefy($pkgs->{$_}{pkg}->group) ] } @keys; } my %sortmethods = ( by_size => sub { sort { $pkgs->{$b->[0]}{pkg}->size <=> $pkgs->{$a->[0]}{pkg}->size } @_ }, by_selection => sub { sort { $pkgs->{$b->[0]}{selected} <=> $pkgs->{$a->[0]}{selected} || uc($a->[0]) cmp uc($b->[0]) } @_ }, by_leaves => sub { my $pkgs_times = 'rpm -q --qf "%{name}-%{version}-%{release} %{installtime}\n" `urpmi_rpm-find-leaves`'; sort { $b->[1] <=> $a->[1] } grep { exists $pkgs->{$_->[0]} } map { [ split ] } run_rpm($pkgs_times); }, flat => sub { no locale; sort { uc($a->[0]) cmp uc($b->[0]) } @_ }, by_medium => sub { sort { $a->[2] <=> $b->[2] || uc($a->[0]) cmp uc($b->[0]) } @_ }, ); if ($flat) { $add_node->($_->[0], '') foreach $sortmethods{$mode || 'flat'}->(@elems); } else { if (0 && $MODE eq 'update') { $add_node->($_->[0], N("All")) foreach $sortmethods{flat}->(@elems); $tree->expand_row($tree_model->get_path($tree_model->get_iter_first), 0); } elsif ($mode eq 'by_source') { $add_node->($_->[0], $_->[1]) foreach $sortmethods{by_medium}->(map { my $m = pkg2medium($pkgs->{$_->[0]}{pkg}, $urpm); [ $_->[0], $m->{name}, $m->{priority} ]; } @elems); } elsif ($mode eq 'by_presence') { $add_node->( $_->[0], $pkgs->{$_->[0]}{pkg}->flag_installed && !$pkgs->{$_->[0]}{pkg}->flag_skip ? N("Upgradable") : N("Addable") ) foreach $sortmethods{flat}->(@elems); } else { #- we populate all the groups tree at first %elems = (); # better loop on packages, create groups tree and push packages in the proper place: foreach my $pkg (@elems) { my $grp = $pkg->[1]; $options->{add_parent}->($grp); $elems{$grp} ||= []; push @{$elems{$grp}}, $pkg; } } } statusbar_msg_remove($wait) if defined $wait; }, grep_unselected => sub { grep { exists $pkgs->{$_} && !$pkgs->{$_}{selected} } @_ }, partialsel_unsel => sub { my ($unsel, $sel) = @_; @$sel = grep { exists $pkgs->{$_} } @$sel; @$unsel < @$sel; }, toggle_nodes => sub { my ($set_state, $old_state, @nodes) = @_; @nodes = grep { exists $pkgs->{$_} } @nodes or return; #- avoid selecting too many packages at once return if !$dont_show_selections && @nodes > 2000; my $new_state = !$pkgs->{$nodes[0]}{selected}; my @nodes_with_deps; my $deps_msg = sub { return 1 if $dont_show_selections; my ($title, $msg, $nodes, $nodes_with_deps) = @_; my @deps = sort { $a cmp $b } difference2($nodes_with_deps, $nodes); @deps > 0 or return 1; deps_msg_again: my $results = interactive_msg_( $title, $msg . urpm::select::translate_why_removed($urpm, $urpm->{state}, @deps), yesno => [ N("Cancel"), N("More info"), N("Ok") ], scroll => 1, ); if ($results eq #-PO: Keep it short, this is gonna be on a button N("More info")) { interactive_packtable( N("Information on packages"), $w->{real_window}, undef, [ map { my $pkg = $_; [ gtknew('HBox', children_tight => [ gtkset_selectable(gtknew('Label', text => $pkg), 1) ]), gtknew('Button', text => N("More information on package..."), clicked => sub { interactive_msg_(N("More information on package..."), $options->{get_info}->($pkg), scroll => 1); }) ] } @deps ], [ gtknew('Button', text => N("Ok"), clicked => sub { Gtk2->main_quit }) ] ); goto deps_msg_again; } else { return $results eq N("Ok"); } }; # deps_msg if (member($old_state, qw(to_remove installed))) { # remove pacckages if ($new_state) { my @remove; slow_func($tree->window, sub { @remove = $closure_removal->(@nodes) }); @nodes_with_deps = grep { !$pkgs->{$_}{selected} && !/^basesystem/ } @remove; $deps_msg->(N("Some additional packages need to be removed"), formatAlaTeX(N("Because of their dependencies, the following package(s) also need to be\nremoved:")) . "\n\n", \@nodes, \@nodes_with_deps) or @nodes_with_deps = (); my @impossible_to_remove; foreach (grep { exists $pkgs->{$_}{base} } @remove) { ${$pkgs->{$_}{base}} == 1 ? push @impossible_to_remove, $_ : ${$pkgs->{$_}{base}}--; } @impossible_to_remove and interactive_msg_(N("Some packages can't be removed"), N("Removing these packages would break your system, sorry:\n\n") . formatlistpkg(@impossible_to_remove)); @nodes_with_deps = difference2(\@nodes_with_deps, \@impossible_to_remove); } else { slow_func($tree->window, sub { @nodes_with_deps = grep { intersection(\@nodes, [ $closure_removal->($_) ]) } grep { $pkgs->{$_}{selected} && !member($_, @nodes) } keys %$pkgs }); push @nodes_with_deps, @nodes; $deps_msg->(N("Some packages can't be removed"), N("Because of their dependencies, the following package(s) must be\nunselected now:\n\n"), \@nodes, \@nodes_with_deps) or @nodes_with_deps = (); $pkgs->{$_}{base} && ${$pkgs->{$_}{base}}++ foreach @nodes_with_deps; } } else { if ($new_state) { if (@nodes > 1) { #- unselect i18n packages of which locales is not already present (happens when user clicks on KDE group) my @bad_i18n_pkgs; foreach my $sel (@nodes) { foreach ($pkgs->{$sel}{pkg}->requires_nosense) { /locales-([^-]+)/ or next; $sel =~ /-$1[-_]/ && !$is_locale_available->($_) and push @bad_i18n_pkgs, $sel; } } @nodes = difference2(\@nodes, \@bad_i18n_pkgs); } my @requested; slow_func( $tree->window, sub { @requested = $urpm->resolve_requested( $db, $urpm->{state}, { map { $pkgs->{$_}{pkg}->id => 1 } @nodes }, callback_choices => $callback_choices, ); }, ); @nodes_with_deps = map { urpm_name($_) } @requested; if (!$deps_msg->(N("Additional packages needed"), N("To satisfy dependencies, the following package(s) also need\nto be installed:\n\n"), \@nodes, \@nodes_with_deps)) { @nodes_with_deps = (); $urpm->disable_selected($db, $urpm->{state}, @requested); goto packages_selection_ok; } if (my @cant = sort(difference2(\@nodes, \@nodes_with_deps))) { my @ask_unselect = urpm::select::unselected_packages($urpm, $urpm->{state}); my @reasons = map { my $cant = $_; my $unsel = find { $_ eq $cant } @ask_unselect; $unsel ? join("\n", urpm::select::translate_why_unselected($urpm, $urpm->{state}, $unsel)) : ($pkgs->{$_}{pkg}->flag_skip ? N("%s (belongs to the skip list)", $cant) : $cant); } @cant; interactive_msg_( N("Some packages can't be installed"), N("Sorry, the following package(s) can't be selected:\n\n%s", join("\n", @reasons)), scroll => 1, ); foreach (@cant) { $pkgs->{$_}{pkg}->set_flag_requested(0); $pkgs->{$_}{pkg}->set_flag_required(0); } } packages_selection_ok: } else { my @unrequested; slow_func($tree->window, sub { @unrequested = $urpm->disable_selected($db, $urpm->{state}, map { $pkgs->{$_}{pkg} } @nodes) }); @nodes_with_deps = map { urpm_name($_) } @unrequested; if (!$deps_msg->(N("Some packages need to be removed"), N("Because of their dependencies, the following package(s) must be\nunselected now:\n\n"), \@nodes, \@nodes_with_deps)) { @nodes_with_deps = (); $urpm->resolve_requested($db, $urpm->{state}, { map { $_->id => 1 } @unrequested }); goto packages_unselection_ok; } packages_unselection_ok: } } foreach (@nodes_with_deps) { #- some deps may exist on some packages which aren't listed because #- not upgradable (older than what currently installed) exists $pkgs->{$_} or next; if (!$pkgs->{$_}{pkg}) { #- can't be removed # FIXME; what about next packages in the loop? $pkgs->{$_}{selected} = 0; log::explanations("can't be removed: $_"); } else { $pkgs->{$_}{selected} = $new_state; } $set_state->($_, $options->{node_state}($_), $detail_list_model); $pkgs->{$_}{pkg} and $size_selected += $pkgs->{$_}{pkg}->size * ($new_state ? 1 : -1); } }, #- toggle_nodes get_status => sub { member($default_list_mode, qw(all non_installed)) ? N("Selected: %s / Free disk space: %s", formatXiB($size_selected), formatXiB($size_free*1024)) : N("Selected size: %d MB", $size_selected/(1024*1024)); }, get_info => sub { my ($key) = @_; #- the package information hasn't been loaded. Instead of rescanning the media, just give up. exists $pkgs->{$key} or return [ [ N("Description not available for this package\n") ] ]; exists $pkgs->{$key}{description} && exists $pkgs->{$key}{files} or slow_func($tree->window, sub { extract_header($pkgs->{$key}, $urpm) }); my $s; eval { $s = format_pkg_simplifiedinfo($pkgs, $key, $urpm, $descriptions) }; if (my $err = $@) { $s = N("A fatal error occurred: %s.", $err); } $s; }, check_interactive_to_toggle => sub { 1 }, grep_allowed_to_toggle => sub { @_ }, rebuild_tree => sub {}, }; $tree_model = Gtk2::TreeStore->new("Glib::String", "Glib::String", "Gtk2::Gdk::Pixbuf"); $tree_model->set_sort_column_id(0, 'ascending'); $tree = Gtk2::TreeView->new_with_model($tree_model); $tree->get_selection->set_mode('browse'); $tree->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::MDV::CellRendererPixWithLabel->new, 'pixbuf' => 2, label => 0)); $tree->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 1)); $tree->set_headers_visible(0); $detail_list_model = Gtk2::ListStore->new("Glib::String", "Gtk2::Gdk::Pixbuf", "Glib::String"); $detail_list = Gtk2::TreeView->new_with_model($detail_list_model); $detail_list->append_column(my $pixcolumn = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererPixbuf->new, 'pixbuf' => 1)); $pixcolumn->{is_pix} = 1; $detail_list->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0)); $detail_list_model->set_sort_column_id(0, 'ascending'); $detail_list->set_headers_visible(0); $detail_list->set_rules_hint(1); ($typical_width) = string_size($tree, translate("Graphical Environment") . "xmms-more-vis-plugins"); $typical_width > 600 and $typical_width = 600; #- try to not being crazy with a too large value $typical_width < 150 and $typical_width = 150; #- XXX : work around bug in gtk2 that clobbers display (see bugzilla #16575) #$textcolumn->set_min_width($typical_width*0.7); my $cursor_to_restore; $_->signal_connect( expose_event => sub { $cursor_to_restore or return; gtkset_mousecursor_normal($tree->window); undef $cursor_to_restore; }, ) foreach $tree, $detail_list; $tree->get_selection->signal_connect(changed => sub { my ($model, $iter) = $_[0]->get_selected; return if !$iter; my $current_group if 0; my $new_group = $model->get($iter, 0); return if $current_group eq $new_group && !$force_displaying_group; undef $force_displaying_group; $current_group = $new_group; $model && $iter or return; my $group = $model->get($iter, 0); my $parent = $iter; while ($parent = $model->iter_parent($parent)) { $group = join('|', $model->get($parent, 0), $group); } slow_func($tree->window, sub { $options->{add_nodes}->(@{$elems{$group}}) }); }); $options->{state}{splited} = 1; #$options->{state}{flat} = 1; $compssUsers = parse_compssUsers_flat(); my %modes = (all => N("All"), installed => N("Installed"), non_installed => N("Non installed"), mandrake_choices => $rpmdrake::branded ? N("%s choices", $rpmdrake::distrib{system}) : N("Mandriva Linux choices"), if_(0, # let's keep the translated strings (to be resurected as sorting the treeview): N("All packages, alphabetical"), by_presence => N("All packages, by update availability"), by_size => N("All packages, by size"), by_selection => N("All packages, by selection state"), by_leaves => N("Leaves only, sorted by install date"), by_group => N("All packages, by group"), ), all_updates => N("All updates"), security => N("Security updates"), bugfix => N("Bugfixes updates"), normal => N("Normal updates") ); my %rmodes = reverse %modes; $options->{rmodes} = \%rmodes; my %default_mode = (install => 'all', # we want the new GUI by default instead of "non_installed" remove => 'installed', update => 'security', ); my %wanted_categories = ( all_updates => [ qw(security bugfix normal) ], security => [ 'security' ], bugfix => [ 'bugfix' ], normal => [ 'normal' ], ); my $reset_search; my $old_value; my $cbox = gtksignal_connect(Gtk2::ComboBox->new_with_strings([ @modes{ 'all', if_($compssUsers, 'mandrake_choices'), qw(installed non_installed all_updates security bugfix normal) } ], $modes{$default_mode{$MODE} || 'all'}), changed => sub { my $val = $_[0]->get_text; return if $val eq $old_value; # workarounding gtk+ sending us sometimes twice events $old_value = $val; $default_list_mode = $rmodes{$val}; if (my @cat = $wanted_categories{$rmodes{$val}} && @{$wanted_categories{$rmodes{$val}}}) { @$mandrakeupdate_wanted_categories = @cat; } if (0) { $reset_search->(); $options->{rebuild_tree}->(); } $options->{state}{flat} = 0; # was 1 only for the "All packages, alphabetical", "All packages, by size", "All packages, by selection state", and "Leaves only, sorted by install date" if ($options->{tree_mode} ne $val) { ($options->{tree_mode}) = $val =~ /^by/ ? $options->{tree_submode} : $rmodes{$val}; $tree_mode->[0] = $options->{tree_mode}; $tree_flat->[0] = $options->{state}{flat}; $reset_search->(); slow_func($::main_window->window, sub { $switch_pkg_list_mode->($rmodes{$val}) }); $options->{rebuild_tree}->(); } } ); my $default_radio = $options->{tree_mode} = $default_list_mode; $cbox->set_text($modes{$default_radio}); my $radio_by; $options->{tree_submode} ||= $default_radio; $options->{tree_subflat} ||= $options->{state}{flat}; my @search_types = qw(normal descriptions files); my $current_search_type = $search_types[0]; my $search_types_optionmenu = Gtk2::ComboBox->new; { $search_types_optionmenu->set_model(Gtk2::ListStore->new('Glib::String')); my $search_types_renderer = Gtk2::CellRendererText->new; $search_types_optionmenu->pack_start($search_types_renderer, 0); $search_types_optionmenu->set_attributes($search_types_renderer, text => 0); my $iter = $search_types_optionmenu->get_model->iter_nth_child(undef, 0); $iter = $search_types_optionmenu->get_model->insert(0); $search_types_optionmenu->get_model->set($iter, 0, N("in names")); $iter = $search_types_optionmenu->get_model->insert(1); $search_types_optionmenu->get_model->set($iter, 0, N("in descriptions")); $iter = $search_types_optionmenu->get_model->insert(2); $search_types_optionmenu->get_model->set($iter, 0, N("in file names")); $search_types_optionmenu->set_active(0); $search_types_optionmenu->signal_connect( changed => sub { $current_search_type = $search_types[$search_types_optionmenu->get_active]; }, ); } my $info = Gtk2::Mdv::TextView->new; $info->set_left_margin(2); $info->set_right_margin(15); #- workaround when right elevator of scrolled window appears my $find_entry; my $clear_button; my $find_callback = sub { $clear_button and $clear_button->set_sensitive(1); do_search($find_entry, $tree, $tree_model, $options, $current_search_type, $urpm, $pkgs); }; $reset_search = sub { $clear_button and $clear_button->set_sensitive(0); $find_entry and $find_entry->set_text(""); }; my $do_action = sub { require urpm::sys; if (!urpm::sys::check_fs_writable()) { $urpm->{fatal}(1, N("Error: %s appears to be mounted read-only.", $urpm::sys::mountpoint)); return; } if (!int(grep { $pkgs->{$_}{selected} } keys %$pkgs)) { interactive_msg_(N("You need to select some packages first."), N("You need to select some packages first.")); return; } my $size_added = sum(map { if_($_->flag_selected && !$_->flag_installed, $_->size) } @{$urpm->{depslist}}); if ($MODE eq 'install' && $size_free - $size_added/1024 < 50*1024) { interactive_msg_(N("Too many packages are selected"), N("Warning: it seems that you are attempting to add so much packages that your filesystem may run out of free diskspace, during or after package installation ; this is particularly dangerous and should be considered with care. Do you really want to install all the selected packages?"), yesno => 1) or return; } if (!$callback_action->($urpm, $pkgs)) { $force_rebuild = 1; $pkgs_provider->({ skip_updating_mu => 1 }, $options->{tree_mode}); $reset_search->(); $size_selected = 0; (undef, $size_free) = MDK::Common::System::df('/usr'); $options->{rebuild_tree}->(); gtktext_insert($info, ''); } }; my $hpaned = gtknew('HPaned', child1 => gtknew('ScrolledWindow', child => $tree, width => $typical_width*0.9) , resize1 => 0, shrink1 => 0, resize2 => 1, shrink2 => 0, child2 => gtknew('VPaned', child1 => gtknew('ScrolledWindow', child => $detail_list), resize1 => 1, shrink1 => 0, child2 => gtknew('ScrolledWindow', child => $info), resize2 => 1, shrink2 => 0 ) ); my $status = gtknew('Label'); my $checkbox_show_autoselect; my ($menu, $factory) = create_factory_menu( $w->{real_window}, [ N("/_File"), undef, undef, undef, '' ], if_( ! $>, [ N("/_File") . N("/_Update media"), undef, sub { update_sources_interactive($urpm, transient => $w->{real_window}) and do { $force_rebuild = 1; $pkgs_provider->({ skip_updating_mu => 1 }, $options->{tree_mode}); $reset_search->(); $size_selected = 0; $options->{rebuild_tree}->(); }; }, undef, '' ] ), [ N("/_File") . N("/_Reset the selection"), undef, sub { if ($MODE ne 'remove') { $urpm->disable_selected( $db, $urpm->{state}, map { if_($pkgs->{$_}{selected}, $pkgs->{$_}{pkg}) } keys %$pkgs, ); } $pkgs->{$_}{selected} = 0 foreach keys %$pkgs; $reset_search->(); $size_selected = 0; $force_displaying_group = 1; $tree->get_selection->signal_emit('changed'); }, undef, '' ], [ N("/_File") . N("/Reload the _packages list"), undef, sub { slow_func($::main_window->window, sub { $force_rebuild = 1; $rmodes{$pkgs_provider->({ skip_updating_mu => 1 }, $options->{tree_mode})}; }); $reset_search->(); $size_selected = 0; $options->{rebuild_tree}->(); }, undef, '' ], [ N("/_File") . N("/_Quit"), N("Q"), sub { Gtk2->main_quit }, undef, '', ], #[ N("/_View"), undef, undef, undef, '' ], if_(!$>, [ N("/_Options"), undef, undef, undef, '' ], [ N("/_Options") . N("/_Media Manager"), undef, sub { run_program::raw({ detach => 1 }, 'edit-urpm-sources.pl'); }, undef, '' ], [ N("/_Options") . N("/_Show automatically selected packages"), undef, sub { $dont_show_selections = !$checkbox_show_autoselect->get_active; }, undef, '' ], ), [ N("/_Help"), undef, undef, undef, '' ], [ N("/_Help") . N("/_Report Bug"), undef, sub { run_program::raw({ detach => 1 }, 'drakbug', '--report', 'rpmdrake') }, undef, '' ], [ N("/_Help") . N("/_Help"), undef, sub { rpmdrake::open_help($MODE) }, undef, '' ], [ N("/_Help") . N("/_About..."), undef, sub { my $license = formatAlaTeX(translate($::license)); $license =~ s/\n/\n\n/sg; # nicer formatting my $w = gtknew('AboutDialog', name => N("Rpmdrake"), version => '2007', copyright => N("Copyright (C) %s by Mandriva", '2002-2006'), license => $license, wrap_license => 1, comments => N("Rpmdrake is Mandriva Linux package management tool."), website => 'http://mandrivalinux.com', website_label => N("Mandriva Linux"), 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; }, undef, '' ] ); $checkbox_show_autoselect = $factory->get_widget("
" . strip_first_underscore(N("/_Options"), N("/_Show automatically selected packages"))) and $checkbox_show_autoselect->set_active(!$dont_show_selections); gtkadd( $w->{window}, gtkpack_( gtknew('VBox', spacing => 3), 0, $menu, 0, getbanner(), 1, gtkadd( gtknew('Frame', border_width => 3, shadow_type => 'none'), gtkpack_( gtknew('VBox', spacing => 3), 0, gtkpack__( gtknew('HBox', spacing => 10), $cbox, gtknew('Label', text => N("Find:")), $search_types_optionmenu, gtksignal_connect( $find_entry = gtknew('Entry'), key_press_event => sub { $_[1]->keyval == $Gtk2::Gdk::Keysyms{Return} and $find_callback->(); }, ), gtksignal_connect(Gtk2::Button->new(but(N("Search"))), clicked => $find_callback), gtksignal_connect( $clear_button = Gtk2::Button->new(but(N("Clear"))), clicked => sub { $reset_search->() }, ), ), 1, $hpaned, 0, $status, 0, gtkpack_( gtknew('HBox', spacing => 20), 0, gtksignal_connect( Gtk2::Button->new(but_(N("Help"))), clicked => sub { rpmdrake::open_help($MODE) }, ), 1, gtknew('Label'), 0, gtksignal_connect( Gtk2::Button->new(but_(N("Select all"))), clicked => sub { return if !ref($options->{toggle_all}); $options->{toggle_all}->(1); }, ), 0, my $action_button = gtksignal_connect( Gtk2::Button->new(but_(N("Apply"))), clicked => sub { $do_action->() }, ), 0, gtksignal_connect( Gtk2::Button->new(but_(N("Quit"))), clicked => sub { Gtk2->main_quit }, ), ), ), ), 0, $statusbar = Gtk2::Statusbar->new, ), ); $action_button->set_sensitive(0) if $>; $clear_button->set_sensitive(0); $find_entry->grab_focus; gtktext_insert($info, [ [ $info->render_icon('gtk-dialog-info', 'GTK_ICON_SIZE_DIALOG', undef) ], @{ ugtk2::markup_to_TextView_format( formatAlaTeX(join("\n\n\n", format_header(N("Quick Introduction")), N("You can browse the packages through the categories tree on the left."), N("You can view information about a package by clicking on it on the right list."), N("To install, update or remove a package, just click on its \"checkbox\".")))) } ]); $w->{rwindow}->set_default_size($typical_width*2.7, 500) if !$::isEmbedded; $find_entry->set_text($options{search}[0]) if $options{search}; $w->{rwindow}->show_all; $pkgs_provider->({}, $default_list_mode); # default mode if (@initial_selection) { $options->{initial_selection} = \@initial_selection; $pkgs->{$_}{selected} = 0 foreach @initial_selection; } $options->{widgets} = { w => $w, tree => $tree, tree_model => $tree_model, detail_list_model => $detail_list_model, detail_list => $detail_list, info => $info, status => $status, }; $options->{init_callback} = $find_callback if $options{search}; $treeview_dialog_run = 1; ask_browse_tree_info_given_widgets_for_rpmdrake($options); } # -=-=-=---=-=-=---=-=-=-- install packages -=-=-=---=-=-=---=-=-=- sub get_pkgs { my ($urpm, $opts) = @_; my $update_name = 'update_source'; my %update_descr; my @update_medias; my $error_happened; my $fatal_handler = sub { $error_happened = 1; interactive_msg_(N("Fatal error"), N("A fatal error occurred: %s.", $_[1])); myexit(-1) if 0; #FIXME }; if (!$urpm) { $urpm ||= urpm->new; $urpm->{fatal} = $fatal_handler; my $media = ref $options{media} ? join(',', @{$options{media}}) : ''; urpm::media::configure($urpm, media => $media); if ($error_happened) { touch('/etc/urpmi/urpmi.cfg'); exec('edit-urpm-sources.pl'); } } my $_lock = urpm::lock::urpmi_db($urpm); my $statedir = $urpm->{statedir}; @update_medias = grep { !$_->{ignore} && $_->{update} } @{$urpm->{media}}; $urpm->{fatal} = $fatal_handler; if (member($default_list_mode, qw(all_updates security bugfix normal))) { unless ($options{'no-media-update'}) { if (@update_medias > 0) { if (!$opts->{skip_updating_mu}) { $options{'no-confirmation'} or interactive_msg_with_banner(N("Confirmation"), N("I need to contact the mirror to get latest update packages. Please check that your network is currently running. Is it ok to continue?"), yesno => 1) or myexit(-1); urpm::media::select_media($urpm, map { $_->{name} } @update_medias); update_sources($urpm, noclean => 1, banner => $::isEmbedded); } } else { if (any { $_->{update} } @{$urpm->{media}}) { interactive_msg_(N("Already existing update media"), 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 Enabled? column). Then, restart %s.", $rpmdrake::myname_update)); myexit(-1); } mu_retry_another_mirror: my ($mirror) = choose_mirror(if_(exists $w->{real_window}, transient => $w->{real_window})); my $m = ref($mirror) ? $mirror->{url} : ''; $m or interactive_msg_(N("How to choose manually your mirror"), N("You may also choose your desired mirror manually: to do so, launch the Software Media Manager, and then add a `Security updates' medium. Then, restart %s.", $rpmdrake::myname_update)), myexit(-1); add_medium_and_check( $urpm, {}, $update_name, make_url_mirror($m), 'media_info/synthesis.hdlist.cz', update => 1, ); @update_medias = { name => $update_name }; #- hack to simulate a medium for parsing of descriptions } } } my ($cur, $section); #- parse the description file foreach my $medium (@update_medias) { foreach (cat_("$statedir/descriptions.$medium->{name}")) { /^%package +(.+)/ and do { my @pkg_list = split /\s+/, $1; exists $cur->{importance} && $cur->{importance} !~ /^(?:security|bugfix)\z/ and $cur->{importance} = 'normal'; $update_descr{$_} = $cur foreach @{$cur->{pkgs} || []}; $cur = { pkgs => \@pkg_list, medium => $medium->{name} }; $section = 'pkg'; next; }; /^%(pre|description)/ and do { $section = $1; next }; /^Updated?: +(.+)/ && $section eq 'pkg' and do { $cur->{update} = $1; next }; /^Importance: +(.+)/ && $section eq 'pkg' and do { $cur->{importance} = $1; next }; /^(ID|URL): +(.+)/ && $section eq 'pkg' and do { $cur->{$1} = $2; next }; $section =~ /^(pre|description)\z/ and $cur->{$1} .= $_; } } my $_unused = N("Please wait, finding available packages..."); my $wait = wait_msg_(N("Please wait, reading packages database...")); if (!$::statusbar) { $wait->{real_window}->show_all; $wait->{real_window}->realize; gtkflush(); $wait->{real_window}->queue_draw; } gtkflush(); # find out installed packages: #$urpm = urpm->new; #$urpm->read_config; my @base = ("basesystem", split /,\s*/, $urpm->{global_config}{'prohibit-remove'}); my (%base, %basepackages); my $db = $db; my $sig_handler = sub { undef $db; exit 3 }; local $SIG{INT} = $sig_handler; local $SIG{QUIT} = $sig_handler; while (defined(local $_ = shift @base)) { exists $basepackages{$_} and next; $db->traverse_tag(m|^/| ? 'path' : 'whatprovides', [ $_ ], sub { push @{$basepackages{$_}}, urpm_name($_[0]); push @base, $_[0]->requires_nosense; }); } foreach (values %basepackages) { my $n = @$_; #- count number of times it's provided foreach (@$_) { $base{$_} = \$n; } } my %installed_pkgs; $db->traverse(sub { my ($pkg) = @_; my $fullname = urpm_name($pkg); #- Extract summary and description since they'll be lost when the header is packed $installed_pkgs{$fullname} = { selected => 0, pkg => $pkg, urpm_name => urpm_name($pkg), summary => rpm_summary($pkg->summary), description => rpm_description($pkg->description), } if !($installed_pkgs{$fullname} && $installed_pkgs{$fullname}{description}); if (my $name = $base{$fullname}) { $installed_pkgs{$fullname}{base} = \$name; $pkg->set_flag_base(1) if $$name == 1; } $pkg->pack_header; }); my $group; if ($options{parallel} && (($group) = @{$options{parallel}})) { urpm::media::configure($urpm, parallel => $group); } # find out availlable packages: #$urpm = urpm->new; $urpm->{state} = {}; my %installable_pkgs; my %updates; @update_medias = grep { !$_->{ignore} && $_->{update} } @{$urpm->{media}}; check_update_media_version($urpm, @update_medias); my $requested = {}; my $state = {}; $urpm->request_packages_to_upgrade( $db, $state, $requested, start => 0, end => $#{$urpm->{depslist}}, ); $urpm->compute_installed_flags($db); # TODO/FIXME: not for updates $urpm->{depslist}[$_]->set_flag_installed foreach keys %$requested; #- pretend it's installed $urpm->{rpmdrake_state} = $state; #- Don't forget it my %pkg_sel = map { $_ => 1 } @{$options{'pkg-sel'} || []}; my %pkg_nosel = map { $_ => 1 } @{$options{'pkg-nosel'} || []}; my @updates_media_names = map { $_->{name} } @update_medias; foreach my $pkg (@{$urpm->{depslist}}) { $pkg->flag_upgrade or next; my $selected = 0; if (member(pkg2medium($pkg, $urpm)->{name}, @updates_media_names) && $pkg->flag_installed) { # TODO/FIXME: for updates any { $pkg->id >= $_->{start} && $pkg->id <= $_->{end} } @update_medias or next; if ($options{'pkg-sel'} || $options{'pkg-nosel'}) { my $n = urpm_name($pkg); $pkg_sel{$n} || $pkg_nosel{$n} or next; $pkg_sel{$n} and $selected = 1; } else { # selecting updates by default: $selected = 1; } $updates{urpm_name($pkg)} = { selected => $selected, pkg => $pkg }; } else { $installable_pkgs{urpm_name($pkg)} = { selected => $selected, pkg => $pkg }; } } if ($options{'pkg-sel'} && $options{'pkg-nosel'}) { push @{$options{'pkg-nosel'}}, @{$options{'pkg-sel'}}; delete $options{'pkg-sel'}; } statusbar_msg_remove($wait); $_->{pkg}->set_flag_installed foreach values %installed_pkgs; +{ urpm => $urpm, installed => \%installed_pkgs, installable => \%installable_pkgs, updates => \%updates, update_descr => \%update_descr, }; } # /var/lib/nfs/etab /var/lib/nfs/rmtab /var/lib/nfs/xtab /var/cache/man/whatis my %ignores_rpmnew = map { $_ => 1 } qw( /etc/adjtime /etc/group /etc/ld.so.conf /etc/localtime /etc/modules /etc/passwd /etc/security/fileshare.conf /etc/shells /etc/sudoers /etc/sysconfig/alsa /etc/sysconfig/autofsck /etc/sysconfig/harddisks /etc/sysconfig/harddrake2/previous_hw /etc/sysconfig/init /etc/sysconfig/installkernel /etc/sysconfig/msec /etc/sysconfig/nfs /etc/sysconfig/pcmcia /etc/sysconfig/rawdevices /etc/sysconfig/saslauthd /etc/sysconfig/syslog /etc/sysconfig/usb /etc/sysconfig/xinetd ); sub dialog_rpmnew { my ($msg, %p2r) = @_; @{$p2r{$_}} = grep { !$ignores_rpmnew{$_} } @{$p2r{$_}} foreach keys %p2r; my $sum_rpmnew = sum(map { int @{$p2r{$_}} } keys %p2r); $sum_rpmnew == 0 and return 1; my @inspect_wsize = ($typical_width*2.5, 500); my $inspect = sub { my ($file) = @_; my ($rpmnew, $rpmsave) = ("$file.rpmnew", "$file.rpmsave"); my $rpmfile = 'rpmnew'; -r $rpmnew or $rpmfile = 'rpmsave'; -r $rpmnew && -r $rpmsave && (stat $rpmsave)[9] > (stat $rpmnew)[9] and $rpmfile = 'rpmsave'; $rpmfile eq 'rpmsave' and $rpmnew = $rpmsave; my @diff = `/usr/bin/diff -u '$file' '$rpmnew'`; @diff = N("(none)") if !@diff; my $d = ugtk2->new(N("Inspecting %s", $file), grab => 1, transient => $w->{real_window}); my $save_wsize = sub { @inspect_wsize = $d->{rwindow}->get_size }; my %texts; gtkadd( $d->{window}, gtkpack_( gtknew('VBox', spacing => 5), 1, create_vpaned( create_vpaned( gtkpack_( gtknew('VBox'), 0, gtknew('Label', text_markup => qq($file:)), 1, gtknew('ScrolledWindow', child => $texts{file} = gtknew('TextView')), ), gtkpack_( gtknew('VBox'), 0, gtknew('Label', text_markup => qq($rpmnew:)), 1, gtknew('ScrolledWindow', child => $texts{rpmnew} = gtknew('TextView')), ), resize1 => 1, ), gtkpack_( gtknew('VBox'), 0, gtknew('Label', text => N("changes:")), 1, gtknew('ScrolledWindow', child => $texts{diff} = gtknew('TextView')), ), resize1 => 1, ), 0, gtkpack__( gtknew('HButtonBox'), gtksignal_connect( gtknew('Button', text => N("Remove .%s", $rpmfile)), clicked => sub { $save_wsize->(); unlink $rpmnew; Gtk2->main_quit }, ), gtksignal_connect( gtknew('Button', text => N("Use .%s as main file", $rpmfile)), clicked => sub { $save_wsize->(); renamef($rpmnew, $file); Gtk2->main_quit }, ), gtksignal_connect( gtknew('Button', text => N("Do nothing")), clicked => sub { $save_wsize->(); Gtk2->main_quit }, ), ) ) ); my %contents = (file => scalar(cat_($file)), rpmnew => scalar(cat_($rpmnew))); gtktext_insert($texts{$_}, [ [ $contents{$_}, { 'font' => 'monospace' } ] ]) foreach keys %contents; my @regexps = ([ '^(--- )|(\+\+\+ )', 'blue' ], [ '^@@ ', 'darkcyan' ], [ '^-', 'red3' ], [ '^\+', 'green3' ]); my $line2col = sub { $_[0] =~ /$_->[0]/ and return $_->[1] foreach @regexps; 'black' }; gtktext_insert($texts{diff}, [ map { [ $_, { 'font' => 'monospace', 'foreground' => $line2col->($_) } ] } @diff ]); $d->{rwindow}->set_default_size(@inspect_wsize); $d->main; }; interactive_packtable( N("Installation finished"), $w->{real_window}, $msg, [ map { my $pkg = $_; map { my $f = $_; my $b; [ gtkpack__( gtknew('HBox'), gtkset_markup( gtkset_selectable(gtknew('Label'), 1), qq($pkg:$f), ) ), gtksignal_connect( $b = gtknew('Button', text => N("Inspect...")), clicked => sub { $inspect->($f); -r "$f.rpmnew" || -r "$f.rpmsave" or $b->set_sensitive(0); }, ) ]; } @{$p2r{$pkg}}; } keys %p2r ], [ gtknew('Button', text => N("Ok"), clicked => sub { Gtk2->main_quit }) ] ); return 0; } sub perform_installation { #- (partially) duplicated from /usr/sbin/urpmi :-( my ($urpm, $pkgs) = @_; my $fatal_msg; my @error_msgs; my %Readmes; my $statusbar_msg_id; local $urpm->{fatal} = sub { printf STDERR "Fatal: %s\n", $_[1]; $fatal_msg = $_[1]; goto fatal_error }; local $urpm->{error} = sub { printf STDERR "Error: %s\n", $_[0]; push @error_msgs, $_[0] }; $w->{rwindow}->set_sensitive(0); my $group; if ($options{parallel} && (($group) = @{$options{parallel}})) { my $pkgs = join(' ', map { if_($_->flag_requested, urpm_name($_)) } @{$urpm->{depslist}}); system("urpmi -v --X --parallel $group $pkgs"); if ($? == 0) { $statusbar_msg_id = statusbar_msg( #N("Everything installed successfully"), N("All requested packages were installed successfully."), ); } else { interactive_msg_( N("Problem during installation"), N("There was a problem during the installation:\n\n%s", join("\n", @error_msgs)), scroll => 1, ); } open_db('force_sync'); $w->{rwindow}->set_sensitive(1); return 0; } my $_lock = urpm::lock::urpmi_db($urpm); my $_rpm_lock = urpm::lock::rpm_db($urpm, 'exclusive'); my %pkgs = map { $_->id => undef } grep { $_->flag_selected } @{$urpm->{depslist}}; my ($local_sources, $list, $local_to_removes) = urpm::get_pkgs::selected2list($urpm, \%pkgs, clean_all => 1, ); my $distant_number = scalar keys %pkgs; if (!$local_sources && (!$list || !@$list)) { interactive_msg_( N("Unable to get source packages."), N("Unable to get source packages, sorry. %s", @error_msgs ? N("\n\nError(s) reported:\n%s", join("\n", @error_msgs)) : ''), scroll => 1, ); goto return_with_error; } foreach (@$local_to_removes) { unlink $_; } my @pkgs = map { scalar($_->fullname) } sort(grep { $_->flag_selected } @{$urpm->{depslist}});#{ $a->name cmp $b->name } @{$urpm->{depslist}}[keys %{$state->{selected}}]; @{$urpm->{ask_remove}} = sort urpm::select::removed_packages($urpm, $urpm->{state}); my @to_remove = grep { $_ } map { if_($pkgs->{$_}{selected}, $pkgs->{$_}{urpm_name}) } keys %$pkgs; my $r = join "\n", urpm::select::translate_why_removed($urpm, $urpm->{state}, @to_remove); my $install_count = int(@pkgs); my $to_install = $install_count ? N("To satisfy dependencies, the following %d packages are going to be installed:\n%s\n", $install_count, formatlistpkg(map { s!.*/!!; $_ } @pkgs)) : ''; interactive_msg_(($to_install ? N("Confirmation") : N("Some packages need to be removed")), ($r ? (!$to_install ? join("\n\n", N("Remove %d packages?", scalar(@to_remove)), $r) : N("The following packages have to be removed for others to be upgraded: %s Is it ok to continue?", join("\n\n", $r, if_($to_install, $to_install)))) : $to_install), scroll => 1, yesno => 1) or do { $w->{rwindow}->set_sensitive(1); return 'canceled'; }; gurpm::init(1 ? N("Please wait") : N("Package installation..."), N("Initializing..."), transient => $w->{real_window}); my $distant_progress; my $canceled; my %sources = $urpm->download_source_packages( $local_sources, $list, force_local => 1, # removed in urpmi 4.8.7 ask_for_medium => sub { interactive_msg_( N("Change medium"), N("Please insert the medium named \"%s\" on device [%s]", $_[0], $_[1]), yesno => 1, text => { no => N("Cancel"), yes => N("Ok") }, ); }, callback => sub { my ($mode, $file, $percent) = @_; if ($mode eq 'start') { gurpm::label(N("Downloading package `%s' (%s/%s)...", basename($file), ++$distant_progress, $distant_number)); gurpm::validate_cancel(but(N("Cancel")), sub { $canceled = 1 }); } elsif ($mode eq 'progress') { gurpm::progress($percent/100); } elsif ($mode eq 'end') { gurpm::progress(1); gurpm::invalidate_cancel(); } $canceled and return 'canceled'; }, ); $canceled and goto return_with_error; gurpm::invalidate_cancel_forever(); my %sources_install = %{$urpm->extract_packages_to_install(\%sources, $urpm->{rpmdrake_state}) || {}}; my @rpms_install = grep { !/\.src\.rpm$/ } values %sources_install; my @rpms_upgrade = grep { !/\.src\.rpm$/ } values %sources; if (!$options{'no-verify-rpm'}) { gurpm::label(N("Verifying package signatures...")); my $total = @rpms_install + @rpms_upgrade; my $progress; my @invalid_sources = urpm::signature::check($urpm, \%sources_install, \%sources, translate => 1, basename => 1, callback => sub { gurpm::progress(++$progress/$total); }, ); if (@invalid_sources) { local $::main_window = $gurpm::mainw->{real_window}; interactive_msg_( N("Warning"), N("The following packages have bad signatures:\n\n%s\n\nDo you want to continue installation?", join("\n", sort @invalid_sources)), yesno => 1, if_(@invalid_sources > 10, scroll => 1), ) or goto return_with_error; } } before_leaving { urpm::removable::try_umounting_removables($urpm) }; my $something_installed; if (@rpms_install || @rpms_upgrade || @to_remove) { if (my @missing = grep { m|^/| && ! -e $_ } @rpms_install, @rpms_upgrade) { interactive_msg_( N("Installation failed"), N("Installation failed, some files are missing:\n%s\n\nYou may want to update your media database.", join "\n", map { " $_" } @missing) . (@error_msgs ? N("\n\nError(s) reported:\n%s", join("\n", @error_msgs)) : ''), if_(@error_msgs > 1, scroll => 1), ); goto return_with_error; } my $progress_nb; my $total_nb = scalar grep { m|^/| } @rpms_install, @rpms_upgrade; my $callback_inst = sub { my ($urpm, $type, $id, $subtype, $amount, $total) = @_; my $pkg = defined $id ? $urpm->{depslist}[$id] : undef; if ($subtype eq 'start') { if ($type eq 'trans') { gurpm::label(@rpms_install ? N("Preparing packages installation...") : N("Preparing...")); } elsif (defined $pkg) { $something_installed = 1; gurpm::label(N("Installing package `%s' (%s/%s)...", $pkg->name, ++$progress_nb, $total_nb)); } } elsif ($subtype eq 'progress') { gurpm::progress($total ? $amount/$total : 1); } }; my $fh; my @errors = urpm::install::install($urpm, \@to_remove, \%sources_install, \%sources, post_clean_cache => $urpm->{options}{'post-clean'}, callback_open => sub { my ($_data, $_type, $id) = @_; my $f = $sources_install{$id} || $sources{$id}; open $fh, $f or $urpm->{error}(N("unable to access rpm file [%s]", $f)); return fileno $fh; }, callback_inst => $callback_inst, callback_trans => $callback_inst, callback_close => sub { my ($urpm, undef, $pkgid) = @_; return unless defined $pkgid; my $pkg = $urpm->{depslist}[$pkgid]; my $fullname = $pkg->fullname; my $trtype = (any { /\Q$fullname/ } values %sources_install) ? 'install' : '(update|upgrade)'; foreach ($pkg->files) { /\bREADME(\.$trtype)?\.urpmi$/ and $Readmes{$_} = $fullname } close $fh; }, ); gurpm::end(); if (@errors || @error_msgs) { interactive_msg_( N("Problem during installation"), N("There was a problem during the installation:\n\n%s", join("\n", @errors, @error_msgs)), if_(@errors + @error_msgs > 1, scroll => 1), ); $w->{rwindow}->set_sensitive(1); return !$something_installed; } my %pkg2rpmnew; foreach my $u (@rpms_upgrade) { $u =~ m|/([^/]+-[^-]+-[^-]+)\.[^\./]+\.rpm$| and $pkg2rpmnew{$1} = [ grep { m|^/etc| && (-r "$_.rpmnew" || -r "$_.rpmsave") } map { chomp_($_) } run_rpm("rpm -ql $1") ]; } dialog_rpmnew(N("The installation is finished; everything was installed correctly. Some configuration files were created as `.rpmnew' or `.rpmsave', you may now inspect some in order to take actions:"), %pkg2rpmnew) and $statusbar_msg_id = statusbar_msg(N("All requested packages were installed successfully.")); if (keys %Readmes) { #- display the README*.urpmi files interactive_packtable( N("Upgrade information"), $w->{real_window}, N("These packages come with upgrade information"), [ map { my $fullname = $_; [ gtkpack__( gtknew('HBox'), gtkset_selectable(gtknew('Label', text => $Readmes{$fullname}),1), ), gtksignal_connect( gtknew('Button', text => N("Upgrade information about this package")), clicked => sub { interactive_msg_( N("Upgrade information about package %s", $Readmes{$fullname}), (join '' => formatAlaTeX(scalar cat_($fullname))), scroll => 1, ); }, ), ] } keys %Readmes ], [ gtknew('Button', text => N("Ok"), clicked => sub { Gtk2->main_quit }) ] ); } } else { gurpm::end(); interactive_msg_(N("Error"), N("Unrecoverable error: no package found for installation, sorry.")); } $w->{rwindow}->set_sensitive(1); statusbar_msg_remove($statusbar_msg_id); #- XXX maybe remove this return !($something_installed || scalar(@to_remove)); fatal_error: gurpm::end(); interactive_msg_(N("Installation failed"), N("There was a problem during the installation:\n\n%s", $fatal_msg)); return_with_error: gurpm::end(); $w->{rwindow}->set_sensitive(1); return 1; } # -=-=-=---=-=-=---=-=-=-- remove packages -=-=-=---=-=-=---=-=-=- sub perform_removal { my ($urpm, $pkgs) = @_; my @toremove = map { if_($pkgs->{$_}{selected}, $pkgs->{$_}{urpm_name}) } keys %$pkgs; my @results; slow_func_statusbar( N("Please wait, removing packages..."), $w->{real_window}, sub { @results = $options{parallel} ? urpm::parallel::remove($urpm, \@toremove) : urpm::install::install($urpm, \@toremove, {}, {}); open_db('force_sync'); }, ); if (@results) { interactive_msg_( N("Problem during removal"), N("There was a problem during the removal of packages:\n\n%s", join("\n", @results)), if_(@results > 1, scroll => 1), ); return 1; } else { return 0; } } # -=-=-=---=-=-=---=-=-=-- main -=-=-=---=-=-=---=-=-=- if ($options{'merge-all-rpmnew'}) { my %pkg2rpmnew; my $wait = wait_msg_(N("Please wait, searching...")); print "Searching .rpmnew and .rpmsave files...\n"; $db->traverse(sub { my $n = my_fullname($_[0]); $pkg2rpmnew{$n} = [ grep { m|^/etc| && (-r "$_.rpmnew" || -r "$_.rpmsave") } map { chomp_($_) } $_[0]->files ]; }); print "done.\n"; undef $wait; $typical_width = 330; dialog_rpmnew('', %pkg2rpmnew) and print "Nothing to do.\n"; myexit(0); } readconf(); $changelog_first = $changelog_first_config->[0]; $changelog_first = 1 if $options{'changelog-first'}; if (!$> && !member($MODE, @$already_splashed)) { interactive_msg_(N("Welcome"), N("%s Is it ok to continue?", $MODE eq 'remove' ? N("Welcome to the software removal tool! This tool will help you choose which software you want to remove from your computer.") : $MODE eq 'update' ? N("Welcome to %s! This tool will help you choose the updates you want to install on your computer.", $rpmdrake::myname_update) : ($rpmdrake::branded ? N("Welcome to the software installation tool!") : N("Welcome to the software installation tool! Your Mandriva Linux system comes with several thousands of software packages on CDROM or DVD. This tool will help you choose which software you want to install on your computer."))) , yesno => 1) or myexit(-1); push @$already_splashed, $MODE; } if ($MODE eq 'remove') { run_treeview_dialog(\&perform_removal); } else { run_treeview_dialog(\&perform_installation); } writeconf(); myexit(0);