1 |
#!/usr/bin/perl |
2 |
|
3 |
#- Copyright (C) 2005 MandrakeSoft SA |
4 |
#- Copyright (C) 2005-2010 Mandriva SA |
5 |
|
6 |
use strict; |
7 |
|
8 |
BEGIN { #- set up a safe path and environment |
9 |
$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"; |
10 |
delete @ENV{qw(ENV BASH_ENV IFS CDPATH)}; |
11 |
} |
12 |
|
13 |
use gurpmi; |
14 |
use urpm::install; |
15 |
use urpm::media; |
16 |
use urpm::signature; |
17 |
use urpm::get_pkgs; |
18 |
use urpm::msg; |
19 |
use urpm::util; |
20 |
use urpm::select; |
21 |
use urpm::main_loop; |
22 |
use Gtk2; |
23 |
|
24 |
#- default options. |
25 |
our $allow_medium_change = 0; |
26 |
our $auto_select = 0; |
27 |
our $force = 0; |
28 |
our $test = 0; |
29 |
our $use_provides = 1; |
30 |
|
31 |
my $progressbar_size = 450; |
32 |
|
33 |
#- GUI globals |
34 |
my ($mainw, $mainbox); |
35 |
|
36 |
#- Replaces the contents of the main window with the specified box |
37 |
#- (avoids popup multiplication) |
38 |
sub change_mainw { |
39 |
$mainw->remove($mainbox); |
40 |
($mainbox) = @_; |
41 |
$mainw->add($mainbox); |
42 |
$mainw->show_all; |
43 |
} |
44 |
|
45 |
sub sync () { |
46 |
$mainw->show; |
47 |
Gtk2->main_iteration while Gtk2->events_pending; |
48 |
} |
49 |
|
50 |
#- sets the window to a please-wait message |
51 |
sub wait_label { |
52 |
my ($o_text) = @_; |
53 |
my $wait_vbox = Gtk2::VBox->new(0, 5); |
54 |
my $wait_label = Gtk2::Label->new($o_text || N("Please wait...")); |
55 |
$wait_label->set_alignment(0.5, 0.5); |
56 |
$wait_vbox->pack_start($wait_label, 1, 1, 0); |
57 |
change_mainw($wait_vbox); |
58 |
sync(); |
59 |
} |
60 |
|
61 |
my @all_rpms = gurpmi::parse_command_line(); |
62 |
|
63 |
$> and fatal(N("Must be root")); |
64 |
|
65 |
#- Now, the graphical stuff. |
66 |
|
67 |
Gtk2->init; |
68 |
Gtk2->croak_execeptions; |
69 |
|
70 |
my $title = $::auto_select ? N("Distribution Upgrade") : N("Packages installation"); |
71 |
|
72 |
#- Create main window |
73 |
|
74 |
$mainw = Gtk2::Window->new('toplevel'); |
75 |
$::main_window = $mainw; |
76 |
$mainw->set_border_width(12); |
77 |
$mainw->set_title($title); |
78 |
$mainw->signal_connect(destroy => \&quit); |
79 |
$mainw->set_position('center'); |
80 |
$mainw->set_default_size($progressbar_size, 60); |
81 |
$mainw->set_type_hint('dialog'); # for matchbox window manager during install |
82 |
$mainw->set_modal(1); # for matchbox window manager during install |
83 |
$mainbox = Gtk2::VBox->new(0, 5); |
84 |
$mainw->add($mainbox); |
85 |
|
86 |
#- Performs installation |
87 |
|
88 |
my $urpm = configure_urpm(); |
89 |
my $state = {}; |
90 |
my %requested = $urpm->register_rpms(@all_rpms); |
91 |
if (@gurpmi::names) { |
92 |
urpm::select::search_packages($urpm, \%requested, [ @gurpmi::names ], |
93 |
use_provides => $use_provides, |
94 |
) || $force or exit 1; |
95 |
} |
96 |
|
97 |
wait_label(N("Preparing packages installation...")); |
98 |
|
99 |
#- return value is true if program should be restarted (in order to take care of important |
100 |
#- packages being upgraded (problably urpmi and perl-URPM, but maybe rpm too, and glibc also ?). |
101 |
my $restart_itself = urpm::select::resolve_dependencies($urpm, |
102 |
$state, |
103 |
\%requested, |
104 |
callback_choices => \&ask_choice, |
105 |
auto_select => $::auto_select, |
106 |
priority_upgrade => $urpm->{options}{'priority-upgrade'}, |
107 |
); |
108 |
my @ask_unselect = urpm::select::unselected_packages($urpm, $state); |
109 |
|
110 |
# If there are some unselected packages, designate that we are going to return nonzero code. |
111 |
if (@ask_unselect) { |
112 |
my $unselect_msg = N("Some requested packages cannot be installed:\n%s", |
113 |
urpm::select::translate_why_unselected($urpm, $state, @ask_unselect)); |
114 |
$urpm::postponed_msg .= $unselect_msg . "\n"; |
115 |
$urpm::postponed_code = 17; |
116 |
} |
117 |
|
118 |
@ask_unselect |
119 |
? ask_continue(N( |
120 |
"Some requested packages cannot be installed:\n%s\nContinue installation anyway?", |
121 |
urpm::select::translate_why_unselected($urpm, $state, @ask_unselect) |
122 |
), \&do_install) |
123 |
: do_install(); |
124 |
|
125 |
$mainw->show_all; |
126 |
Gtk2->main; |
127 |
|
128 |
my ($rpm_lock, $urpmi_lock); |
129 |
|
130 |
#- Creates and configure an urpm object for this application to use. |
131 |
sub configure_urpm() { |
132 |
my $urpm; |
133 |
{ |
134 |
local @ARGV = @ARGV; |
135 |
$urpm = urpm->new_parse_cmdline; |
136 |
} |
137 |
|
138 |
$urpm->{fatal} = sub { |
139 |
printf STDERR "%s\n", $_[1]; |
140 |
Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'error', 'ok', Locale::gettext::iconv($_[1], undef, 'UTF-8'))->run; |
141 |
quit(); |
142 |
exit $_[0]; |
143 |
}; |
144 |
$urpm->{log} = sub { printf "%s\n", $_[0] }; |
145 |
$urpm->{error} = sub { |
146 |
my ($message) = @_; |
147 |
printf STDERR "%s\n", $message; |
148 |
|
149 |
if (my $download_errors = delete $urpm->{download_errors}) { |
150 |
$message = join("\n", @$download_errors, $message); |
151 |
} |
152 |
my $nb_lines = $message =~ tr/\n/\n/; |
153 |
my $w; |
154 |
if ($nb_lines > 30 || $message =~ /^transaction is too small/) { |
155 |
$w = Gtk2::Dialog->new(N("Warning"), $mainw, [qw(modal destroy-with-parent)], N("Ok"), 'ok'); |
156 |
$w->vbox->add(my $f = Gtk2::Frame->new); |
157 |
my $sw = create_scrolled_window(my $text = Gtk2::TextView->new); |
158 |
$sw->set_border_width(2); |
159 |
$text->set_wrap_mode('word'); |
160 |
$f->add($sw); |
161 |
$text->get_buffer->set_text($message); |
162 |
$text->set_editable(0); |
163 |
$_->show foreach $f, $sw, $text; |
164 |
$w->set_size_request(400, 400); |
165 |
$w->set_default_response('ok'); |
166 |
} else { |
167 |
$w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'ok', $message); |
168 |
} |
169 |
$w->run; |
170 |
$w->destroy; |
171 |
}; |
172 |
urpm::select::set_priority_upgrade_option($urpm, $gurpmi::options{previous_priority_upgrade}); |
173 |
$rpm_lock = urpm::lock::rpm_db($urpm, 'exclusive'); |
174 |
$urpmi_lock = urpm::lock::urpmi_db($urpm); |
175 |
urpm::media::configure($urpm, |
176 |
media => $gurpmi::options{media}, |
177 |
searchmedia => $gurpmi::options{searchmedia}, |
178 |
update => $::update, |
179 |
); |
180 |
$urpm->{options}{'verify-rpm'} = 0 if $gurpmi::options{'no-verify-rpm'}; |
181 |
$urpm; |
182 |
} |
183 |
|
184 |
#- Callback for choices |
185 |
sub ask_choice { |
186 |
my (undef, undef, undef, $choices) = @_; |
187 |
return $choices->[0] if $gurpmi::options{auto}; |
188 |
my $radio; |
189 |
my @radios = map { |
190 |
$radio = Gtk2::RadioButton->new_with_label( |
191 |
$radio ? $radio->get_group : undef, |
192 |
(scalar $_->fullname) . " : " . $_->summary |
193 |
. ($_->flag_installed ? N(" (to upgrade)") : '') |
194 |
. ($_->flag_upgrade ? N(" (to install)") : '') |
195 |
); |
196 |
} @$choices; |
197 |
my $d = Gtk2::Dialog->new(N("Package choice"), $mainw, [], N("_Cancel") => 0, N("_Ok") => 1); |
198 |
my $label = Gtk2::Label->new(N("One of the following packages is needed:")); |
199 |
$label->set_alignment(0.5, 0.5); |
200 |
$d->vbox->pack_start($label, 1, 1, 0); |
201 |
$d->vbox->pack_start($_, 1, 1, 0) foreach @radios; |
202 |
my $n = 0; |
203 |
$d->signal_connect(response => sub { |
204 |
if ($_[1] == 1) { #- "ok" |
205 |
foreach (@radios) { last if $_->get_active; ++$n } |
206 |
} |
207 |
$d->destroy; |
208 |
exit(1) if $_[1] == 0; #- "cancel" |
209 |
}); |
210 |
$radios[0]->set_active(1); |
211 |
$d->set_default_response(1); # defaults to ok |
212 |
$d->show_all; |
213 |
$d->run; |
214 |
$choices->[$n]; |
215 |
} |
216 |
|
217 |
sub ask_continue { |
218 |
my ($msg, $nextclosure) = @_; |
219 |
my $vbox = Gtk2::VBox->new(0, 5); |
220 |
$vbox->pack_start(new_label($msg), 1, 1, 0); |
221 |
$urpm->{log}($msg); |
222 |
my $continue_button = Gtk2::Button->new(but(N("_Ok"))); |
223 |
my $quit_button = Gtk2::Button->new(but(N("_Abort"))); |
224 |
$quit_button->signal_connect(clicked => sub { $urpm->{log}("=> cancel"); &quit(); exit 1 }); |
225 |
$continue_button->signal_connect(clicked => sub { $urpm->{log}("=> ok"); goto &$nextclosure }); |
226 |
add_button_box($vbox, $quit_button, $continue_button); |
227 |
change_mainw($vbox); |
228 |
# default is to continue, but according to some HIG, warning should reverse the choise and defaults to abort |
229 |
$mainw->set_focus($continue_button); # also set_default should be called but it gives a warning! |
230 |
} |
231 |
|
232 |
sub ask_continue_if_no_auto { |
233 |
my ($msg, $nextclosure) = @_; |
234 |
if ($gurpmi::options{auto}) { |
235 |
$urpm->{log}($msg); |
236 |
$urpm->{log}("=> ok(auto)"); |
237 |
goto &$nextclosure; |
238 |
} else { |
239 |
ask_continue($msg, $nextclosure); |
240 |
} |
241 |
} |
242 |
|
243 |
sub ask_continue_blocking { |
244 |
my ($msg) = @_; |
245 |
my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'question', 'yes-no', $msg); |
246 |
my $answer = $w->run; |
247 |
$w->destroy; |
248 |
$urpm->{log}($msg . " => " . $answer); |
249 |
exit(1) if $answer eq 'no'; |
250 |
1; |
251 |
} |
252 |
|
253 |
sub do_install { |
254 |
wait_label(); |
255 |
my @ask_remove = urpm::select::removed_packages($urpm, $state); |
256 |
@ask_remove |
257 |
? ask_continue_if_no_auto(N( |
258 |
"The following packages have to be removed for others to be upgraded:\n%s\nContinue installation anyway?", |
259 |
urpm::select::translate_why_removed($urpm, $state, @ask_remove) |
260 |
), \&do_install_2) |
261 |
: goto &do_install_2; |
262 |
} |
263 |
|
264 |
sub do_install_2 () { |
265 |
my @to_install; |
266 |
my $sum; |
267 |
foreach my $pkg (sort { $a->name cmp $b->name } @{$urpm->{depslist}}[keys %{$state->{selected}}]) { |
268 |
if ($pkg->arch ne 'src') { |
269 |
push @to_install, scalar $pkg->fullname; |
270 |
$sum += $pkg->size; |
271 |
} |
272 |
} |
273 |
$urpm->{nb_install} = @to_install; |
274 |
@to_install > 1 |
275 |
? ask_continue_if_no_auto( |
276 |
(scalar(@to_install) == 1 ? |
277 |
N("To satisfy dependencies, the following package is going to be installed:") |
278 |
: N("To satisfy dependencies, the following packages are going to be installed:")) |
279 |
. join("\n", '', @to_install, '') |
280 |
. P("(%d package, %d MB)", "(%d packages, %d MB)", scalar(@to_install), scalar(@to_install), toMb($sum)), |
281 |
, \&do_install_3) |
282 |
: goto \&do_install_3; |
283 |
} |
284 |
|
285 |
sub do_install_3 () { |
286 |
wait_label($title); |
287 |
my ($local_sources, $blists) = urpm::get_pkgs::selected2local_and_blists($urpm, $state->{selected}); |
288 |
$local_sources || $blists or $urpm->{fatal}(3, N("unable to get source packages, aborting")); |
289 |
my $vbox = Gtk2::VBox->new(0, 5); |
290 |
|
291 |
my $global_label = gtk_new_Label_Left("<b>$title</b>"); |
292 |
$global_label->set_use_markup(1); |
293 |
$vbox->pack_start($global_label, 0, 0, 0); |
294 |
|
295 |
my $global_progressbar = Gtk2::ProgressBar->new; |
296 |
$vbox->pack_start($global_progressbar, 0, 0, 0); |
297 |
|
298 |
my $progress_label = gtk_new_Label_Left('-'); |
299 |
$vbox->pack_start($progress_label, 1, 1, 0); |
300 |
|
301 |
|
302 |
my $progressbar = Gtk2::ProgressBar->new; |
303 |
$progressbar->set_size_request($progressbar_size, -1); |
304 |
$vbox->pack_start($progressbar, 0, 0, 0); |
305 |
|
306 |
change_mainw($vbox); |
307 |
my ($progress_nb, $download_nb); |
308 |
my $set_progressbar = sub { |
309 |
my ($local_ratio) = @_; |
310 |
if ($progress_nb || $download_nb) { # this happens when computing transaction |
311 |
$global_progressbar->set_fraction(($download_nb + $progress_nb - 1 + $local_ratio) / 2 / $urpm->{nb_install}); |
312 |
} |
313 |
$progressbar->set_fraction($local_ratio); |
314 |
}; |
315 |
my $callback_inst = sub { |
316 |
my ($urpm, $type, $id, $subtype, $amount, $total) = @_; |
317 |
my $pkg = defined $id ? $urpm->{depslist}[$id] : undef; |
318 |
if ($subtype eq 'start') { |
319 |
if ($type eq 'trans') { |
320 |
$progress_label->set_label(N("Preparing...")); |
321 |
} elsif ($pkg) { |
322 |
$progress_nb++; |
323 |
$download_nb = max($download_nb, $progress_nb); |
324 |
$set_progressbar->(0); |
325 |
$progress_label->set_label( |
326 |
N("Installing package `%s' (%s/%s)...", $pkg->name, $progress_nb, $urpm->{nb_install}) |
327 |
); |
328 |
} |
329 |
} elsif ($subtype eq 'progress') { |
330 |
$set_progressbar->($amount / $total); |
331 |
} |
332 |
sync(); |
333 |
}; |
334 |
|
335 |
my $exit_code = urpm::main_loop::run($urpm, $state, scalar(@gurpmi::names), \@ask_unselect, \%requested, { |
336 |
bad_signature => sub { |
337 |
my ($msg, $msg2) = @_; |
338 |
$urpm->{log}("$msg\n$msg2"); |
339 |
ask_continue_blocking("$msg\n$msg2"); |
340 |
}, |
341 |
copy_removable => sub { |
342 |
#FIXME: use use hal to wait-for/mount cdroms: |
343 |
my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'ok-cancel', |
344 |
N("Please insert the medium named \"%s\"", $_[0]) |
345 |
); |
346 |
my $response = $w->run; |
347 |
$w->destroy; |
348 |
exit 1 if $response eq 'cancel'; |
349 |
1; |
350 |
}, |
351 |
trans_log => sub { |
352 |
my ($mode, $file, $percent, $_total, $_eta, $_speed) = @_; |
353 |
|
354 |
urpm::download::sync_logger(@_); |
355 |
|
356 |
if (member($mode, 'start', 'progress')) { |
357 |
$file =~ s|/*\s*$||; $file =~ s|.*/||; |
358 |
$progress_label->set_label(N("Downloading package `%s'...", $file) . "\n" . &urpm::download::progress_text); |
359 |
} |
360 |
if ($mode eq 'start') { |
361 |
$download_nb++; |
362 |
$set_progressbar->(0); |
363 |
select(undef, undef, undef, 0.1); #- hackish |
364 |
} elsif ($mode eq 'progress') { |
365 |
$set_progressbar->($percent / 100); |
366 |
} elsif ($mode eq 'end') { |
367 |
$set_progressbar->(1); |
368 |
} elsif ($mode eq 'error') { |
369 |
#- error is 3rd argument, saved in $percent |
370 |
push @{$urpm->{download_errors}}, N("...retrieving failed: %s", $percent); |
371 |
} |
372 |
sync(); |
373 |
}, |
374 |
ask_yes_or_no => \&ask_yes_or_no, |
375 |
|
376 |
completed => sub { |
377 |
$urpmi_lock->unlock; |
378 |
$rpm_lock->unlock; |
379 |
urpm::removable::try_umounting_removables($urpm); |
380 |
$vbox = Gtk2::VBox->new(0, 5); |
381 |
$progress_label = Gtk2::Label->new('-'); |
382 |
return 0 if $gurpmi::options{auto}; |
383 |
my $sw = create_scrolled_window($progress_label); |
384 |
$sw->set_size_request(500, 200); |
385 |
$vbox->pack_start($sw, 1, 1, 0); |
386 |
my $quit_button = Gtk2::Button->new(but(N("_Done"))); |
387 |
$quit_button->signal_connect(clicked => \&quit); |
388 |
add_button_box($vbox, $quit_button); |
389 |
change_mainw($vbox); |
390 |
$mainw->set_focus($quit_button); |
391 |
}, |
392 |
need_restart => sub { |
393 |
return if $gurpmi::options{auto}; |
394 |
my ($need_restart_formatted) = @_; |
395 |
my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'ok', |
396 |
join("\n", values %$need_restart_formatted) |
397 |
); |
398 |
$w->run; |
399 |
$w->destroy; |
400 |
}, |
401 |
missing_files_summary => sub { |
402 |
my ($error_sources) = @_; |
403 |
$progress_label->set_label(N("Installation failed, some files are missing:\n%s\nYou may want to update your urpmi database", |
404 |
join("\n", map { s|([^:]*://[^/:\@]*:)[^/:\@]*(\@.*)|$1xxxx$2|; " $_" } |
405 |
values %$error_sources))); |
406 |
}, |
407 |
trans_error_summary => sub { |
408 |
my ($_nok, $errors) = @_; |
409 |
$progress_label->set_label(N("Installation failed:") . "\n" . join("\n", map { "\t$_" } @$errors)); |
410 |
}, |
411 |
# TODO: use urpmi strings: |
412 |
already_installed_or_not_installable => sub { |
413 |
my ($_msg1, $_msg2) = @_; |
414 |
$progress_label->set_label(N("The package(s) are already installed")); |
415 |
}, |
416 |
success_summary => sub { $progress_label->set_label(N("Installation finished")) }, |
417 |
callback_report_uninst => sub { $progress_label->set_label(N("removing %s", $_[0])) }, |
418 |
inst => $callback_inst, |
419 |
trans => $callback_inst, |
420 |
} |
421 |
); |
422 |
|
423 |
# Merge postponed exit code to the result of package installation. |
424 |
$exit_code ||= $urpm::postponed_code; |
425 |
|
426 |
#- restart gurpmi if needed, keep command line for that. |
427 |
if ($restart_itself && !$exit_code) { |
428 |
print N("restarting urpmi"), "\n"; |
429 |
#- it seems to work correctly with exec instead of system, provided |
430 |
#- added --previous-priority-upgrade to allow checking if yet if |
431 |
#- priority-upgrade list has changed. and make sure we don't uselessly restart |
432 |
@ARGV = ('--previous-priority-upgrade=' . $urpm->{options}{'priority-upgrade'}, |
433 |
grep { !/^--no-priority-upgrade$|--previous-priority-upgrade=/ } @ARGV); |
434 |
exec $0, @ARGV; |
435 |
} |
436 |
|
437 |
# Show postponed message before exiting |
438 |
$urpm->{error}->($urpm::postponed_msg) if $urpm::postponed_code != 0; |
439 |
|
440 |
exit $exit_code; |
441 |
} |
442 |
|
443 |
sub ask_yes_or_no { |
444 |
my ($_title, $msg) = @_; |
445 |
# MessageDialogs have no titles unless using 'secondary-text' |
446 |
my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'yes-no', $msg); |
447 |
my $response = $w->run; |
448 |
$w->destroy; |
449 |
$response eq 'yes'; |
450 |
} |
451 |
|
452 |
sub gtk_new_Label_Left { |
453 |
my ($text) = @_; |
454 |
my $w = Gtk2::Label->new($text); |
455 |
$w->set_alignment(0, 0); |
456 |
$w; |
457 |
} |