#!/usr/bin/perl -w

# Simple script to get web pages and compare them with a stored version.
# Essentially, this is a glorified way to call wget(1) and diff(1).
#
# see wwwdiff(1) and wwwdiff-files(5) for a documentation

use strict;

my $home=$ENV{'HOME'};
defined $home && -d $home or $home = "";

my $wwwdiff_directory = $ENV{'WWWDIFF_DIRECTORY'}; 
defined($wwwdiff_directory) or $wwwdiff_directory = "$home/.wwwdiff";

-d $wwwdiff_directory or mkdir $wwwdiff_directory or die "Couldn't create mirror directory $wwwdiff_directory\n";

$wwwdiff_directory =~ /\/$/ and chop($wwwdiff_directory);

@ARGV == 2 and do {
	add_url($ARGV[0], $ARGV[1]);
	exit 0;
};

my $single_url = "";
my $initial_wait = $ENV{'WWWDIFF_INITIALWAIT'};
my $wait = $ENV{'WWWDIFF_WAIT'};

@ARGV == 1 and do {
	$single_url = quote($ARGV[0]);
	defined($initial_wait) or $initial_wait = 2;
	defined($wait) or $wait = 3;
};

@ARGV != 0 and @ARGV != 1 and die "Number of arguments has to be 0, 1 or 2 (URL and email).\n";

print `date`;
print "Updating mirror...\n";

defined($initial_wait) or $initial_wait = 3600;
$initial_wait = int(rand($initial_wait));

defined($wait) or $wait = 600;

print "Waiting for $initial_wait seconds to scatter start time.\n";
sleep($initial_wait);

opendir my $mdir, $wwwdiff_directory
	or die "Unable to open $wwwdiff_directory ($!)\n";

my @TASKS = readdir $mdir;

close $mdir;

foreach(@TASKS) {
	$_ eq "." and next;
	$_ eq ".." and next;
	$single_url eq "" or $_ eq $single_url or next;

	my $dir = $wwwdiff_directory . "/" . $_;

	my $dowait = int(rand($wait));

	print "Resting for $dowait seconds.\n";
	sleep($dowait);
	print "Updating $_\n";
	print `date`;
	get_lock("$dir/#lock") and next;

	system "/bin/sh", "$dir/get", "$dir/datanew"
		and do {
			print "Fetch failed ($?), giving up.\n";
			rmdir "$dir/#lock";
			next;
		};

	system "touch", "$dir/datanew";
	open(DIFF, "diff -u \Q$dir/data\E \Q$dir/datanew\E |")
		or do {
			print "couldn't fork diff ($!)\n";
			rmdir "$dir/#lock";
			next;
		};

	my $diff = do {local $/; <DIFF> };

	close(DIFF) or (not $!) or do {
		print "close of diff failed ($!)\n";
		rmdir "$dir/#lock";
		next;
	};

	$? >> 8 == 1 and do {
		print "Found a difference:\n" . $diff . "\n";
		
		open(MAIL,"| /bin/sh \Q$dir/notify\E") 
			or do {
				print "couldn't open notificaiton process ($!)\n";
				rmdir "$dir/#lock";
				next;
			};

		print MAIL $diff;

		close(MAIL) 
			or do {
				print "Notificaiton processes close failed ($!)\n";
				rmdir "$dir/#lock";
				next;
			};

	};

	system "mv", "$dir/data", "$dir/dataold";
	system "mv", "$dir/datanew", "$dir/data";

	rmdir "$dir/#lock";
}

print "Done: " . `date`;
exit 0;

######################################################################

sub add_url
{
	my ($url,$email) = @_;

	print "Will watch $url informing $email about changes.\n";
	my $dir = $wwwdiff_directory . "/" . quote($url);

	-d $dir and die "Directory $dir exists already.\nIt seems that the URL is already mirrored;" .
		" rename the directory if this is not the case.\n";

	mkdir $dir;
	
	open(GET, "> $dir/get")
		or die "Failed to open $dir/get ($!)\n";

	if ($url =~ /^</) {
	  # watch an edbrowse marco
	  print GET "edbrowse <<EOI\n$url\nw \$1\nqt\nEOI\n";
    } else {
	  print GET "wget \Q$url\E -O \$1";
    }
	
	close(GET)
		or die "Failed to close $dir/get ($!)\n";

	open(NOTIFY, "> $dir/notify")
		or die "Failed to open $dir/notify ($!)\n";

	print NOTIFY "mailx -s \QUpdate of $url\E \Q$email\E";

	close(NOTIFY)
		or die "Failed to open $dir/notify ($!)\n";

	print "Administrative files written. Will fetch to get initial state\n";

	system "/bin/sh", "$dir/get", "$dir/datanew"
		and print "Warning: fetch failed! ($?)\n";

	system "cp", "$dir/datanew", "$dir/dataold"
		and print "Waring: cp failed. ($?)\n";

	system "mv", "$dir/datanew", "$dir/data"
		and print "Waring: cp failed. ($?)\n";

}

# try to crete a new directory $lock; if not wait
# and trie another few times.
sub get_lock
{
	my ($lock) = @_;

	my $retries = 4;

	for($retries--;$retries>=0;$retries--) {
		mkdir $lock and return 0;
		print "Failed to create lock $lock ($!)\nWill try another $retries times.\n";
		if ($retries) {
			my $waitlock = 10+int(rand(60));
			print "Wating $waitlock seconds before retrying again.\n";
			sleep $waitlock;
		}
	}
	print "Giving up on getting lock $lock.\n";
	return 1;
}

sub quote
{
	my ($name) = @_;

	$name =~ s/([^-a-zA-Z0-9])/"_" . ord ($1) . "_" /eg;
	return $name;
}
