You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.3 KiB
175 lines
5.3 KiB
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use Data::Dumper;
|
|
use File::Temp;
|
|
use POSIX qw/ strftime /;
|
|
use POSIX ":sys_wait_h";
|
|
|
|
my $sync_dir = $ENV{HOME}."/sync/";
|
|
my $doclib_path = "/path/to/products";
|
|
my $homes = "rsync.server.example.test";
|
|
|
|
sub version_compare {
|
|
my ($a, $b) = @_;
|
|
|
|
my @left = split( /\./, $a );
|
|
my @right = split( /\./, $b );
|
|
|
|
my $i = 0;
|
|
while ((defined $left[$i]) and (defined $right[$i])) {
|
|
my $cmp = $left[$i] <=> $right[$i];
|
|
return $cmp if $cmp;
|
|
$i++;
|
|
}
|
|
return ($left[$i] || 0) <=> ($right[$i] || 0);
|
|
}
|
|
|
|
sub split_path_with_prefix {
|
|
my ($path, $prefix) = @_;
|
|
my ($product, $version, $file);
|
|
|
|
if (($product, $version, $file) = $path =~ m#^${prefix}([A-Z]+)/(?:[-a-z]+[- ])?\1[- v]([0-9]+\.[0-9]+(?:\.[0-9]+)?)(/.+)?$#i) {
|
|
return ($product, $version, $file);
|
|
}
|
|
return;
|
|
}
|
|
|
|
print "\n+".("-"x76)."+\n";
|
|
print "|".(" "x31)."ACME Data Sync".(" "x31)."|\n";
|
|
print "+".("-"x76)."+\n\n";
|
|
|
|
|
|
open FIND_PRODUCTS, "-|", "ssh $homes find $doclib_path -maxdepth 2 -type d"
|
|
or die "open: ssh: $!";
|
|
|
|
my %keep = map { $_ => 1 } qw/PRODUCTA PRODUCTB PRODUCTC/;
|
|
my %available_products;
|
|
|
|
while (my $path = <FIND_PRODUCTS>) {
|
|
chomp $path;
|
|
my ($product, $version, $file) = split_path_with_prefix($path, $doclib_path);
|
|
next unless defined $product and $keep{$product};
|
|
$path = substr $path, length $doclib_path;
|
|
$available_products{$product}->{$version} = $path;
|
|
}
|
|
|
|
close FIND_PRODUCTS;
|
|
|
|
my %remote_major_versions;
|
|
while (my ($product, $versions) = each(%available_products)) {
|
|
my @versions = keys %{$versions};
|
|
|
|
# Compute major versions
|
|
@versions = sort {version_compare($a, $b)} @versions;
|
|
my %versions = map { m/^([0-9]+\.[0-9]+)/; $1 => $_ } @versions;
|
|
$remote_major_versions{$product} = \%versions;
|
|
}
|
|
|
|
my %local_versions;
|
|
open FIND_LOCAL_PRODUCTS, "-|", "find $sync_dir -maxdepth 2 -type d"
|
|
or die "open: find: $!";
|
|
while (my $path = <FIND_LOCAL_PRODUCTS>) {
|
|
chomp $path;
|
|
my ($product, $version, $file) = split_path_with_prefix($path, $sync_dir);
|
|
next unless defined $product;
|
|
$path = substr $path, length $sync_dir;
|
|
push @{$local_versions{$product}}, { version => $version, path => $path };
|
|
}
|
|
close FIND_LOCAL_PRODUCTS;
|
|
|
|
my %local_major_versions;
|
|
while (my ($product, $versions) = each(%local_versions)) {
|
|
my @versions = sort {version_compare($a, $b)} map { $_->{version} } @{$versions};
|
|
my %versions = map { m/^([0-9]+\.[0-9]+)/; $1 => $_ } @versions;
|
|
$local_major_versions{$product} = \%versions;
|
|
}
|
|
|
|
my %upgrades;
|
|
my %new_versions;
|
|
while (my ($product, $versions) = each(%remote_major_versions)) {
|
|
# Upgrade already fetched major versions
|
|
while (my ($major_version, $minor_version) = each(%{$versions})) {
|
|
if (exists $local_major_versions{$product} and exists $local_major_versions{$product}->{$major_version}) {
|
|
# Upgrade ?
|
|
if (version_compare($remote_major_versions{$product}->{$major_version}, $local_major_versions{$product}->{$major_version}) > 0) {
|
|
# Upgrade needed
|
|
$upgrades{$product}->{$local_major_versions{$product}->{$major_version}} = $minor_version;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Fetch the last version of new products
|
|
my @sorted_versions = sort {-version_compare($a, $b)} keys %{$versions};
|
|
unless (defined $local_major_versions{$product}) {
|
|
push @{$new_versions{$product}}, $remote_major_versions{$product}->{$sorted_versions[0]};
|
|
next;
|
|
}
|
|
|
|
# Fetch new major versions of existing products
|
|
foreach my $version (@sorted_versions) {
|
|
last if exists $local_major_versions{$product}->{$version};
|
|
push @{$new_versions{$product}}, $remote_major_versions{$product}->{$version};
|
|
}
|
|
}
|
|
|
|
unless ((scalar keys %new_versions) + (scalar keys %upgrades)) {
|
|
print "Nothing to do !\n";
|
|
exit;
|
|
}
|
|
|
|
print "New Versions: \n";
|
|
foreach my $product (sort keys %keep) {
|
|
if (exists $upgrades{$product}) {
|
|
while (my ($major, $minor) = each(%{$upgrades{$product}})) {
|
|
print " $product".(" "x(16 - length $product))."$minor\n";
|
|
}
|
|
}
|
|
if (exists $new_versions{$product}) {
|
|
foreach my $version (@{$new_versions{$product}}) {
|
|
print " $product".(" "x(16 - length $product))."$version\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
print "\nSync is starting...\n\n";
|
|
|
|
# Start RSYNC
|
|
pipe FROM_FATHER, TO_RSYNC0;
|
|
my $pid_rsync = fork();
|
|
die "fork: $!" unless defined $pid_rsync;
|
|
if ($pid_rsync == 0) { # Child: RSYNC
|
|
close TO_RSYNC0;
|
|
|
|
open STDIN, '<&', \*FROM_FATHER;
|
|
|
|
exec "rsync", "-aizy", "--progress", "--filter=. -", "$homes:$doclib_path", $sync_dir
|
|
or die "exec: rsync: $!";
|
|
# Child stops here
|
|
} # The father continues...
|
|
|
|
# Clean up...
|
|
close FROM_FATHER;
|
|
|
|
# Dump files to sync
|
|
my %seen_product_path;
|
|
while (my ($product, $versions) = each(%new_versions)) {
|
|
foreach my $version (@{$versions}) {
|
|
my $path = $available_products{$product}->{$version};
|
|
my ($product_path, @remaining_items) = split "/", $path;
|
|
unless (defined $seen_product_path{$product_path}) {
|
|
print TO_RSYNC0 "+ /$product_path\n";
|
|
$seen_product_path{$product_path} = 1;
|
|
}
|
|
print TO_RSYNC0 "+ /$path\n";
|
|
print TO_RSYNC0 "+ /$path/**\n";
|
|
}
|
|
}
|
|
print TO_RSYNC0 "- *\n";
|
|
|
|
# No more file to sync, notify RSYNC...
|
|
close TO_RSYNC0;
|
|
|
|
# Wait for RSYNC
|
|
waitpid $pid_rsync, 0;
|
|
|
|
|