#!/usr/bin/perl -w
#
=head1 NAME

cbusterize - Creates the correct CasparBuster structure in your CMS environment

=head1 SYNOPSIS

cbusterize [options] [--source /path/to/file/to/CasparBusterize | --plugin PluginName]

 Options:
   --debug  |-d			debug mode
   --help   |-h			brief help message
   --man			full documentation
   --force   |-f			force copy of files, even if they exist
   --source |-s <file/dir>	directory or files to copy in the CasparBuster tree (',' separated if many)
   --plugin |-p <plugin name>	plugin defining what to copy in the CasparBuster tree (',' separated if many)
   --machine|-m <machine>	machine to consider in the subtree

=head1 OPTIONS

=over 4

=item B<--debug>

Enter debug mode. This will print what would be done. No commands are executed,
so this is safe to use when testing.

=item B<--help>

Print a brief help message and exits.

=item B<--man>

Prints the manual page and exits.

=item B<--machine> I<machine name>

Specify the machine to consider when dealing with the CasparBuster structure. 
The file will be taken from this machine, and a subdirectory named after the machine 
will be used under the basedir to host the directory structure to manage

=item B<--source> I<path>

Specify the path to the source file or directory to manage with CasparBuster. Multiple paths can be specified separated by ','.

=item B<--plugin> I<name>

Specify the name of the plugin to manage with CasparBuster. Multiple plugins can be specified separated by ','.
A plugin defines a set of files (with their mode and owner), a set of directories (with their mode and owner) and a set of scripts to launch once the files are copied remotely.

=back

=head1 DESCRIPTION

Creates a directory under the machine dir passed as parameter in working 
directory or directory passed as parameter named like the last path
element of the parameter, and creates the standard CasparBuster setup 
that refers to the parameter path in the new directory, and configuration 
files when possible. It also copies the original config file into the new dir.
Is reasonably picky about path names, tries to avoid common errors.

Schema looks like:

Base dir
   |
   |- machine1 (optional)
   |     |
   |     |-- conf dir1
   |     |       |
   |     |       |- conf file 1
   |   [...]    [...]
   | 
   |- machine2 (optional)
   |     |
   |     |-- conf dir2
   |     |       |
   |     |       |- conf file 2
   |   [...]    [...]

Use of machines require use of option -m

=head1 EXAMPLES

	# this will create the appropriate CasparBuster environment
	# under the base ~/prj/musique-ancienne.org directory (Cf cbbasedir in cb.conf)
	# containing the directory victoria2 for this machine
	# under which it will copy the required structure if needed (/etc/ssh)
	# to finaly put a copy of the file sshd_conf in it from the victoria2 machine

	cbusterize -m victoria2 -s /etc/ssh/sshd_config

=head1 AUTHOR

=over 4

Bruno Cornec, http://brunocornec.wordpress.com

=back

=head1 LICENSE

Copyright (C) 2012  Bruno Cornec <bruno@project-builder.org>
Released under the GPLv2 or the Artistic license at your will.

=cut
use strict;
use CasparBuster::Version;
use CasparBuster::Env;
use CasparBuster::Plugin;
use CasparBuster::SSH;
use File::Basename;
use File::Path;
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
use Net::SSH2;
use Cwd;
use Archive::Tar;
use Carp;
use ProjectBuilder::Base;
use ProjectBuilder::Conf;
use ProjectBuilder::VCS;

# settings
my $debug = 0;
my $help = undef;
my $man = undef;
my $source = undef;
my $machine = undef;
my $plugin = undef;
my $quiet = undef;
my $force = undef;
my $log = undef;
my $LOG = undef;
my $ssh2 = undef;
my $chan = undef;

my ($cbver,$cbrev) = cb_version_init();
my $appname = "cb";
$ENV{'PBPROJ'} = $appname;

# Initialize the syntax string
pb_syntax_init("$appname (aka CasparBuster) Version $cbver-$cbrev\n");

# parse command-line options
GetOptions(
	'machine|m=s' => \$machine,
	'debug|d+'    => \$debug,
	'help|h'      => \$help,
	'quiet|q'     => \$quiet,
	'force|f'     => \$force,
	'man'         => \$man,
	'logfile|l=s' => \$log,
	'source|s=s'  => \$source,
	'plugin|p=s'  => \$plugin,
) || pb_syntax(-1,0);

if (defined $help) {
	pb_syntax(0,1);
}
if (defined $man) {
	pb_syntax(0,2);
}
if (defined $quiet) {
	$debug=-1;
}
if (defined $log) {
	open(LOG,"> $log") || die "Unable to log to $log: $!";
	$LOG = \*LOG;
	$debug = 0  if ($debug == -1);
}

$pbdebug = $debug;
pb_log_init($debug, $LOG);
pb_temp_init();
pb_log(1,"Starting cbusterize\n");

# Get conf file in context
pb_conf_init($appname);
# The personal one if there is such
pb_conf_add("$ENV{'HOME'}/.cbrc") if (-f "$ENV{'HOME'}/.cbrc");
# The system one
pb_conf_add(cb_env_conffile());

# Get configuration parameters
my %cb;
my $cb = \%cb;
($cb->{'basedir'},$cb->{'cms'}) = pb_conf_get("cbbasedir","cbcms");
pb_log(2,"%cb: ",Dumper($cb));

# Check for mandatory params
pod2usage("Error: --source or --plugin is a mandatory argument\n") if ((not defined $source) && (not defined $plugin));
pod2usage("Error: --machine is a mandatory argument\n") if (not defined $machine);

if (defined $plugin) {
	# Load plugins
	cb_plugin_load();
}

my $basedir = $cb->{'basedir'}->{$appname};
eval { $basedir =~ s/(\$ENV.+\})/$1/eeg };

pb_log(1, "DEBUG MODE, not doing anything, just printing\nDEBUG: basedir = $basedir\n");
pb_log(1, "DEBUG: source = $source\n") if (defined $source);
pb_log(1, "DEBUG: machine = $machine\n");

# Use potentially a remote account if defined
my $account = undef;
my $remote = undef;
($account) = pb_conf_get_if("cbaccount");
$remote = $account->{$machine} if ((defined $account) && (defined $account->{$machine}));
pb_log(1, "DEBUG: remote account1 = $remote\n") if (defined $remote);
$remote = getpwuid($<) if (not defined $remote);
pb_log(1, "DEBUG: remote account2 = $remote\n");

$ssh2 = cb_ssh_init($remote,$machine,$debug) if (not defined $ssh2);

# Create basedir if it doesn't exist
if (not -d $basedir) {
	if ($debug) {
		pb_log(1, "DEBUG: Creating recursively directory $basedir\n");
	} else {
		pb_mkdir_p($basedir) || die "Unable to recursively create $basedir: $!";
	}
}

if (defined $source) {
	foreach my $f (split(/,/,$source)) {	
		cb_busterize($f,"true");
	}
}

# Now handle plugins if any
my $cbp = ();

if (defined $plugin) {
	foreach my $p (split(/,/,$plugin)) {	
		pb_log(1,"Getting context for plugin $p\n");
		$cbp = cb_plugin_get($p,$cbp,$remote,$machine,$debug,$ssh2);
		pb_log(2,"cbp: ".Dumper($cbp)."\n");
		foreach my $k (keys %{$cbp->{$plugin}->{'dirsandfiles'}}) {
			cb_busterize($k,"true");
		}
		foreach my $k ((keys %{$cbp->{$plugin}->{'dirs'}}),(keys %{$cbp->{$plugin}->{'files'}})) {
			cb_busterize($k,"false");
		}
	}
}

cb_ssh_close($ssh2);

sub cb_busterize {

my $source = shift;
my $recur = shift;

pb_log(2,"Entering cb_busterize source: $source\n");
# Is the source a file or a dir ? Split the source parameter in 2
my $srcdir = undef;
my $srcfile = undef;
my $realsrc = "";
my $type = "";

$chan = $ssh2->channel();
pb_log(3,"DEBUG: SSH2 chan called\n");
confess "Unable to create channel for $remote\@$machine: $!" if (not defined $chan);
if ($debug) {
	pb_log(1,"DEBUG: launching a shell via Net:SSH2 ($remote\@$machine)\n");
}
confess "Unable to launch remote shell through Net:SSH2 ($remote\@$machine)" if (not $chan->shell());
pb_log(3,"DEBUG: SSH2 shell called\n");

pb_log(2,"DEBUG: Calling readlink -f $source\n");
print $chan "readlink -f $source\n";
while (<$chan>) {
	$realsrc = $_;
	chomp($realsrc);
	pb_log(3,"DEBUG: Found $realsrc");
}
pb_log(2,"DEBUG: Found realsrc = $realsrc\n");
print $chan "LANGUAGE=C stat -c '%F' $realsrc\n";
pb_log(2,"DEBUG: Calling LANGUAGE=C stat -c '%F' $realsrc\n");
while (<$chan>) {
	$type = $_;
	chomp($type);
	pb_log(3,"DEBUG: Found $type");
}
pb_log(2,"DEBUG: Found type = $type\n");
if ($type =~ /directory/) {
	$srcdir = $source;
	pb_log(1,"DEBUG: Found remote dir = $source\n");
} else {
	# File or something else potentially
	$srcdir = dirname($source);
	$srcfile = basename($source);
	pb_log(1,"DEBUG: Found remote file = $source\n");
}

pb_log(1,"DEBUG: Found srcdir = $srcdir\n");
if (defined $srcfile) {
	pb_log(1,"DEBUG: Found srcfile = $srcfile\n");
} else {
	pb_log(1,"DEBUG: Found no srcfile\n");
}

my $scheme = $cb->{'cms'}->{$appname};

# Deduce the target directory from the local structure and the source
my $target = "$basedir/$machine/$srcdir";

# If both source and target are dirs, then copy into the parent of the target
#$target = basename($target) if ((not defined $srcfile) && (-d $target));

# Create target if it doesn't exist
if (not -d $target) {
	if ($debug) {
		pb_log(1,"DEBUG: Creating recursively directory $target\n");
	} else {
		pb_mkdir_p($target) || confess "Unable to recursively create $target: $!";
	}
	# Add all the dirs in it to VCS (in reverse order) if not already in it
	my $tdir = $target;
	my @tab = ();
	while ($tdir ne $basedir) {
		push(@tab,$tdir);
		$tdir = dirname($tdir);
		pb_log(3,"tdir is now $tdir\n");
	}
	if (not $debug) {
		pb_vcs_add_if_not_in($scheme,reverse(@tab));
	}
	pb_log(0,"INFO: Added to your $scheme system the dirs: ".join(' ',reverse(@tab))."\n");
}

if (defined $srcfile) {
	# File case
	if ((! -f "$target/$srcfile") || (defined $force)) {
		$ssh2->scp_put($source,"$target/$srcfile");
		#cb_ssh_do($remote,$machine,$source,"$basedir/$machine",$debug,$ssh2,$chan);
		
		if (not $debug) {
			pb_vcs_add_if_not_in($scheme,"$target/$srcfile");
		}
		pb_log(0,"INFO: Created $target/$srcfile and added it to your $scheme system if needed\n");
	} else {
		pb_log(0,"INFO: File $target/$srcfile already there. Will not add without forcing with -f\n");
	}
} else {
	# Directory case
	if ($recur eq "true") {
		# with files in it
		# TODO: if sudo asks a passwd it won't work.
		my $cmd = "sudo tar cvhf /tmp/cb.$$.tar $source\n"; 
		if ($debug) {
			pb_log(1,"DEBUG: launching through Net:SSH2 ($remote\@$machine) $cmd");
		}
		print $chan "$cmd";
		pb_log(1,"DEBUG: LINE : $_") while <$chan>;
		$cmd = "sudo chmod 600 /tmp/cb.$$.tar\n"; 
		if ($debug) {
			pb_log(1,"DEBUG: launching through Net:SSH2 ($remote\@$machine) $cmd");
		}
		print $chan "$cmd";
		pb_log(1,"DEBUG: LINE : $_") while <$chan>;
		$cmd = "sudo chown $remote /tmp/cb.$$.tar\n"; 
		if ($debug) {
			pb_log(1,"DEBUG: launching through Net:SSH2 ($remote\@$machine) $cmd");
		}
		print $chan "$cmd";
		pb_log(1,"DEBUG: LINE : $_") while <$chan>;

		if ($debug) {
			pb_log(1,"DEBUG: gettting through Net:SSH2 ($remote\@$machine) /tmp/cb.$$.tar\n");
		}

		$ssh2->scp_get("/tmp/cb.$$.tar","/tmp/cb.$$.tar");

		if ($debug) {
			pb_log(1,"DEBUG: erasing through Net:SSH2 ($remote\@$machine) /tmp/cb.$$.tar\n");
		} else {
			print $chan "sudo rm -f /tmp/cb.$$.tar\n";
		}
		my $tar = Archive::Tar->new("/tmp/cb.$$.tar");
		$tar->setcwd("$basedir/$machine");
		if ($debug) {
			pb_log(1,"DEBUG: Extracting /tmp/cb.$$.tar\n");
			foreach my $f ($tar->list_files()) {
				pb_log(1,"DEBUG: $f\n");
			}
		} else {
			foreach my $f ($tar->get_files) {
				my $sname = $f->name;
				$sname =~ s|/$||;
				next if ($srcdir =~ /$sname/);
				if ($f->is_file) {
					#pb_log(0,"Extracting and Adding ".$f->name." to your $scheme system\n");
					$tar->extract($f);
					pb_vcs_add_if_not_in($scheme,"$basedir/$machine/".$f->name);
				} elsif ($f->is_dir) {
					pb_log(0,"Calling recursively cbusterize on ".$f->name."\n");
					cb_busterize("/".$f->name,$recur);
				} else {
					pb_log(0,"File type $f->type for $f->name is not handled yet\n");
				}
			}
		}
		if ($debug) {
			pb_log(1,"DEBUG: please cleanup manually /tmp/cb.$$.tar\n");
		} else {
			$tar->clear;
			unlink("/tmp/cb.$$.tar");
		}
	} else {
		# Only deal with that dir, nothing below, so just created locally
		if ($debug) {
			pb_log(1,"DEBUG: Creatng and Adding $target to your $scheme system\n");
		} else {
			pb_mkdir_p("$target");
			pb_vcs_add_if_not_in($scheme,"$target");
		}
	}
}
$chan->close();

pb_log(2,"Exiting cb_busterize\n");
}


