#!/usr/bin/perl
use warnings;
use strict;

use autodie;
use POSIX;
use File::Slurp;
use IO::Select;
use Time::HiRes;
use Data::Dump qw(dump);
use English;
use Module::Refresh;

use lib 'lib';
use WarnColor;
use CloudStore::Couchbase;

my $dir   = $ENV{RSYNC_DIR}  || '/srv/cloudstore';
my $port  = $ENV{RSYNC_PORT} || 6501;
my $users = "users";
my $default_quota = $ENV{QUOTA} || 200 * 1024; # 200 Kb for test.sh

my $log_fifo = "$dir/var/$port.log";
my $pid_file = "$dir/var/$port.pid";
my $cfg_file = "$dir/var/$port.conf";

my $rsync = 'rsync';
$rsync = 'bin/rsync' if -x 'bin/rsync'; # use 3.1dev version!

my @transfer = qw(
timestamp:%t:timestamp
login:%m:text
port:$port:int
auth:%u:text
host:%h:text
pid:%p:int
perms:%B:text
itemize:%i:text
mtime:%M:timestamp
md5:%C:text
op:%o:text
size:%l:int
transfered:%b:int
file:%f:text
);

$transfer[2] = "port:$port:int"; # expand $port

my @transfer_names =          map { ( split(/:/,$_,3) )[0] } @transfer;
my $transfer_log   = join('|',map { ( split(/:/,$_,3) )[1] } @transfer );

if ( $ENV{SQL} ) {
	print "CREATE TABLE rsync_transfer (\n\t",
	join(",\n\t", map { my @m = split(/:/,$_,3); "$m[0] $m[2]" } @transfer),
	"\n);\n";
	exit 1;
}

my $store = CloudStore::Couchbase->new;

mkdir "$dir/var" if ! -e "$dir/var";

unlink $log_fifo if -f $log_fifo;
mkfifo $log_fifo, 0700 unless -p $log_fifo;

my $rsync_config = qq{

#uid = nobody
#gid = nogroup
#use chroot = yes
use chroot = no

#max connections = 4
lock file = $dir/var/$port.lock

#syslog facility = local5
log file  = $log_fifo

transfer logging = yes
log format = transfer-log:$transfer_log
max verbosity = 5

pid file  = $pid_file

# don't check secrets file permission (uid)
strict modes = no

pre-xfer exec = /srv/cloudstore/rsync-xfer-trigger.pl
post-xfer exec = /srv/cloudstore/rsync-xfer-trigger.pl

};

foreach my $path ( glob "$users/*" ) {

	my $login = $path;
	$login =~ s{^.+/([^/]+)$}{$1}; 

	if ( -d $path && -d "$path/blob" && -f "$path/secrets" ) {
		my @secrets = map { chomp; $_ } read_file "$path/secrets";
		my $auth_users = join(', ', map { s/:.+$//; $_ } @secrets );

		$rsync_config .= <<__RSYNC_MODULE__;

[$login]
	path = $dir/users/$login/blob
	auth users = $auth_users
	secrets file = $dir/users/$login/secrets
	read only = false

__RSYNC_MODULE__

		print "INFO: added $login = $auth_users\n";

		my $quota = $default_quota;
		$quota = read_file("$path/quota") * 1 if -e "$path/quota";

		$store->user_set({
			login => $login,
			path => $path,
			secrets => [ @secrets ],
			quota => $quota,
			port => $port,
		});

	} else {
		warn "skipped $login: $!";
	}

}

write_file $cfg_file, $rsync_config;
warn "created $cfg_file ", -s $cfg_file, " bytes\n";

sub rsync_running_pid {
	return unless -e $pid_file;
	my $pid = read_file $pid_file;
	chomp($pid);
	return $pid;
}

if ( my $pid = rsync_running_pid ) {
	if ( kill 0, $pid ) {
		warn "found rsync pid $pid\n";
=for kill-rsync
		kill 2, $pid;
		while ( -e $pid_file ) {
			warn "waiting for rsync to die...\n";
			sleep 1;
		}
		
		open(my $fifo, '<', $log_fifo);
		while ( kill 0, $pid ) {
			my $line = <$fifo>;
			warn "<<< $line\n";
		}

		kill 0, $pid && die "can't kill it!";
=cut
	} else {
		unlink $pid_file;
	}
}

use POSIX ":sys_wait_h";
sub REAPER {
	my $child;
	while ((my $waitedpid = waitpid(-1,WNOHANG)) > 0) {
		warn "reaped $waitedpid" . ($? ? " with exit $?" : '');
	}
	$SIG{CHLD} = \&REAPER;  # loathe SysV
}

$SIG{CHLD} = \&REAPER;


if ( ! -e $pid_file ) {
	my $exec = "$rsync --daemon --config $cfg_file --no-detach --port=$port";
	warn "START $exec\n";

	die "could not fork\n" unless defined(my $pid = fork);
	unless ($pid) {
		warn "start server with $exec\n";
		exec $exec || die $!;
	}

	warn "wait for pid file";
	while ( ! -e $pid_file ) {
		sleep 1;
	}
}

=for gearman
use Gearman::Client;
my $gearman = Gearman::Client->new;
$gearman->job_servers('127.0.0.1:4730');
=cut

while(1) {
	die "no rsync running" unless kill 0, rsync_running_pid;
	warn "waiting for log from $log_fifo\n";
	open(my $fifo, '<', $log_fifo);
	while( my $line = <$fifo> ) {
		Module::Refresh->refresh;
		chomp $line;
		warn $line, $/;

		if ( $line =~ /transfer-log:(.+)/ ) {
			my $transfer = $1;
			$transfer =~ s|(\d\d\d\d)/(\d\d)/(\d\d)[-\s](\d\d:\d\d:\d\d)|$1-$2-$3T$4|g;
			my ( $yyyy,$mm,$dd,undef,$login,undef ) = split( /[\-T\|]/, $transfer, 6 );
			my $host = $1 if $login =~ s/\+(.+)//;

			my $path = "users/$login/log";
			mkdir $path unless -d $path;
			$path .= "/$yyyy-$mm-$dd";
			my $new_log = ! -e $path;
			open( my $log, '>>', $path );
			print $log join('|',@transfer),"\n" if $new_log; # store header
			print $log "$transfer\n";
			close $log;

			my @v = split(/\|/,$transfer,$#transfer + 1);
			my %data;
			@data{@transfer_names} = @v ; # FIXME validate?

			print ">>> data ",dump( \%data ) if $ENV{DEBUG};

			$store->transfer( \%data );
=for gearman
			$gearman->dispatch_background( 'rsync_transfer' => $json );
=cut

			die "no rsync running" unless kill 0, rsync_running_pid;
		}
	}
	close($fifo);
	sleep 1;
}

