#!/usr/bin/perl

#- Copyright (C) 1999,2001 MandrakeSoft (pixel@linux-mandrake.com)
#-
#- This program is free software; you can redistribute it and/or modify
#- it under the terms of the GNU General Public License as published by
#- the Free Software Foundation; either version 2, or (at your option)
#- any later version.
#-
#- 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 qw(subs vars refs);
use urpm;

#- get I18N translation method.
import urpm _;

#- default options.
my $update = 0;
my $media = 0;
my $auto = 0;
my $allow_medium_change = 0;
my $auto_select = 0;
my $force = 0;
my $sync = undef;
my $X = 0;
my $WID = 0;
my $all = 0;
my $rpm_opt = "vh";
my $use_provides = 1;
my $fuzzy = 0;
my $src = 0;
my $noclean = 0;
my $verbose = 0;
my $root = '';
my $bug = '';
my $env = '';
my $log = '';

my $uid;
my @files;
my @src_files;
my @rpms_install;
my @rpms_upgrade;
my @names;
my @src_names;

$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin";
delete @ENV{qw(ENV BASH_ENV IFS CDPATH)};
($<, $uid) = ($>, $<);
    
sub usage {
    print STDERR _("urpmi version %s
Copyright (C) 1999, 2000, 2001, 2002 MandrakeSoft.
This is free software and may be redistributed under the terms of the GNU GPL.

usage:
", $urpm::VERSION) . _("  --help         - print this help message.
") . _("  --update       - use only update media.
") . _("  --media        - use only the media listed by comma.
") . _("  --auto         - automatically select a good package in choices.
") . _("  --auto-select  - automatically select packages for upgrading the system.
") . _("  --fuzzy        - impose fuzzy search (same as -y).
") . _("  --src          - next package is a source package (same as -s).
") . _("  --noclean      - keep rpm not used in cache.
") . _("  --force        - force invocation even if some packages do not exist.
") . _("  --wget         - use wget to retrieve distant files.
") . _("  --curl         - use curl to retrieve distant files.
") . _("  --bug          - output a bug report in directory indicated by next arg.
") . _("  --env          - use specific environment (typically a bug report).
") . _("  --X            - use X interface.
") . _("  --best-output  - choose best interface according to the environment:
                   X or text mode.
") . _("  -a             - select all matches on command line.
") . _("  -p             - allow search in provides to find package.
") . _("  -P             - do not search in provides to find package.
") . _("  -y             - impose fuzzy search (same as --fuzzy).
") . _("  -s             - next package is a source package (same as --src).
") . _("  -q             - quiet mode.
") . _("  -v             - verbose mode.
") . "\n" . _("  names or rpm files given on command line are installed.
");
    exit(0);
}

#- parse arguments list.
my @nextargv;
for (@ARGV) {
    /^--help$/ and do { usage; next };
    /^--update$/ and do { $update = 1; next };
    /^--media$/ and do { push @nextargv, \$media; next };
    /^--mediums$/ and do { push @nextargv, \$media; next };
    /^--auto$/ and do { $auto = 1; next };
    /^--allow-medium-change$/ and do { $allow_medium_change = 1; next };
    /^--auto-select$/ and do { $auto_select = 1; next };
    /^--fuzzy$/ and do { $fuzzy = 1; next };
    /^--src$/ and do { $src = 1; next };
    /^--noclean$/ and do { $noclean = 1; next };
    /^--force$/ and do { $force = 1; next };
    /^--wget$/ and do { $sync = \&urpm::sync_wget; next };
    /^--curl$/ and do { $sync = \&urpm::sync_curl; next };
    /^--bug$/ and do { push @nextargv, \$bug; next };
    /^--env$/ and do { push @nextargv, \$env; next };
    /^--X$/ and do { $X = 1; next };
    /^--WID=(.*)$/ and do { $WID = $1; next };
    /^--WID$/ and do { push @nextargv, \$WID; next };
    /^--best-output$/ and do { $X ||= $ENV{DISPLAY} && -x "/usr/X11R6/bin/grpmi" && system('/usr/X11R6/bin/xtest', '') == 0;
			       next };
    /^--comment$/ and do { push @nextargv, undef; next };
    /^--root$/ and do { push @nextargv, \$root; next };
    /^-(.*)$/ and do { foreach (split //, $1) {
	/[\?h]/ and do { usage; next };
	/a/ and do { $all = 1; next };
	/c/ and do { next };
	/m/ and do { next };
	/M/ and do { next }; #- nop
	/q/ and do { $rpm_opt = ""; next };
	/p/ and do { $use_provides = 1; next };
	/P/ and do { $use_provides = 0; next };
	/y/ and do { $fuzzy = 1; next };
	/s/ and do { $src = 1; next };
	/v/ and do { $verbose = 1; next };
	die _("urpmi: unknown option \"-%s\", check usage with --help\n", $1); } next };
    @nextargv and do { my $r = shift @nextargv; $r and $$r = $_; next };
    if (/\.rpm$/) {
	if (/\.src\.rpm$/) {
	    push @src_files, $_;
	} else {
	    push @files, untaint($_);
	}
	next;
    }
    if ($src) {
	push @src_names, $_;
    } else {
	push @names, $_;
    }
    $src = 0; #- reset switch for next package.
}

#- params contains informations to parse installed system.
my $urpm = new urpm;
my ($pid_out, $pid_err);

#- prepare bug report.
if ($bug) {
    system("rm", "-rf", $bug);
    mkdir $bug or $urpm->{fatal}(8, _("Unable to create directory [%s] for bug report", $bug));
    #- copy all synthesis file used, along with configuration of urpmi
    system("cp", "-af", $urpm->{config}, $urpm->{skiplist}, $urpm->{instlist}, $bug);
    local *DIR;
    opendir DIR, $urpm->{statedir};
    while (defined ($_ = readdir DIR)) {
	/synthesis\./ and system "cp", "-af", "$urpm->{statedir}/$_", $bug;
    }
    closedir DIR;
    #- allow log file.
    $log = "$bug/urpmi.log";
}

if ($env) {
    print STDERR "using specific environment on $env\n";
    $log = "$env/urpmi_env.log";
    unlink $log;
    #- setting new environment.
    $urpm->{config} = "$env/urpmi.cfg";
    $urpm->{skiplist} = "$env/skip.list";
    $urpm->{instlist} = "$env/inst.list";
    $urpm->{statedir} = $env;
} else {
    if ($uid > 0) {
	#- only src files are installable using urpmi.
	@names || @files and $urpm->{fatal}(1, _("Only superuser is allowed to install packages"));

	#- allow installation.
	@rpms_install = @src_files;
    } else {
	#- allow log if not defined.
	$log ||= "/var/log/urpmi.log";
    }
}

if ($log) {
    #- log only at this point in case of query usage.
    log_it(scalar localtime, " urpmi called with @ARGV\n");

    open SAVEOUT, ">&STDOUT"; select SAVEOUT; $| = 1;
    open SAVEERR, ">&STDERR"; select SAVEERR; $| = 1;
    unless ($pid_out = open STDOUT, "|-") {
	while (<STDIN>) {
	    open F, ">>$log"; select F; $| = 1;
	    select SAVEOUT; $| = 1;
	    $/ = \1;
	    print SAVEOUT $_;
	    print F $_;
	    close F;
	}
	exit 0;
    }
    unless ($pid_err = open STDERR, "|-") {
	while (<STDIN>) {
	    open F, ">>$log"; select F; $| = 1;
	    select SAVEERR; $| = 1;
	    $/ = \1;
	    print SAVEERR $_;
	    print F $_;
	    close F;
	}
	exit 0;
    }
    select STDERR; $| = 1;     # make unbuffered
    select STDOUT; $| = 1;     # make unbuffered
}

#- use specific sync routine.
$sync and $urpm->{sync} = $sync;

#- remove verbose if not asked.
unless ($bug) {
    $urpm->{fatal} = sub { printf SAVEERR "%s\n", $_[1]; exit($_[0]) };
    $urpm->{error} = sub { printf SAVEERR "%s\n", $_[0] };
    $urpm->{log} = sub { printf SAVEERR "%s\n", $_[0] };
}
$verbose or $urpm->{log} = sub {};

$urpm->configure(nocheck_access => $env || $uid > 0,
		 media => $media,
		 update => $update,
		);

my ($start, $end) = $urpm->register_rpms(@files, @src_files);

if ($bug) {
    #- and a dump of rpmdb itself as synthesis file.
    my $db = URPM::DB::open($root);
    my $sig_handler = sub { undef $db; exit 3 };
    local $SIG{INT} = $sig_handler;
    local $SIG{QUIT} = $sig_handler;
    local *RPMDB;
    open RPMDB, "| " . ($ENV{LD_LOADER} || '') . " gzip -9 >'$bug/rpmdb.cz'";
    $db->traverse(sub{
		      my ($p) = @_;
		      #- this is not right but may be enough.
		      my $files = join '@', grep { exists $urpm->{provides}{$_} } $p->files;
		      $p->pack_header;
		      $p->build_info(fileno *RPMDB, $files);
		  });
    close RPMDB;
}

#- select individual files.
my $state = { requested => {} };
defined $start && defined $end and @{$state->{requested}}{($start .. $end)} = ();

#- search the packages according the selection given by the user,
#- basesystem is added to the list so if it need to be upgraded,
#- all its dependency will be updated too.
#- make sure basesystem exists before.
if (@names) {
    $urpm->search_packages($state->{requested}, [ @names ],
			   all => $all,
			   use_provides => $use_provides,
			   fuzzy => $fuzzy)
	or $force or exit 1;
}
if (@src_names) {
    $urpm->search_packages($state->{requested}, [ @src_names ],
			   all => $all,
			   use_provides => $use_provides,
			   fuzzy => $fuzzy,
			   src => 1)
	or $force or exit 1;
}

#- filter to add in packages selected required packages.
sub ask_choice {
    my ($urpm, $db, $state, $choices) = @_;
    my $n = 1; #- default value.
    my (@l) = map { scalar $_->fullname } @$choices;
    my $from = undef; #TODO

    if (@l > 1 && !$auto) {
	my $msg = (defined $from ?
		   _("One of the following packages is needed to install %s:", $from) :
		   _("One of the following packages is needed:"));
	if ($X) {
	    `gchooser "$msg" @l`;
	    $n = $? >> 8 || die;
	} else {
	    message("$msg");
	    my $i = 0; foreach (@l) { message(" " . ++$i . "- $_") }
	    while (1) {
		$n = message_input(_("What is your choice? (1-%d) ", $i));
		defined $n or exit 1;
		1 <= $n && $n <= $i and last;
		message(_("Sorry, bad choice, try again\n"));
	    }
	}
    }

    $choices->[$n - 1];
};

#- open/close of database should be moved here, in order to allow testing
#- some bogus case and check for integrity.
{
    my $db;
    
    #- take care of specific environment.
    if ($env) {
	$db = new URPM;
	$db->parse_synthesis("$env/rpmdb.cz");
    } else {
	$db = URPM::DB::open($root);
    }

    my $sig_handler = sub { undef $db; exit 3 };
    local $SIG{INT} = $sig_handler;
    local $SIG{QUIT} = $sig_handler;

    require URPM::Resolve;
    #- auto select package for upgrading the distribution.
    if ($auto_select) {
	my (%to_remove, %keep_files);

	$urpm->resolve_packages_to_upgrade($db, $state, requested => undef);

	if (%{$state->{ask_remove}} || %{$state->{ask_unselect}}) {
	    $urpm->{error}(_("some packages have to be removed for being upgraded, this is not supported yet\n"));
	}
    }

    $urpm->resolve_requested($db, $state, callback_choices => \&ask_choice);
}

#- get out of package that should not be upgraded.
$urpm->deselect_unwanted_packages($state->{selected});

#- package to install as a array of strings.
my @to_install;

#- check if there is at least one package to install that
#- has not been given by the user.
my $ask_user = $env;
my $sum = 0;
my @root_only;

foreach my $pkg (sort { $a->name cmp $b->name } @{$urpm->{depslist}}[keys %{$state->{selected}}]) {
    $ask_user ||= $pkg->flag_required || $auto_select;

    my $fullname = $pkg->fullname;
    if (!$env && $uid > 0 && $pkg->arch ne 'src') {
	push @root_only, $fullname;
    } elsif ($uid > 0 || $pkg->arch ne 'src') {
	$sum += $pkg->size;
	push @to_install, $fullname;
    }
}
if ($env) {
    my $msg = _("To satisfy dependencies, the following packages are going to be installed (%d MB)", toMb($sum));
    my $p = join "\n", @to_install;
    print STDERR "$msg:\n$p\n";
    exit 0; #- exit now for specific environment.
}
if (@root_only) {
    print STDERR _("You need to be root to install the following dependencies:\n%s\n", join ' ', @root_only);
    exit 1;
} elsif (!$auto && $ask_user) {
    my $msg = _("To satisfy dependencies, the following packages are going to be installed (%d MB)", toMb($sum));
    my $msg2 = _("Is it OK?");
    my $p = join "\n", @to_install;
    if ($X) {
	my $ok = _("Ok");
	my $cancel = _("Cancel");
	`gmessage -default $ok -buttons "$ok:0,$cancel:2" "$msg:\n$p\n\n$msg2"`;
	$? and exit 0;
    } else {
	$noexpr = _("Nn");
	$yesexpr = _("Yy");
	message_input("$msg:\n$p\n$msg2" . _(" (Y/n) ")) =~ /[$noexpr]/ and exit 0;
    }
}

#- if not root, the list become invisible and no download will be possible.
my ($local_sources, $list, $local_to_removes) = $urpm->get_source_packages($state->{selected});
unless ($local_sources || $list) {
    $urpm->{fatal}(3, _("unable to get source packages, aborting"));
}
#- clean cache with file that are not necessary with this transaction.
unless ($noclean) {
    foreach (@$local_to_removes) {
	unlink $_;
    }
}

my %sources = $urpm->download_source_packages($local_sources, $list, ($X ? '' : 'force_local'),
					      (!$auto || $allow_medium_change) && sub {
						  my $msg = _("Please insert the medium named \"%s\" on device [%s]", @_);
						  my $msg2 = _("Press Enter when it's done...");
						  if ($X) {
						      my $ok = _("Ok");
						      my $cancel = _("Cancel");
						      $msg =~ s/"/\\"/g;
						      `gmessage -default $ok -buttons "$ok:0,$cancel:2" "$msg"`;
						      !$?;
						  } else {
						      defined message_input("$msg\n$msg2 ");
						  }
					      });

#- install package.
if ($uid == 0) {
    @rpms_install = grep { $_ !~ /\.src\.rpm$/ } values %{$urpm->extract_packages_to_install(\%sources) || {}};
    @rpms_upgrade = grep { $_ !~ /\.src\.rpm$/ } values %sources;
}

if (@rpms_install || @rpms_upgrade) {
    message(_("installing %s\n", join(' ', @rpms_install, @rpms_upgrade)));
    log_it(scalar localtime, " ", join(' ', @rpms_install, @rpms_upgrade), "\n");
    #- check for local files.
    foreach (@rpms_install, @rpms_upgrade) {
	m|^/| && ! -e $_ or next;
	message(_("Installation failed, some files are missing.\nYou may want to update your urpmi database"));
	exit 2;
    }
    $urpm->{log}("starting installing packages");
    if ($X && !$root) {
	system("grpmi", $WID ? ("--WID=$WID") : (),
	       (map { ("-noupgrade", $_) } @rpms_install), @rpms_upgrade);
	if ($?) {
	    #- grpmi handles --nodeps and --force by itself,
	    #- and $WID is defined when integrated in rpminst.
	    $WID or message(_("Installation failed"));
	    exit(($? >> 8) + 32); #- forward grpmi error + 32
	}
    } else {
	if (@rpms_install) {
	    system("rpm", "-i$rpm_opt", "--nodeps", @rpms_install, ($root ? ("--root", $root) : ()));
	    if (@$_) {
		message(_("Installation failed"));

		m|^/| && !-e $_ and exit 2 foreach @rpms_install; #- missing local file
		$auto and exit 1; #- if auto has been set, avoid asking user.

		$noexpr = _("Nn");
		$yesexpr = _("Yy");
		message_input(_("Try installation even more strongly (--force)? (y/N) "), $force && $yesexpr) =~ /[$yesexpr]/
		  or exit 1;
		$urpm->{log}("starting force installing packages without deps");
		system("rpm", "-i$rpm_opt", "--nodeps", "--force", @rpms_install, ($root ? ("--root", $root) : ()));
	    }
	}
	if (@rpms_upgrade) {
	    system("rpm", "-U$rpm_opt", @rpms_upgrade, ($root ? ("--root", $root) : ()));
	    if ($?) {
		message(_("Installation failed"));

		m|^/| && !-e $_ and exit 2 foreach @rpms_upgrade; #- missing local file
		$auto and exit 1; #- if auto has been set, avoid asking user.

		$noexpr = _("Nn");
		$yesexpr = _("Yy");
		message_input(_("Try installation without checking dependencies? (y/N) "), $force && $yesexpr) =~ /[$yesexpr]/
		  or exit 1;
		$urpm->{log}("starting installing packages without deps");
		system("rpm", "-U$rpm_opt", "--nodeps", @rpms_upgrade, ($root ? ("--root", $root) : ()));

		if ($?) {
		    message(_("Installation failed"));
		    message_input(_("Try installation even more strongly (--force)? (y/N) "), $force && $yesexpr) =~ /[$yesexpr]/
		      or exit 1;
		    $urpm->{log}("starting force installing packages without deps");
		    system("rpm", "-U$rpm_opt", "--nodeps", "--force", @rpms_upgrade, ($root ? ("--root", $root) : ()));
		}
	    }
	}
    }
} else {
    message(_("everything already installed"), $auto);
}

#- this help flushing correctly by closing this file before (piped on tee).
#- but killing them is generally better.
if ($pid_err && $pid_out) {
    kill 15, $pid_err, $pid_out;
    close STDERR;
    close STDOUT;
}

sub toMb {
    my $nb = $_[0] / 1024 / 1024;
    int $nb + 0.5;
}

sub log_it {
    #- if invoked as a simple user, nothing should be logged.
    if ($log) {
	local *LOG;
	open LOG, ">>$log" or die "can't output to log file\n";
	print LOG @_;
    }
}

#- message functions.
sub message {
    my ($msg, $noX) = @_;
    if ($X && !$noX) {
	`gmessage -default Ok -buttons Ok "$msg"`;
	$bug and log_it($msg);
    } else {
	if ($bug) {
	    print STDOUT "$msg\n";
	} else {
	    print SAVEOUT "$msg\n";
	}
    }
}
sub message_input {
    my ($msg, $default_input) = @_;

    if ($X && !default_input) { #- if a default input is given, the user doesn't have to choose (and being asked).
	`gmessage -default Ok -buttons Ok "$msg"`;
	$bug and log_it($msg);
    } else {
	if ($bug) {
	    print STDOUT "$msg";
	} else {
	    print SAVEOUT "$msg";
	}
    }

    my $input = $default_input || <STDIN>;
    $bug and log_it($input);
    return $input;
}

sub untaint {
    my @r = ();
    foreach (@_) {
        /(.*)/;
	push @r, $1;
    }
    @r == 1 ? $r[0] : @r
}