#!/usr/bin/perl -w
use strict;

# All the same scheduling and checking if the drive is mounted,
# but uses rsync instead of dar!

=pod testing
my $src_dir = "/home/pjungwir/src/flashcards/";
my $dest_dir = "/home/pjungwir/src/test/rsync/bak2";
my $excludes_file = "$dest_dir/excludes.txt";
my $current_dir = "current";
my $incremental_dir = "inc";
my $last_backup_file = "$dest_dir/last-backup.txt";
my $one_day = 60*60*24;	# one day in seconds
my $today_in_seconds = time();
my $lock = "/tmp/rsync-backup";
my $max_incrementals = 3;
my $previous_backup_date;
=cut

# my $src_dir = "/home/pjungwir";
my $src_dir = "/";
my $dest_dir = "/media/Elements/bak/shiny";
my $excludes_file = "$dest_dir/excludes.txt";
my $current_dir = "current";
my $incremental_dir = "inc";
my $last_backup_file = "$dest_dir/last-backup.txt";
my $one_day = 60*60*24;	# one day in seconds
my $today_in_seconds = time();
my $lock = "/tmp/rsync-backup";
my $max_incrementals = 3;
my $previous_backup_date;

my $LS = "/bin/ls";
my $DATE = "/bin/date";
my $RSYNC = "/usr/bin/rsync";
my $RM = "/bin/rm";

print_time("starting");
check_for_disk($dest_dir);
try_lock_file($lock);
handle_signals();
write_lock_file($lock);
remove_old_incrementals($dest_dir, $max_incrementals);
$previous_backup_date = get_last_backup_date($last_backup_file);
run_rsync($src_dir, $dest_dir, $excludes_file, $incremental_dir, $previous_backup_date);
write_last_backup_date($last_backup_file, $today_in_seconds);
clean_lock_file($lock);
print_time("finished");


sub print_time {
	my $msg = shift;
	print "$msg " . localtime() . "\n";
}

# Don't bother doing a backup if the external hard drive isn't connected:
sub check_for_disk {
	my $disk_dir = shift;
	(-e $disk_dir) or die "No disk connected at $disk_dir\n";
}

# remove all but the last n incremental backups.
# TODO: make this smarter so it removes however many are required
# to provide adequate space for the backup
sub remove_old_incrementals {
	my ($dest, $n) = @_;
	my @dirs = split /\n/, run("$LS -t1 $dest/inc-* 2>/dev/null");
	for my $dir (@dirs[$n .. scalar @dirs]) {
		next unless $dir;
		# run("echo $RM -rf $dir");
		run("$RM -rf $dir");
	}
}

sub write_last_backup_date {
	my $filename = shift;
	my $today = shift;
	# TODO: error check date command
	my $date = run("$DATE +%Y-%m-%d");
	chomp $date;
	open DFILE, ">$filename" or die "Can't open $filename for writing: $!\n";
	print DFILE "$date\n";
	close DFILE;
}

sub get_last_backup_date {
	my $filename = shift;
	my $success = open DFILE, "<$filename";
	if ($success) {
		my $date = <DFILE>;
		chomp $date;
		close DFILE;
		return $date;
	} else {
		print STDOUT "No 'last backup' file found at $filename\n";
		return 0;
	}
}

sub run {
	my $command = shift;
	print "$command\n";
	my $result = `$command`;
	return $result;
}

sub run_rsync {
	my ($src, $dest, $excludes, $inc_dir, $prev_backup_date) = @_;
	my $opts = '';

	if ($excludes) {
		$opts .= " --exclude-from='$excludes'";
	}
	if ($prev_backup_date) {
		$opts .= " --backup --backup-dir='../$inc_dir-$prev_backup_date'";
	}

	my $command = "$RSYNC -avz $opts '$src' '$dest/current'";
	print run($command);
	my $err = $? >> 8;
	return $err;
}

sub handle_signals {
	$SIG{'INT'} = 'clean_lock_file';
	$SIG{'__DIE__'} = 'clean_lock_file';
}

sub is_running {
	my $pid = shift;
	# TODO: use ps?
	return 0;
}

sub clean_lock_file {
	my $lock_file = shift || $lock;
	unlink $lock_file;
}

sub write_lock_file {
	# TODO: fork rsync and write its pid, too.
	my $lock_file = shift;
	open LF, ">$lock_file" or die "Can't open $lock_file for writing: $!\n";
	print LF "$$\n";
	close LF;
}

sub try_lock_file {
	my $lock_file = shift;
	if (-e $lock_file) {
		my @stats = stat($lock_file) or die "Can't stat $lock_file: $!\n";
		my $file_age = $today_in_seconds - $stats[9];
		if ($file_age >= $one_day) {
			open LF, "<$lock_file" or die "Can't open $lock_file for reading: $!\n";
			my $pid = <LF>;

			system("kill -9 $pid");
			if (is_running($pid)) {
				die "Can't kill $pid\n";
			}
			close LF;
			print STDERR "WARN: stale process found\n";
			# don't exit so we can go ahead an run the backup.
		} else {
			die "process already running\n";
		}
	} else {
		# no lock file means we can run; do nothing here.
	}
}


