#!/usr/bin/perl
# 
# This runs on striker dashboards and syncs files under /mnt/shared on all known systems. It reaches out and 
# pulls over any files under /mnt/shared/files/ to the same on the local system. It then pushes files out to
# all members of the same Anvil!. 
# 
# If this is called with a job-uuid, file-specific tasks will be handled, like moving files uploaded over a
# browser or deleting / purging a file.
# 
# NOTE: This file is NOT responsible for sync'ing definition files! That is handles in scan-server.
# 
# TODO:
# - Handle deleting files by user input, or if a given file that was on an Anvil! has been removed for both
#   nodes and DR, where applicable.
# - 
 
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;

my $THIS_FILE           =  ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory   =  ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
	$running_directory =~ s/^\./$ENV{PWD}/;
}

# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
$| = 1;

my $anvil = Anvil::Tools->new();

# Get switches
$anvil->Get->switches({list => ["job-uuid"], man => $THIS_FILE});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0115", variables => { program => $THIS_FILE }});

# Connect to the database(s).
$anvil->Database->connect;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0132"});

# If we have a job-uuid, process it.
if ($anvil->data->{switches}{'job-uuid'})
{
	# Load the job data.
	$anvil->Database->get_anvils({debug => 3});
	$anvil->Job->get_job_details({debug => 3});
	$anvil->Job->clear({debug => 3});
	
	$anvil->data->{sys}{progress} = 1;
	if ($anvil->data->{jobs}{job_name} eq "storage::move_incoming")
	{
		process_incoming_file($anvil);
	}
	if ($anvil->data->{jobs}{job_name} eq "storage::pull_file")
	{
		process_pull_file($anvil);
	}
	if ($anvil->data->{jobs}{job_name} eq "storage::purge")
	{
		process_purge_file($anvil);
	}
	if ($anvil->data->{jobs}{job_name} eq "storage::rename")
	{
		process_rename_file($anvil);
	}
	if ($anvil->data->{jobs}{job_name} eq "storage::check_mode")
	{
		process_file_mode($anvil);
	}
}

$anvil->nice_exit({exit_code => 0});


#############################################################################################################
# Functions                                                                                                 #
#############################################################################################################

sub process_file_mode
{
	my ($anvil) = @_;
	
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress},
		message  => "message_0205", 
	});
	
	# We have to include deleted because purges will already have the file flagged as deleted.
	$anvil->Database->get_files({debug => 3, include_deleted => 1});
	$anvil->Database->get_file_locations({debug => 3});
	
	my $file_uuid = ($anvil->data->{jobs}{job_data} =~ /file_uuid=(.*)$/)[0];
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_uuid => $file_uuid }});
	
	if (not $file_uuid)
	{
		# Can't do anything, file wasn't parsed.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0185,!!job_uuid!".$anvil->data->{switches}{'job-uuid'}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0185", variables => { job_uuid => $anvil->data->{switches}{'job-uuid'} }});
		$anvil->nice_exit({exit_code => 1});
	}
	elsif (not exists $anvil->data->{files}{file_uuid}{$file_uuid})
	{
		# File UUID doesn't appear to be valid.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0186,!!file_uuid!".$file_uuid."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0186", variables => { file_uuid => $file_uuid }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	my $file_name      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_name};
	my $file_directory = $anvil->data->{files}{file_uuid}{$file_uuid}{file_directory};
	my $file_path      = $file_directory."/".$file_name;
	my $file_type      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_type};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		's1:file_name'      => $file_name, 
		's2:file_directory' => $file_directory, 
		's3:file_path'      => $file_path, 
		's4:file_type'      => $file_type, 
	}});
	
	# From here, we'll unlink if it exists. If it doesn't exist, we still exit sucessfully.
	if (-e $file_path)
	{
		# Change the mode, if need it.
		my $new_mode = "a-x";
		if ($file_type eq "script")
		{
			$new_mode = "a+x";
		}
		$anvil->Storage->change_mode({
			path => $file_path, 
			mode => $new_mode,
		});
		$anvil->Job->update_progress({
			progress => 100,
			message  => "job_0145,!!file_path!".$file_path."!!,!!new_mode!".$new_mode."!!", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0145", variables => { 
			file_path => $file_path,
			new_mode  => $new_mode, 
		}});
		$anvil->nice_exit({exit_code => 0});
	}
	else
	{
		# Doesn't exist, nothing to do.
		$anvil->Job->update_progress({
			progress => 100,
			message  => "job_0146,!!file_path!".$file_path."!!", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0146", variables => { file_path => $file_path }});
		$anvil->nice_exit({exit_code => 0});
	}
	
	return(0);
}

sub process_rename_file
{
	my ($anvil) = @_;
	
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress},
		message  => "message_0204", 
	});

	$anvil->Database->get_files({debug => 3});
	$anvil->Database->get_file_locations({debug => 3});
	
	my $file_uuid = "";
	my $old_name  = "";
	my $new_name  = "";
	foreach my $line (split/\n/, $anvil->data->{jobs}{job_data})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
		if ($line =~ /file_uuid=(.*)$/)
		{
			$file_uuid = $1;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_uuid => $file_uuid }});
		}
		if ($line =~ /old_name=(.*)$/)
		{
			$old_name = $1;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { old_name => $old_name }});
		}
		if ($line =~ /new_name=(.*)$/)
		{
			$new_name = $1;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { new_name => $new_name }});
		}
	}
	
	if (not $file_uuid)
	{
		# Can't do anything, file wasn't parsed.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0181,!!job_uuid!".$anvil->data->{switches}{'job-uuid'}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0181", variables => { job_uuid => $anvil->data->{switches}{'job-uuid'} }});
		$anvil->nice_exit({exit_code => 1});
	}
	elsif (not exists $anvil->data->{files}{file_uuid}{$file_uuid})
	{
		# File UUID doesn't appear to be valid.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0182,!!file_uuid!".$file_uuid."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0182", variables => { file_uuid => $file_uuid }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	my $file_name      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_name};
	my $file_directory = $anvil->data->{files}{file_uuid}{$file_uuid}{file_directory};
	my $old_file_path  = $file_directory."/".$old_name;
	my $new_file_path  = $file_directory."/".$new_name;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		's1:file_name'      => $file_name, 
		's2:file_directory' => $file_directory, 
		's3:old_file_path'  => $old_file_path, 
		's4:new_file_path'  => $new_file_path, 
	}});
	
	if (not $new_name)
	{
		# Can't do anything, file wasn't parsed.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0183,!!file_name!".$new_file_path."!!,!!job_uuid!".$anvil->data->{switches}{'job-uuid'}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0183", variables => { 
			file_name => $new_file_path, 
			job_uuid  => $anvil->data->{switches}{'job-uuid'},
		}});
		$anvil->nice_exit({exit_code => 1});
	}
	
	# If the file doesn't exist, there's nothing to do (and that's OK).
	if (-e $old_file_path)
	{
		# Move the file.
		$anvil->Job->update_progress({
			progress => 50,
			message  => "job_0141,!!old_file!".$old_file_path."!!,!!new_file!".$new_file_path."!!", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0141", variables => { 
			old_file => $old_file_path, 
			new_file => $new_file_path, 
		}});
		$anvil->Storage->move_file({
			source_file => $old_file_path, 
			target_file => $new_file_path,
		});
		
		if ((not -e $old_file_path) && (-e $new_file_path))
		{
			# Success!
			$anvil->Job->update_progress({
				progress => 100,
				message  => "job_0142", 
			});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0142"});
			$anvil->nice_exit({exit_code => 0});
		}
		elsif ((-e $old_file_path) && (not -e $new_file_path))
		{
			# Move failed
			$anvil->Job->update_progress({
				progress   => 1,
				message    => "error_0184", 
				job_status => "failed", 
			});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0184"});
			sleep 60;
			$anvil->nice_exit({exit_code => 1});
		}
	}
	else
	{
		# Nothing to do
		$anvil->Job->update_progress({
			progress => 100,
			message  => "job_0140,!!file_path!".$old_file_path."!!", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0140", variables => { file_path => $old_file_path }});
		$anvil->nice_exit({exit_code => 0});
	}
	
	return(0);
}

sub process_purge_file
{
	my ($anvil) = @_;
	
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress},
		message  => "message_0203", 
	});
	
	# We have to include deleted because purges will already have the file flagged as deleted.
	$anvil->Database->get_files({debug => 3, include_deleted => 1});
	$anvil->Database->get_file_locations({debug => 3});
	
	my $file_uuid = ($anvil->data->{jobs}{job_data} =~ /file_uuid=(.*)$/)[0];
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_uuid => $file_uuid }});
	
	if (not $file_uuid)
	{
		# Can't do anything, file wasn't parsed.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0178,!!job_uuid!".$anvil->data->{switches}{'job-uuid'}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0178", variables => { job_uuid => $anvil->data->{switches}{'job-uuid'} }});
		$anvil->nice_exit({exit_code => 1});
	}
	elsif (not exists $anvil->data->{files}{file_uuid}{$file_uuid})
	{
		# File UUID doesn't appear to be valid.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0179,!!file_uuid!".$file_uuid."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0179", variables => { file_uuid => $file_uuid }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	my $file_name      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_name};
	my $file_directory = $anvil->data->{files}{file_uuid}{$file_uuid}{file_directory};
	my $file_path      = $file_directory."/".$file_name;
	my $file_type      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_type};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		's1:file_name'      => $file_name, 
		's2:file_directory' => $file_directory, 
		's3:file_path'      => $file_path, 
		's4:file_type'      => $file_type, 
	}});
	
	# From here, we'll unlink if it exists. If it doesn't exist, we still exit sucessfully.
	if (-e $file_path)
	{
		# Remove it
		unlink $file_path;
		if (-e $file_path)
		{
			# Failed.
			my $error = $!;
			$anvil->Job->update_progress({
				progress   => 100,
				message    => "error_0180,!!file_path!".$file_path."!!,!!error!".$error."!!", 
				job_status => "failed", 
			});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0180", variables => { 
				file_path => $file_path, 
				error     => $error, 
			}});
			$anvil->nice_exit({exit_code => 1});
		}
		else
		{
			# Success!
			$anvil->Job->update_progress({
				progress => 100,
				message  => "job_0134,!!file_path!".$file_path."!!", 
			});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0134", variables => { file_path => $file_path }});
			$anvil->nice_exit({exit_code => 0});
		}
	}
	else
	{
		# Doesn't exist, nothing to do.
		$anvil->Job->update_progress({
			progress => 100,
			message  => "job_0135,!!file_path!".$file_path."!!", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "job_0135", variables => { file_path => $file_path }});
		$anvil->nice_exit({exit_code => 0});
	}
	
	return(0);
}

sub process_incoming_file
{
	my ($anvil) = @_;
	
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress},
		message  => "message_0191", 
	});
	
	my $file = ($anvil->data->{jobs}{job_data} =~ /file=(.*)$/)[0];
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file => $file }});
	if (not $file)
	{
		# Can't do anything, file wasn't parsed.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0170,!!job_uuid!".$anvil->data->{switches}{'job-uuid'}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0170", variables => { job_uuid => $anvil->data->{switches}{'job-uuid'} }});
		
		$anvil->nice_exit({exit_code => 1});
	}
	elsif (not -e $file)
	{
		# Can't do anything, file doesn't exist
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0171,!!file!".$file."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0171", variables => { file => $file }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	# Make sure the file actually done uploading.
	$anvil->data->{sys}{progress} = 5;
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress}, 
		message  => "message_0309,!!file!".$file."!!", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0309", variables => { file => $file }});
	$anvil->Storage->_wait_if_changing({
		debug => 2, 
		file  => $file,
	});
	
	# Move it over to files.
	$anvil->data->{sys}{progress} = 5;
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress}, 
		message  => "message_0192,!!file!".$file."!!", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0192", variables => { file => $file }});
	$anvil->Storage->move_file({
		debug       => 2,
		overwrite   => 1, 
		source_file => $file,
		target_file => $anvil->data->{path}{directories}{shared}{files}."/", 
	});
	
	my $file_name   = ($file =~ /\/.*\/(.*?)$/)[0];
	my $target_file = $anvil->data->{path}{directories}{shared}{files}."/".$file_name;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		file_name   => $file_name,
		target_file => $target_file, 
	}});
	
	if (not -e $target_file)
	{
		# Failed to move.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0172,!!file!".$file."!!,!!target_directory!".$anvil->data->{path}{directories}{shared}{files}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0171", variables => { file => $file }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	# Change the owner as it'll be striker-ui-api, which won't be a valid users on anvil members.
	$anvil->Storage->change_owner({
		debug => 2, 
		path  => $target_file, 
		user  => "root", 
		group => "root",
	});
	
	# Calculate the md5sum.
	$anvil->data->{sys}{progress} = 20;
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress}, 
		message  => "message_0193", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0193"});
	
	my ($string, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{md5sum}." ".$target_file});
	my $md5sum = ($string =~ /^(.*?)\s/)[0];
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		string      => $string, 
		md5sum      => $md5sum,
		return_code => $return_code,
	}});
	
	# Store the file details!
	$anvil->data->{sys}{progress} = 30;
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress}, 
		message  => "message_0194,!!md5sum!".$md5sum."!!", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0194", variables => { md5sum => $md5sum }});
	
	$anvil->Storage->get_file_stats({
		debug     => 2,
		file_path => $target_file, 
	});
	
	my $file_mimetype = $anvil->data->{file_stat}{$target_file}{mimetype};
	my $file_size     = $anvil->data->{file_stat}{$target_file}{size};
	my $file_mtime    = $anvil->data->{file_stat}{$target_file}{modified_time}; 
	my $executable    = -x $target_file ? 1 : 0;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		file_mimetype => $file_mimetype, 
		file_size     => $file_size." (".$anvil->Convert->bytes_to_human_readable({"bytes" => $file_size}).")",
		file_mtime    => $file_mtime,
		executable    => $executable, 
	}});
	
	# This is the file's type/purpose. The expected values are 'iso', 'rpm', 'script', 'disk-image', or 
	# 'other'. If set to 'DELETED', the file will be removed from disk.
	my $file_type = "other";
	if ($file_mimetype =~ /cd-image/)
	{
		$file_type = "iso";
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_type => $file_type }});
	}
	# This will need to be expanded over time
	elsif (($executable) or ($file_mimetype =~ /perl/) or ($file_mimetype =~ /python/))
	{
		$file_type = "script";
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_type => $file_type }});
		
		# Change the mode to be executable
		$anvil->Storage->change_mode({
			debug => 2, 
			path  => $target_file, 
			mode  => "0755",
		});
	}
	elsif ($file_mimetype =~ /raw-disk-image/)
	{
		$file_type = "image";
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_type => $file_type }});
	}
	
	my $file_uuid = $anvil->Database->insert_or_update_files({
		debug          => 2,
		file_name      => $file_name, 
		file_directory => $anvil->data->{path}{directories}{shared}{files}, 
		file_size      => $file_size, 
		file_md5sum    => $md5sum, 
		file_type      => $file_type, 
		file_mtime     => $file_mtime, 
	});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_uuid => $file_uuid }});
	
	# Now copy this to our peers. We're going to do this serially so that we don't overwhelm the system,
	# Any hosts not currently online will have a job registered.
	$anvil->Storage->push_file({
		debug     => 2, 
		file      => $target_file,
		file_uuid => $file_uuid,
	});
	
	# Call track_files, it'll make sure the file_locations are setup.
	$anvil->Database->track_files({debug => 2});
	
	# Done!
	$anvil->Job->update_progress({
		progress => 100, 
		message  => "message_0197", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0197"});
	
	return(0);
}

# This pulls files from dashboards onto the running host.
sub process_pull_file
{
	my ($anvil) = @_;
	
	$anvil->Job->update_progress({
		progress => $anvil->data->{sys}{progress},
		message  => "message_0198", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0198" });
	
	# Collect some data
	$anvil->Database->get_anvils({debug => 3});
	$anvil->Database->get_files({debug => 3});
	$anvil->Database->get_file_locations({debug => 3});
	
	my $file_uuid  = ($anvil->data->{jobs}{job_data} =~ /file_uuid=(.*)$/)[0];
	my $anvil_uuid = $anvil->Cluster->get_anvil_uuid({debug => 2});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		anvil_uuid => $anvil_uuid,
		file_uuid  => $file_uuid, 
	}});
	
	if (not $file_uuid)
	{
		# Can't do anything, file wasn't parsed.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0173,!!job_uuid!".$anvil->data->{switches}{'job-uuid'}."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0173", variables => { job_uuid => $anvil->data->{switches}{'job-uuid'} }});
		$anvil->nice_exit({exit_code => 1});
	}
	elsif (not exists $anvil->data->{files}{file_uuid}{$file_uuid})
	{
		# File UUID doesn't appear to be valid.
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0174,!!file_uuid!".$file_uuid."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0174", variables => { file_uuid => $file_uuid }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	my $file_name      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_name};
	my $file_directory = $anvil->data->{files}{file_uuid}{$file_uuid}{file_directory};
	my $file_path      = $file_directory."/".$file_name;
	my $file_size      = $anvil->data->{files}{file_uuid}{$file_uuid}{file_size};
	my $file_md5sum    = $anvil->data->{files}{file_uuid}{$file_uuid}{file_md5sum};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		's1:file_name'      => $file_name, 
		's2:file_directory' => $file_directory, 
		's3:file_path'      => $file_path, 
		's4:file_size'      => $file_size." (".$anvil->Convert->bytes_to_human_readable({"bytes" => $file_size}).")",
		's5:file_md5sum'    => $file_md5sum, 
	}});
	
	if (not $anvil_uuid)
	{
		# Uhhh...
		$anvil->Job->update_progress({
			progress   => 100,
			message    => "error_0175,!!file!".$file_path."!!", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0175", variables => { file => $file_path }});
		$anvil->nice_exit({exit_code => 1});
	}
	
	# How many Strikers are up and have the file we're looking for?
	$anvil->data->{target_strikers} = [];
	foreach my $host_uuid (keys %{$anvil->data->{database}})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host_uuid => $host_uuid }});
		
		my $host_name = $anvil->Get->host_name_from_uuid({debug => 2, host_uuid => $host_uuid});
		my $target    = $anvil->data->{database}{$host_uuid}{host};
		my $password  = $anvil->data->{database}{$host_uuid}{password};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			host_name => $host_name, 
			target    => $target,
			password  => $anvil->Log->is_secure($password), 
		}});
		
		# If the file exists, the return code is '0'. If the file isn't found, '1' is returned.
		# When found, the size in bytes followed by the file name is returned.
		my $shell_call = $anvil->data->{path}{exe}{wc}." -c ".$file_path;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
		my ($output, $error, $return_code) = $anvil->Remote->call({
			debug       => 3, 
			shell_call  => $shell_call, 
			target      => $target,
			password    => $password,
			remote_user => "root", 
		});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			output => $output, 
			error  => $error,
			output => $output,
		}});
		if ($output =~ /^(\d+)\s+$file_path$/)
		{
			my $size_on_peer = $1;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				size_on_peer => $size_on_peer." (".$anvil->Convert->bytes_to_human_readable({"bytes" => $size_on_peer}).")"
			}});
			
			# For now, we only do a size check as md5sums can take a long time.
			if ($size_on_peer eq $file_size)
			{
				# We can pull from this striker!
				push @{$anvil->data->{target_strikers}}, {
					host_name => $host_name,
					host_uuid => $host_uuid,
					target    => $target,
					password  => $password,
				};
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					host_name => $host_name,
					host_uuid => $host_uuid,
					target    => $target,
					password  => $anvil->Log->is_secure($password),
				}});
			}
			else
			{
				# The file doesn't exist or we couldn't contact the Striker, so we'll skip 
				# it.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0576", variables => { 
					file_path => $file_path,
					host_name => $host_name, 
				}});
			}
		}
	}
	
	my $anvil_node1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node1_host_uuid};
	my $anvil_node2_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node2_host_uuid};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		anvil_node1_host_uuid => $anvil_node1_host_uuid, 
		anvil_node2_host_uuid => $anvil_node2_host_uuid, 
	}});
	
	# Where we pull from will depend on which machine we are and how many strikers we have. If we have
	# one Anvil!, sub-node 1 and 2 download at the same time. If there are two strikers, Each node will
	# download from a different striker (if possible).
	my $i_am = "node1";
	if ($anvil->Get->host_uuid eq $anvil_node2_host_uuid)
	{
		$i_am = "node2";
	}
	
	my $striker_count = @{$anvil->data->{target_strikers}};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		i_am          => $i_am, 
		striker_count => $striker_count, 
	}});
	if (not $striker_count)
	{
		# No available Strikers.
		$anvil->Job->update_progress({
			progress   => 1,
			message    => "warning_0072,!!file_path!".$file_path."!!", 
			job_status => "failed", 
		});
		sleep 60;
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "warning_0072", variables => { file_path => $file_path }});
		$anvil->nice_exit({exit_code => 2});
	}
	
	my $use = 0;
	if ($striker_count >= 3)
	{
		if ($i_am eq "node1")    { $use = 0; }
		elsif ($i_am eq "node2") { $use = 1; }
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'use' => $use }});
	}
	elsif ($striker_count == 2)
	{
		# Two strikers, each node will use the other Striker
		if ($i_am eq "node1")    { $use = 0; }
		elsif ($i_am eq "node2") { $use = 1; }
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'use' => $use }});
	}
	elsif ($striker_count == 1)
	{
		# Only 1 Striker
		if ($i_am eq "node1")    { $use = 0; }
		elsif ($i_am eq "node2") { $use = 0; }
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'use' => $use }});
	}
	
	# Now proceed with the download!
	my $target_host_name = $anvil->data->{target_strikers}->[$use]->{host_name};
	my $target_host_uuid = $anvil->data->{target_strikers}->[$use]->{host_uuid};
	my $target_host      = $anvil->data->{target_strikers}->[$use]->{target};
	my $target_password  = $anvil->data->{target_strikers}->[$use]->{password};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		target_host_name => $target_host_name,
		target_host_uuid => $target_host_uuid, 
		target_host      => $target_host, 
		target_password  => $anvil->Log->is_secure($target_password),
	}});
	
	# Rsync the file.
	my $say_source_file  = "root\@".$target_host.":".$file_path;
	my $target_directory = $file_directory."/";
	$anvil->Job->update_progress({
		progress => 50, 
		message  => "message_0200,!!source_file!".$say_source_file."!!,!!target_directory!".$target_directory."!!", 
	});
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0200", variables => { 
		source_file      => $say_source_file, 
		target_directory => $target_directory, 
	}});
	
	$anvil->Storage->rsync({
		debug       => 2, 
		source      => $say_source_file, 
		destination => $target_directory, 
		try_again   => 1, 
	});
	
	if (-e $file_path)
	{
		# Calculate the md5sum.
		$anvil->Job->update_progress({
			progress => 75, 
			message  => "message_0201", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0201"});
		
		my $local_md5sum = $anvil->Get->md5sum({file => $file_path});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { local_md5sum => $local_md5sum }});
		
		if ($file_md5sum eq $local_md5sum)
		{
			$anvil->Job->update_progress({
				progress => 100,
				message  => "message_0202", 
			});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "message_0202"});
			$anvil->nice_exit({exit_code => 0});
		}
		else
		{
			# Unlink the file. The perioding sync call can try again later.
			unlink $file_path;
			$anvil->Job->update_progress({
				progress   => 1,
				message    => "error_0176,!!local_md5sum!".$local_md5sum."!!,!!file_md5sum!".$file_md5sum."!!", 
				job_status => "failed", 
			});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0176", variables => { 
				local_md5sum => $local_md5sum,
				file_md5sum  => $file_md5sum,
			}});
			sleep 60;
			$anvil->nice_exit({exit_code => 1});
		}
	}
	else
	{
		# Failed... 
		$anvil->Job->update_progress({
			progress   => 1,
			message    => "error_0177", 
			job_status => "failed", 
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => 'err', key => "error_0177" });
		sleep 60;
		$anvil->nice_exit({exit_code => 1});
	}
	
	return(0);
}

# This takes a host and job UUID and determines if we're still waiting on the target.
sub wait_on_host
{
	my ($anvil, $host_uuid, $job_uuid) = @_;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		host_uuid => $host_uuid." (".$anvil->Get->host_name_from_uuid({host_uuid => $host_uuid}).")", 
		job_uuid  => $job_uuid,
	}});
	
	my $waiting = 1;
	
	# Look up the job progress.
	my $query = "SELECT job_progress FROM jobs WHERE job_uuid = ".$anvil->Database->quote($job_uuid).";";
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
	
	my $progress = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { progress => $progress }});
	
	if ($progress == 100)
	{
		$waiting = 0;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { waiting => $waiting }});
	}
	else
	{
		# Can I ping the node?
		my $pinged = 0;
		foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$host_uuid}{interface}})
		{
			my $target_ip = $anvil->data->{network}{$host_uuid}{interface}{$interface}{ip};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				interface => $interface, 
				target_ip => $target_ip,
			}});
			
			($pinged, my $average_time) = $anvil->Network->ping({
				ping  => $target_ip, 
				count => 1,
			});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { pinged => $pinged }});
			
			last if $pinged;
		}
		
		if (not $pinged)
		{
			# Stop waiting, it looks to be offline.
			$waiting = 0;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { waiting => $waiting }});
		}
	}
	
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { waiting => $waiting }});
	return($waiting)
}
