#!/usr/bin/perl

#- Synchronize mulitple RPMS/SRPMS directories.
#- Copyright (C) 1999-2005 Mandriva (fpons@mandriva.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);

#- compare a version string.
use URPM;

#- keep some packages...
my $keep = 'kernel-BOOT';

#- get basename for a file.
sub basename { $_[0] =~ /([^\/]*)$/ ? $1 : $_[0]; }

#- system functions.
sub cp {
    my $pid;
    if ($pid = fork()) {
	waitpid($pid, 0);
    } else {
	exec '/bin/cp', @_;
    }
}
sub mv {
    my $pid;
    if ($pid = fork()) {
	waitpid($pid, 0);
    } else {
	exec '/bin/mv', @_;
    }
}
sub rm {
    my $pid;
    if ($pid = fork()) {
	waitpid($pid, 0);
    } else {
	exec '/bin/rm', '-f', @_;
    }
}

#- get a hash on name of srpms/rpms in a directory.
sub get_rpms {
    my ($dir, $rpms, $flag, $modifiable) = @_;

    opendir D, $dir or die "cannot open directory $dir\n";
    map { 
	if (/([^\/]*?)-([^-]*)-([^-]*)\.([^-\.]*)\.rpm$/) {
	    my $key = "$1 $4"; #- get name including architecture.
	    if ($rpms->{$key}) {
		if (URPM::ranges_overlap("== $2-$3", "> $rpms->{$key}{version}-$rpms->{$key}{release}")) {
		    if ($modifiable) {
			if ($flag->{sorted}) {
			    print "you said rpms directory is sorted, so I keep obsoleted $rpms->{$key}{file} by $_ in $rpms->{$key}{dir}\n" if $flag->{verbose};
			} else {
			    if (-d $flag->{conflict}) {
				print "moving obsoleted $rpms->{$key}{file} by $_ in $rpms->{$key}{dir}\n" if $flag->{verbose};
				mv("$rpms->{$key}{dir}/$rpms->{$key}{file}", $flag->{conflict});
			    } elsif ($flag->{clean}) {
				print "removing obsoleted $rpms->{$key}{file} by $_ in $rpms->{$key}{dir}\n" if $flag->{verbose};
				rm("$rpms->{$key}{dir}/$rpms->{$key}{file}");
			    }
			}
		    }
		    $rpms->{$key} = { key => $key,
				      name => $1,
				      version => $2,
				      release => $3,
				      arch => $4,
				      dir => $dir,
				      file => $_,
				    };
		} else {
		    if ($modifiable) {
			if (-d $flag->{conflict}) {
			    print "copying older or equal $_ by $rpms->{$key}{file} in $flag->{conflict}\n" if $flag->{verbose};
			    cp("$dir/$_", $flag->{conflict});
			    chmod 0644, "$flag->{conflict}/$_";
			} elsif ($flag->{clean}) {
			    print "removing older or equal $_ by $rpms->{$key}{file} in $dir\n" if $flag->{verbose};
			    rm("$dir/$_");
			}
		    }
		}
	    } else {
		$rpms->{$key} = { key => $key,
				  name => $1,
				  version => $2,
				  release => $3,
				  arch => $4,
				  dir => $dir,
				  file => $_,
			        };
	    }
	} else {
	    print STDERR "unable to parse filename $_\n";
	}
    } grep { /\.rpm$/ } readdir D;
    closedir D;
}

#- sync packages list according to hashes of rpms.
sub sync_medium {
    my ($rpmsdirs, $list, $rpms, $flag) = @_;
    my %pkg2dir;

    #- build a hash according to rpmsdirs and list for package name.
    my $i = 0;
    foreach (@$list) {
	local *F;
	open F, $_ or die "unable to open packages list file \"$_\"\n";
	foreach (<F>) {
	    chomp;
	    print STDERR "package \"$_\" is listed in mulitple list files!\n" if $pkg2dir{$_};
	    $pkg2dir{$_} = $rpmsdirs->[$i];
	    print "package \"$_\" listed in list files does not exists in rpms directory\n" if $flag->{verbose} && !$rpms->{$_};
	}
	close F;

	++$i;
    }

    #- check for right directory, and move if necessary.
    foreach (values %$rpms) {
	unless ($pkg2dir{$_->{key}}) {
	    print "file $_->{file} in $_->{dir} define package \"$_->{name}\" not listed in list files\n" if $flag->{verbose};
	} elsif ($_->{dir} ne $pkg2dir{$_->{key}}) {
	    print "moving file $_->{file} in $_->{dir} to $pkg2dir{$_->{key}}\n" if $flag->{verbose};
	    mv("$_->{dir}/$_->{file}", $pkg2dir{$_->{key}});
	    $_->{dir} = $pkg2dir{$_->{key}};
	}
    }
}

#- sync two hashes of rpms, update rpms and printer newer version that are not taken into account.
sub sync_rpms {
    my ($source, $target, $flag) = @_;

    #- search in target part.
    foreach (keys %$target) {
	/$keep/ and next;
	unless ($source->{$_}) {
	    if ($flag->{verbose}) {
		print "removing $target->{$_}{file}" . ($flag->{remove} ? " from $target->{$_}{dir}\n" : " is neccessary!\n");
		my $k = $_;
	    }
	    if ($flag->{remove}) {
		rm("$target->{$_}{dir}/$target->{$_}{file}");
	    }
	}
    }

    #- search in both part.
    foreach (keys %$source) {
	/$keep/ and next;
	if ($target->{$_}) {
	    if (URPM::ranges_overlap("== $source->{$_}{version}-$source->{$_}{release}",
				     ">= $target->{$_}{version}-$target->{$_}{release}")) {
		if ("$source->{$_}{version}-$source->{$_}{release}" eq "$target->{$_}{version}-$target->{$_}{release}") {
		    -s "$source->{$_}{dir}/$source->{$_}{file}" == -s "$target->{$_}{dir}/$target->{$_}{file}" and next;
		}
		if ($flag->{verbose}) {
		    print "updating $target->{$_}{dir}/$target->{$_}{file} with newer version $source->{$_}{file}\n";
		}
		if ($flag->{update}) {
		    cp("$source->{$_}{dir}/$source->{$_}{file}", $target->{$_}{dir});
		    chmod 0644, "$target->{$_}{dir}/$source->{$_}{file}";
		    unless (-e "$target->{$_}{dir}/$source->{$_}{file}") {
			die "unable to copy $source->{$_}{file} from $source->{$_}{dir} into $target->{$_}{dir}\n";
		    }
		    rm("$target->{$_}{dir}/$target->{$_}{file}") unless $source->{$_}{file} eq $target->{$_}{file}; #- copy on eq
		}
	    } elsif ($source->{$_}{version} ne $target->{$_}{version} || $source->{$_}{release} ne $target->{$_}{release}) {
		if ($flag->{verbose}) {
		    print STDERR "keeping more up-to-date version $target->{$_}{dir}/$target->{$_}{file} against $source->{$_}{dir}/$source->{$_}{file}, check your repository !\n";
		}
	    } #- say nothing if source is equal to target.
	}
    }

    #- search in source part.
    foreach (keys %$source) {
	/$keep/ and next;
	unless ($target->{$_}) {
	    if ($flag->{verbose}) {
		print "adding $source->{$_}{file}" . (-d $flag->{add} ? " to $flag->{add}\n" : " is neccessary!\n");
	    }
	    if (-d $flag->{add}) {
		cp("$source->{$_}{dir}/$source->{$_}{file}", $flag->{add});
		chmod 0644, "$flag->{add}/$source->{$_}{file}";
	    }
	}
    }
}

#- main program.
sub main {
    my @from_rpms;
    my @to_rpms;
    my @list;
    my $target;
    my %flag;
    my %source;
    my %target;

    foreach (@_) {
	if (/^--(\w*)$/) {
	    if ($1 eq 'verbose' || $1 eq 'update' || $1 eq 'remove' || $1 eq 'clean' || $1 eq 'sorted') {
		$flag{$1} = 1;
	    } elsif ($1 eq 'add' || $1 eq 'conflict') {
		$flag{$1} = undef;
	    } elsif ($1 eq 'from') {
		$target = \@from_rpms;
	    } elsif ($1 eq 'to') {
		$target = \@to_rpms;
	    } elsif ($1 eq 'list') {
		$target = \@list;
	    } else {
		die "unknown option: $1\n";
	    }
	} else {
	    if (exists $flag{add} && ! $flag{add}) {
		$flag{add} = $_;
		die "cannot add to non-directory: $_\n" unless -d $flag{add};
	    } elsif (exists $flag{conflict} && ! $flag{conflict}) {
		$flag{conflict} = $_;
		die "cannot add to non-directory: $_\n" unless -d $flag{conflict};
	    } else {
		die "unknown parameter: $_\n" unless $target;
		push @$target, $_;
	    }
	}
    }

    die "usage: syncrpms [--update] [--remove] [--clean] [--sorted] [--add <dir>] [--conflict <dir>] --from <dir_sources> --to <dir_targets> [--list <files>]\n"
	unless scalar(@from_rpms) > 0 || scalar(@to_rpms) > 0;

    #- parse directory structures.
    get_rpms($_, \%source, \%flag, 0) foreach @from_rpms;
    print STDERR "reading " . scalar(keys %source) . " packages as source rpms from\n";
    print STDERR "    $_\n" foreach @from_rpms;

    get_rpms($_, \%target, \%flag, 1) foreach @to_rpms;
    print STDERR "reading " . scalar(keys %target) . " packages as target rpms from\n";
    print STDERR "    $_\n" foreach @to_rpms;

    sync_medium(\@to_rpms, \@list, \%target, \%flag) if scalar(@list) > 0 && scalar(@to_rpms) > 0;
    sync_rpms(\%source, \%target, \%flag) if scalar(@from_rpms) > 0 && scalar(@to_rpms) > 0;
}

main(@ARGV);