#! /usr/bin/perl -w
##################################################################
#
# $Id: compare_rpm_version.in,v 1.8 2003/01/30 22:40:25 rbos Exp $
#
# A script to compare rpm versions
#
# Written by Richard Bos
# Copyright (C) 2002,2003 Richard Bos
#
# Send suggestions to: apt4rpm-devel@lists.sourceforge.net
# Homepage: http://apt4rpm.sourceforge.net
#
# 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
# of the License, 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.
#
# For a copy of the GNU General Public License, visit
# http://www.gnu.org or write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##################################################################

use strict;
use Getopt::Long;

my $PROGRAM = 'compare_rpm_version';
my $VERSION = '0.64.2';
my $BUGS = 'apt4rpm-users@lists.sourceforge.net';

sub help ()
{
	print <<EOF;
Usage: $PROGRAM [options] infile

Retrieve the most recent rpms from aptate's caches.
The algoritm used to determine the most recent rpm is purely
based on the version and release string.

Options:
	--rpmtype:	bin - for binary rpms (default)
			pat - for patch rpms
			src - for source rpms

	--format:	Specify the output format, there are 2 formats.
			- shortname (default)
			- path

	--debug:	verbose output

Report bugs to <$BUGS>
EOF

	exit 0;
}

sub version ()
{
	printf("%s %s\n", $PROGRAM, $VERSION);
	exit 0;
}

my $debug;
sub cnv_alpha {

	# Characters in a version/release string are informative for humans, but
	# they are difficult when determining the most recent version of an rpm.
	# If characters are encountered they are replaced by an empty string ("..").

	# Strings in version identifiers are used to tell that the package is
	# a Release Candidate (rc), Pre Release (pre), alpha (a) state, etc.
	# Examples are:
	# - 1.0rc1
	# - 1.0pre2
	# - 0.6.8rc3-03
	# - 2.1 is considered smaller than 2.10
	# - 2.1 is considered equal to 2.01
	# - 2.01.1 is newer than 2.1 (or 2.01)
	# - If the result of the comparison results in a status quo (the 2
	#   version/release strings are considered numerically to be same),
	#   the rpm version/release strings are compared alphanumerically.
	#   If compared alphanumerically. the strings are converted to lower case.
	#   An example: 1.11b36-2 is considered more recent than 1.11a36-2 or
	#   1.11A36-2 (this case the a stands for alpha and the b for beta).

	# The result of this function has been compared with the application
	# rpmver and it seems that this function does a better job :)

	my $str  = shift;
	my $type = shift;

	if ($str =~ /[_[:alpha:]]/) {
		if ($debug) {
			printf(STDERR "DEBUG: found characters in %s string\n", $type);
			printf(STDERR "DEBUG: %s=%s\n", $type, $str);
		}
		
		$str =~ s/\.[_[:alpha:]]+\./../g;
		if ($debug) {
			printf(STDERR "DEBUG: filtering with: .[:alpha:]+. => %s\n", $str);
		}
	
		$str =~ s/[_[:alpha:]]+\./../g;
		if ($debug) {
			printf(STDERR "DEBUG: filtering with:  [:alpha:]+. => %s\n", $str);
		}
	
		$str =~ s/\.[_[:alpha:]]+/../g;
		if ($debug) {
			printf(STDERR "DEBUG: filtering with: .[:alpha:]+  => %s\n", $str);
		}
	
		$str =~ s/[_[:alpha:]]+/../g;
		if ($debug) {
			printf(STDERR "DEBUG: filtering with:  [:alpha:]+  => %s\n", $str);
		}
	}

	if ($debug) {
		printf(STDERR "DEBUG: %s format after alpha filtering=%s\n", $type, $str);
	}
	
	return ($str);
}

sub fill {

	my $arr    = shift;
	my $maxpos = shift;
	my $vrtype = shift;	# version, release
	my $type   = shift;	# challenger, defender
	my $field;

	for ($field = @{$arr}; $field <= $maxpos; $field++) {
		$arr->[$field] = 0;
	}

	if ( $debug ) {
		printf(STDERR "DEBUG: new %s %s format=%s\n",
		  $type, $vrtype, (join '.', @{$arr}))
	}
}

sub compare {
	my $df = shift;
	my $ch = shift;
	my $vrtype = shift;

	my (@df, @ch);

	@df = split /\./, $df;

	my $tmp = cnv_alpha($ch, $vrtype);
	@ch = split /\./, $tmp;

	# Make the comparing arrays the same length/size
	if ( $#ch > $#df ) {

		fill (\@df, $#ch, $vrtype, "defender");

	} else {

		fill (\@ch, $#df, $vrtype, "challenger");
	}

	my $status = "equal";
	for (my $fld = 0; $fld <= $#df; $fld++) {

		# A variable may not be empty in a numerical comparison ($a > $b).
		# A variable is empty because at the concerning location a string
		# was located.  A string is considered smaller than "0" => make it -1.
		if ( $ch[$fld] eq "" ) { $ch[$fld] = -1 };
		if ( $df[$fld] eq "" ) { $df[$fld] = -1 };

		if ($debug) {
			printf(STDERR "DEBUG: matching field %s: ", $fld);
			printf(STDERR "challenger=%s ", $ch[$fld]);
			printf(STDERR "defender=%s => ", $df[$fld]);
		}

		if ($ch[$fld] > $df[$fld]) {

			$status = "bigger";

			if ($debug) {
				printf(STDERR "bigger\n");
			}
			last;

		} elsif ( $ch[$fld] < $df[$fld]) {

			$status = "smaller";

			if ($debug) {
				printf(STDERR "smaller\n");
			}
			last;

		} else {

			if ($debug) {
				printf(STDERR "equal\n");
			}
		}
	}

	return ($status, $tmp)
}

my $format = '';
my $rpmtype = '';
Getopt::Long::config ("bundling", "pass_through");
Getopt::Long::GetOptions (
	'version'	=> \&version,
	'help'		=> \&help,
	'debug'		=> \$debug,
	'rpmtype=s'	=> \$rpmtype,
	'format=s'	=> \$format,
) or exit 1;

die "$0: wrong number of arguments.\n"
	if ( $#ARGV != 0 );
my $infile = $ARGV[0] ;

die "$0: input file missing.\n"
	if ( not $infile );
die "$0: input file $infile not found.\n"
	if (not -e $infile);

if ( $rpmtype eq "" ) {
	$rpmtype = "bin"

} elsif ( $rpmtype ne "bin" && $rpmtype ne "pat" && $rpmtype ne "src" ) {
	printf ("$0: incorrect rpm type specified\n");
	exit 1;
}

if ( $format eq "" ) {
	$format = "shortname"

} elsif ( $format ne "path" && $format ne "shortname" ) {
	printf ("$0: incorrect output format specified\n");
	exit 1;
}

my @fld;
my @rpm;
my %seen;
my $tmp;
my @uniq;

# Field positions
my $type		= 0;
my $file		= 1;
my $name		= 2;
my $vr		= 3; # version-release string
my $arch		= 4;
my $size		= 9;
my $version	= 10;
my $release	= 11;

open (CACHE, "< $infile");
while (<CACHE>) {
	chomp;
	@fld = split ';';
	push (@uniq, $fld[$name]) unless $seen{$fld[$name]}++;

	# Add 2 seperate fields for rpm version and rpm release
	$tmp = @fld;

	($fld[$version], $fld[$release]) = split /-/, $fld[$vr];
	push @rpm, [@fld];
}

my $dv;
my $dr;
my $pkg;
my $rel;
my $status;
my $versn;
my %vr;

foreach my $rpm (@uniq) {
	$dv = 0;  
	$dr = 0;  

	if ($debug) {
		printf(STDERR "DEBUG: ===   package: %s   ===\n", $rpm);
	}

	for $pkg (@rpm) {

		# Can't use perl's grep function as it fails on filenames with +'s
		# Example of such a flename is: libsigc++-1.0.3
		if ( $pkg->[$type] eq $rpmtype && $pkg->[$name] eq $rpm ) {
			if ($debug) {
				printf(STDERR "DEBUG: defender:   %s-%s\n", $dv, $dr);
				printf(STDERR "DEBUG: challenger: %s-%s\n", 
				  $pkg->[$version], $pkg->[$release]);
			}

			($status, $versn) = compare ($dv, $pkg->[$version], "version");

			if ($status eq "bigger") {

				$dv = $versn;
				$dr = cnv_alpha($pkg->[$release], "release");

				$vr{$rpm}->{"version"} = $pkg->[$version];
				$vr{$rpm}->{"release"} = $pkg->[$release];
				$vr{$rpm}->{"arch"}    = $pkg->[$arch];
				$vr{$rpm}->{"type"}    = $pkg->[$type];
				$vr{$rpm}->{"file"}    = $pkg->[$file];
				$vr{$rpm}->{"size"}    = $pkg->[$size];

				if ($debug) {
					printf(STDERR "DEBUG: new defender version=%s-%s\n", $dv, $dr)
				}

			} elsif ( $status eq "equal" ) {

				if ($debug) {
					printf(STDERR "DEBUG: examining release string\n")
				}

				($status, $rel) = compare ($dr, $pkg->[$release], "release");

				if ($status eq "bigger") {
					$dv = $versn;
					$dr = $rel;

				 	$vr{$rpm}->{"version"} = $pkg->[$version];
					$vr{$rpm}->{"release"} = $pkg->[$release];
					$vr{$rpm}->{"type"}    = $pkg->[$type];
					$vr{$rpm}->{"file"}    = $pkg->[$file];
					$vr{$rpm}->{"arch"}    = $pkg->[$arch];

					if ($debug) {
						printf(STDERR "DEBUG: New defender version=%s-%s\n", $dv, $dr)
					}
			   } elsif ( $status eq "equal" ) {

					my $dvr; # Defender Version Release
					$dvr = lc $vr{$rpm}->{"version"}."-".$vr{$rpm}->{"release"};

					my $cvr; # Challender Version Release
					$cvr = lc $pkg->[$version]."-".$pkg->[$release];

					if ($debug) {
						printf(STDERR "DEBUG: performing alphanumerical comparison\n");
						printf(STDERR "DEBUG: comparing challenger: %s\n", $cvr);
						printf(STDERR "DEBUG: with defender       : %s => ", $dvr);
					}

					if ( $cvr gt $dvr ) {
						if ($debug) {
							printf(STDERR "bigger\n");
						}

						$dv = $versn;
						$dr = $rel;

					 	$vr{$rpm}->{"version"} = $pkg->[$version];
						$vr{$rpm}->{"release"} = $pkg->[$release];
						$vr{$rpm}->{"arch"}    = $pkg->[$arch];

						if ($debug) {
							printf(STDERR "DEBUG: New defender version=%s-%s\n", $dv, $dr)
						}
					} elsif ( $cvr eq $dvr ) {
						if ($debug) {
							printf(STDERR "equal\n");
						}
					} elsif ($debug) {
						printf(STDERR "smaller\n");
					}
				}
			}

			if ($debug) {
				printf(STDERR "DEBUG: --------------------\n")
			}

		}
	}
}

my $rpm;
if ($format eq "shortname" ) {
	foreach $rpm (sort keys %vr) {
		printf("%s.rpm;%s-%s-%s.%s.rpm\n", $rpm, $rpm,
		  $vr{$rpm}->{"version"}, $vr{$rpm}->{"release"}, $vr{$rpm}->{"arch"}); 
	}
} else {
	foreach $rpm (sort keys %vr) {
		printf("%s;%s-%s-%s.%s.rpm;%s;%s\n",
		  $vr{$rpm}->{"type"},
		  $rpm,
		  $vr{$rpm}->{"version"}, $vr{$rpm}->{"release"},
		  $vr{$rpm}->{"arch"},
		  $vr{$rpm}->{"file"},
		  $vr{$rpm}->{"size"},
		); 
	}
}

#vim:sts=3:sw=3

