#!/usr/bin/perl -w
#
# I use up2date, and have it not delete the binaries. This accumulates
# cruft over time, and up2date doesn't have the "delete older
# binaries" option that I'd like it to have. So, this script attempts
# to clean up the specified (default current) directory in that
# manner.
#
# CAUTION: Deletes files. YHBW.
#
# September 2002:
#  Use RPM::Header to examine and compare versions
#  Use Getopts::Long to get options
# 08/06/2003 RPM::Header is no longer viable. curses!
# 15/02/2004 Added back in a hand-rolled vercmp
# 22/02/2004 Swapped meaning of debug & verbose flags (sanity)

# Try and find a viable RPM package
my $RPMAPI = "RPM::Header";
eval {
    require RPM::Header;
};
if ( $@) {
    eval {
        require RPM2;
        $RPMAPI = "RPM2";
    };
}
if ( $@ ) {
    $RPMAPI = "rpm";
}

# hush about warnings, thanks
sub goober {
    $RPM::err;
}

use Getopt::Long;

my $verbose = 0;
my $dryrun = 0;
my $debug = 0;

GetOptions( "verbose!" => \$verbose,
            "debug!" => \$debug,
            "dry-run!" => \$dryrun ) or
  print STDERR "usage: $0 [--verbose] [--dryrun] [rpmdirs]\n" and
  exit 1;

# if no directory is specified, clean the current directory
if (!@ARGV) {
	push @ARGV, ".";
}

while ( @ARGV ) {
    my %packages;

    $dir = shift;
    $dir =~ s|(.+)/$|$1|;
    print STDERR "Cleaning $dir\n" if $debug;

    if ( ! -d $dir ) {
        print STDERR "$dir: no such directory, skipping\n";
        next;
    }

    # Get a list of ".rpm" files in the directory. Note that gnorpm
    # currently downloads files as .rpm.tmp and doesn't rename them, so
    # we'll pick them up too. And .hdr files are from up2date; we just
    # want to remove unattached ones of those.
    opendir( DIR, "$dir" ) or die "$dir:$!\n";
    @files = grep /\.rpm$|\.rpm.tmp$|\.hdr$/, readdir( DIR );
    closedir( DIR );

    # Loop over the set of files we've collected
    for my $file ( sort @files ) {
        next if $file =~ /\.hdr$/;
        my $deleteme = "";
        my @bits;
        $file = "$dir/$file";
        print STDERR "Checking $file..." if $debug;

        # Use RPM to parse out the version, rather than the mish-mash of
        # regexps I'd otherwise need.
        my ( $package, $version, $release, $hdr ) =
          get_NVR( $file );

        # Maybe it's busted.
        if ( !defined( $package ) || !defined( $version ) ||
             !defined( $release )) {
            print STDERR "Failed to get version info on $file\n";
            next;
        }

        # Rename the GnoRPM files
        if ( $file =~ s/\.tmp$// ) {
            rename( "${file}.tmp", $file );
        }

        # treat source files separately
        if ( $file =~ /src\.rpm$/ ) {
            $package = "$package src";
        }

        print " Package $package $version $release: " if $debug;

        # Compare it against what we've already got.
        if ( defined( $packages{$package} )) {
            my ( $p, $v, $r, $f, $h )  = @{$packages{$package}};

            # Outright version difference
            my $vdiff = cmpver( $hdr, $h, $v, $r );

            if ( $vdiff < 0 ) {
                @bits = ( $p, $v, $r, $f, $h );
                $deleteme = $file;
                print "older than $v $r, " if $debug;
            } elsif ( $vdiff == 0 ) {
                @bits = ( $package, $version, $release, $file, $hdr );
                $deleteme = "";
                print "identical to $v $r, skipping.\n" if $debug;
            } else {
                @bits = ( $package, $version, $release, $file, $hdr );
                $deleteme = $f;
                print "supercedes $v $r, " if $debug;
            }
        } else {
            # Don't know about this package yet
            print "noted.\n" if $debug;
            @bits = ( $package, $version, $release, $file, $hdr );
        }

        # Nuke if necessary
        if ( $deleteme ) {
            print "deleting $deleteme.\n" if ( $verbose or $debug);
            unlink( $deleteme ) unless $dryrun;
            $deleteme =~ s/\.rpm/\.hdr/;
            unlink( $deleteme ) unless $dryrun;
        }

        # Make a note
        $packages{$package} = \@bits;
    }

    # Clean up unclaimed hdr files
    #  for my $file ( @files ) {
    #	next unless $file =~ /\.hdr$/;
    #	next unless defined $hdr{$hdr};
    #   unlink( $file );
    #  }
}

sub get_NVR {
    my $file = shift;
    my ( $package, $version, $release, $hdr );
    if ( $RPMAPI eq "RPM::Header" ) {
        $hdr = new RPM::Header $file;
        if ( !defined( $hdr )) {
            print STDERR "Can't access header of $file: $RPM::err\n";
            return;
        }
        ( $package, $version, $release ) =
          ( $hdr->{'NAME'}, $hdr->{'VERSION'}, $hdr->{'RELEASE'} );
    } else {
        ( $package, $version, $release ) =
          split( /\s/, `rpm -qp --queryformat "%{NAME} %{VERSION} %{RELEASE}" $file` );
        $hdr = [ $package, $version, $release ];
    }

    # Return the results
    return ( $package, $version, $release, $hdr );
}

sub cmpver {
    my ( $hdr, $h, $v2, $r2 ) = @_;

    if ( $RPMAPI eq "RPM::Header" ) {
        return $hdr->cmpver( $h )
    } else {
        my ( $v1, $r1 ) = ( $hdr->[1], $hdr->[2] );

        # this is actually pretty damned awkward, due to the wide variety
        # of crap that can appear in a version string. Also, we should be
        # checking the epoch if it's present, but screw that for a game of
        # soldiers.
        if ( $v1 eq $v2 ) {
            return $r1 <=> $r2 if $r1 =~ /^\d+(\.\d+)?$/ and $r2 =~ /^\d+(\.\d+)?$/;
            return myvercmp( $r1, $r2 );
        } else {
            # simple case: numeric compare
            return $v1 <=> $v2 if $v1 =~ /^\d+(\.\d+)?$/ and $v2 =~ /^\d+(\.\d+)?$/;
            return myvercmp( $v1, $v2 );
        }
    }
}

sub myvercmp {
    my $v1 = shift;
    my $v2 = shift;

    my @parts1 = split( /\./, $v1 );
    my @parts2 = split( /\./, $v2 );

    my $parts = scalar( @parts1 );
    if ( scalar( @parts2 ) > $parts ) {
        $parts = scalar( @parts2 );
    }

    for my $i ( 0 .. $parts - 1 ) {
        my $p1 = $parts1[$i] || 0;
        my $p2 = $parts2[$i] || 0;
        next if $p1 eq $p2;

        if ( $p1 =~ /^\d+$/ and $p2 =~ /^\d+$/ ) {
            return $p1 <=> $p2;
        } else {
            # here be dragons. due to collating order issues, this may
            # actually screw up and I should do more work on it as
            # soon as I can be bothered.
            return $p1 cmp $p2;
        }
    }
}
