#!/usr/bin/perl

# $Id: distit,v 1.33 1997/01/03 00:22:40 drz Exp drz $
# Copyright (c) 1996 Tim Newsome <drz@cybercomm.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.
#
#    You can find a copy of the GNU General Public License at
#    ftp://prep.ai.mit.edu/pub/gnu/COPYING or by writing to the Free
#    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# I wrote this cute little perl script to keep my .files the same on my
# miscellaneous shell accounts. The program gets all the files you specify
# from all your accounts. Then it determines which one is the newest, and
# copies that to all your accounts. It uses ssh, but it should work with
# rsh too.
# Put a list of all your accounts in ~/.di_accounts, and a list of files
# you want distributed in ~/.di_files. You can put a comment in by starting
# the line with a #. Here are mine:
# ------ .di_accounts ------
# drz@localhost
# drz@raven.cybercomm.net
# tim@regal.megasoft.com
# tim@reddevil.megasoft.com
# ------ .di_files ------
# .addressbook
# .di_*
# .fvwmrc
# .indent.pro
# .ircrc
# .lice.save
# .netscape/bookmarks.html
# .signature
# .vimrc
# .zshrc

# distit now also takes command line parameters.
# To include an account not in your .di_accounts or a file not in your
# .di_files just add it on the command line.
# If you don't want distit to read your .di_files or .di_accounts use
# -nf and -na on the commandline respectively. That way you could for
# instance use
# distit -nf -na .forward me@old.account me@different.old.account
# To only distribute certain files to certain accounts.
# Enjoy! I'd appreciate any feedback you may have.

sub user_configurable {
	# Where to put temporary files. This directory will be "rm -rf"ed so
	# pick a directory that will only be used for distit.
	$tempdir = "$mydir/".".distit";

	# Verbosity level.
	# 0 == quiet, 5 == max
	$chatter = 2;

	# You should be able to change those to rsh without any big problems.
	$ssh = "ssh";
	$scp = "scp";

	# If all your accounts have it installed, you can use md5sum which should
	# be more reliable (and a bit slower) in detecting changes.
	$sum = "sum";

	# Are there any other programs that are flag-compatible with tar?
	$tar = "tar";

	# The config files.
	$di_accounts = "$mydir/.di_accounts";
	$di_files = "$mydir/.di_files";
}

sub init {
	$mydir = $ENV{'HOME'};
	if ( $mydir eq "" ) {
		$me = `whoami`;
		chomp($me);
		$mydir = "/home/$me";
	}

	user_configurable;

	$use_accounts = 1;
	$use_files = 1;
	
	for $arg (@ARGV) {
		if ($arg eq "-na") {
			$use_accounts = 0;
		} elsif ( $arg eq "-nf" ) {
			$use_files = 0;
		} elsif ($arg =~ /^\w*\@/) {
			# It's an account name.
			push @accounts, $arg;
		} else {
			# It's a file-name.
			push @files, $arg;
		}
	}

	if ($use_accounts) {
		open(ACCOUNTS, $di_accounts) || die "Can't open $di_accounts: $!";
		while (<ACCOUNTS>) {
			unless (/^#/ || /^\s*$/) {
				chomp;
				push @accounts, $_;
			}
		}
		close ACCOUNTS;
	}

	if ($use_files) {
		open(FILES, $di_files) || die "Can't open $di_files: $!";
		while (<FILES>) {
			unless (/^#/ || /^\s*$/) {
				chomp;
				push @files, $_;
			}
		}
		close FILES;
	}

	%monthnum = ("Jan", "01",
			"Feb", "02",
			"Mar", "03",
			"Apr", "04",
			"May", "05",
			"Jun", "06",
			"Jul", "07",
			"Aug", "08",
			"Sep", "09",
			"Oct", "10",
			"Nov", "11",
			"Dec", "12");
	$time = localtime;
	($curmonth, $curyear) = ($time =~ /\w*\s*(\w*).*(\d{4})/);

	# This works on all shells, 2>&1 is bash-clone specific.
	#$tostdout = "|& cat";
}

sub is_up {
	local $host = $_[0];

	open(PING, "$ping $host |");
	while(<PING>) {
	}
}

sub main {
	# Expand any shell globbing.
	$tmp = `echo @files`;
	chomp($tmp);
	@files = split(/ /, $tmp);

	# Zeroth, set all files to not existing.

	for $account (@accounts) {
		for $file (@files) {
			$sum{$file}{$account} = "!exists";
		}
	}

	# First, get sum and date info about the files.

	for $account (@accounts) {
		chatter(1, "Getting info from $account...\n");
		($user, $host) = ($account =~ /(.*)\@(.*)/);
		$flist = "";
		for $file (@files) {
			$flist .= " $file";
		}
		# Add /dev/null so sum will always print a filename, even if $flist
		# only contains one file.
		$cmd = "$ssh -l $user $host \"$sum $flist /dev/null; ls -ld $flist\"";
		chatter(3, "$cmd\n");
		open(INFO, "$cmd |") || die "Couldn't open \"$cmd\": $!";
		while (<INFO>) {
			chomp;
			if (/^\s*(\d+)\s+(\d+)\s+(\S+)$/) {
				# Result of sum
				$sum{$3}{$account} = "$1-$2";
			} elsif (/^(\w+)\s+(\S+)$/) {
				# Result of md5sum
				$sum{$2}{$account} = $1;
			} elsif (/(\d+)\s+(\w+)\s+(\d+)\s+(\S+)\s+(\S+)$/) {
				# result of ls
				# $yot == year or time
				# ls prints either the year, or the modification time depending on
				# how long ago the file was last modified (cutoff is 6 months)
				($size, $month, $date, $yot, $fname) = ($1, $2, $3, $4, $5);
				if ($yot =~ /(\d*):(\d*)/) {
					# File has been modified in the last 6 months, so the year
					# wasn't given.
					if ($monthnum{$curmonth} >= $monthnum{$month}) {
						$year = $curyear;
					} else {
						$year = $curyear - 1;
					}
					$hour = $1;
					$minute = $2;
				} else {
					$year = $yot;
					$hour = "00";
					$minute = "00";
				}
				$date{$fname}{$account} = sprintf(
						"$year$monthnum{$month}%02d$hour$minute", $date);
			}
			chatter(4, "$_\n");
		}
		close INFO;
	}

	for $file (@files) {
		chatter(2, "Info for $file:\n");
		for $account (@accounts) {
			chatter(2, sprintf("\t%30s\t%12s\t$date{$file}{$account}\n",
					$account, $sum{$file}{$account}));
		}
	}

	# Now, let's see which files need to be redisted.

	for $file (@files) {
		$sum = "";
		for $account (@accounts) {
			if ($sum{$file}{$account} ne "") {
				if ($sum ne "" && $sum ne $sum{$file}{$account}) {
					push @distlist, $file;
					last;
				}
				$sum = $sum{$file}{$account};
			}
		}
	}

	if ($#distlist == -1) {
		chatter(1, "All files are up-to-date everywhere.\n");
		return;
	}

	chatter(1, "Redistributing @distlist...\n");

	# Next, get the latest edition of each file.

	$cmd = "rm -rf $tempdir ; mkdir $tempdir";
	chatter(3, "$cmd\n");
	system($cmd);

	chdir $tempdir;
	for $file (@distlist) {
		$lastdate = 0;
		$distacct{$file} = "";
		for $account (@accounts) {
			if ($date{$file}{$account} > $lastdate) {
				$lastdate = $date{$file}{$account};
				$distacct{$file} = $account;
			}
		}
		chatter(1, "Leeching $file from $distacct{$file}...\n");
		#$cmd = "$scp -l $distacct{$file}:~/$file $tempdir/";
		($user, $host) = ($distacct{$file} =~ /(.*)\@(.*)/);
		$cmd = "$ssh -l $user $host $tar cfp - $file 2>/dev/null | tar xfp -";
		chatter(3, "$cmd\n");
		system($cmd);
	}

	# And send it over.

	$tarfile = "$tempdir/updates.tar";
	for $account(@accounts) {
		$flist = "";
		for $file (@distlist) {
			if ($sum{$file}{$distacct{$file}} ne $sum{$file}{$account}) {
				# This account has a different file from the one we're
				# disting.
				$flist .= " $file";
			}
		}
		if ($flist ne "") {
			chatter(1, "Sharing the wealth with $account...\n");
			$cmd = "$tar cfp $tarfile $flist";
			chatter(3, "$cmd\n");
			system($cmd);# || die "\"$cmd\" failed: $!";
			($user, $host) = ($account =~ /(.*)\@(.*)/);
			$cmd = "$ssh -l $user $host $tar xfp - < $tarfile";
			chatter(3, "$cmd\n");
			system($cmd);
		}
	}

	# Finally, clear up our directory.

	system("rm -rf $tempdir");

	return;
}

sub chatter {
	local ($level, $text) = ($_[0], $_[1]);

	if ($level <= $chatter) {
		print $text;
	}
}

init;
main;
