#!/usr/bin/perl -w
use strict;
use Linux::Inotify2;
use Path::Class;
use Number::Bytes::Human;
use File::stat;
use Data::Dumper;

## JSON rpc call
use LWP::UserAgent;
use HTTP::Request;
use JSON::PP;

my $debug=1;   ## just more verbose


# Define required credentials.
my $jsonUser = "xbmc";
my $jsonPass = "<YOU XBMC password>";
my $jsonHost = "localhost:8080";

my $wait=60; ## how many seconds to wait after proccessing an event. This will allow us to group events/updates. (60 seconds default)

## only update xbmc / notify  on these extentions
my @video_files =  qw(avi mkv mpg mpeg mp4 mov wmv flv m1v m2v mpe);

## watched dirs - recursive
my @dirs = ("/Videos/",
	    "/OtherVideoDir/",
    );

## This requires the script from prowl app
## url: http://prowlapp.com/static/prowl.pl
my $prowl = {'enabled' => '0',
	     'script' => '/usr/local/bin/prowl.pl',
	     'api_key' => 'FIXME',
	     'app_name' => 'XBMC Inotify',
};

####################################################### Configuration DONE #######################################################

my @files;
my $inotify = Linux::Inotify2->new
    or die "unable to create new inotify object: $!";

my $host=`hostname`; ## maybe used for later
my $pid=$$;

## on start - lets scan the library

&jsonXBMC('VideoLibrary.Scan');
&notifyProwl('Initializing','program startup');


## setup initial watch dirs
my $dirs =0;
my $update_xbmc=0;
my $clean_xbmc=0;
my @start_list;  ## used for notifications
my @finish_list; ## used for notifications
my @remove_list; ## used for notifications

## Intialize watched dirs - INIT
foreach my $drop_dir(@dirs) {
    &log("Processing subdirs for: $drop_dir\n",1);
    my $dir = dir($drop_dir);
    my $c = 0;
    $dir->traverse(sub{
	my ($child, $cont, $indent) = @_;
	if ($child->is_dir) {
	    $dirs++;
	    &log("subdir: $child",1);
	    $inotify->watch("$child",  IN_CLOSE_WRITE | IN_CREATE | IN_MOVED_TO |  IN_DELETE , \&MyWatch);
	    
	    ## in_modify if too verbose.. it grabs any changes to file before final close
	    ## IN_CLOSE_WRITE == after a file is written too/closed
	    ## IN_CREATE == new directories/files
	    ## IN_MOVED_TO == moved directory/files (to watch dirs only)
	}
	$cont->($c + 1);
		   });
}

## log/notify status of watched dirs
my $init_s = "Watching $dirs directories";
&log($init_s,1);
&notifyProwl('Initialized',$init_s);

## LOOP to keep watching dirs/files
while (1) {
    $inotify->poll;
    my $had_event = 0;
    ## notify on sync start and finish

    if (@start_list)  {  
	my $info = join(', ', @start_list);
	my $title = 'rSync Started';
	&notifyProwl($title,$info);
	## notify XBMC
	my %params = (
	    "title" => $title,
	    "message" => $info,
	    "displaytime" => 10000,
	    );
	&jsonXBMC('GUI.ShowNotification',\%params);
	@start_list = ();
    }
    if (@finish_list) {  
	my $info = join(', ', @finish_list);
	my $title = 'rSync Finished';
	&notifyProwl($title,$info);
	## notify XBMC
	my %params = (
	    "title" => $title,
	    "message" => $info,
	    "displaytime" => 10000,
	    );
	&jsonXBMC('GUI.ShowNotification',\%params);
	@finish_list = ();
    }
    if (@remove_list)  { 
	&notifyProwl('Removed',join(', ', @remove_list));  
	@remove_list = ();
    }

    
    if ($update_xbmc) {
	$had_event = 1;
	$update_xbmc = 0;
	&log("Updating XBMC library");
	&jsonXBMC('VideoLibrary.Scan');
    }
    if ($clean_xbmc) {
	$had_event = 1;
	$clean_xbmc = 0;
	&log("Cleaning XBMC library");
	&jsonXBMC('VideoLibrary.Clean');
    }
    if ($had_event) {
	&log("Sleeping '$wait' seconds before processing any new events");
	sleep($wait);
	&log("I\'m awake again - ready to process any new events");
    }

}



sub MyWatch() {
    #$inotify->watch("$child",  IN_CLOSE_WRITE | IN_MOVED_TO , sub {
    my $event = shift;
    my $name = $event->fullname;		
    my $file_name = $event->name;		
    ## continue on..
    my $log = 'unknown';
    if ($event->IN_IGNORED) {
	$event->w->cancel;  ## cancel watch
	
	#$update_xbmc = 1; frodo doesn't work well yet with deleting after update.. so enableing clean again
	$log = "DIR $name removed -- cancelling watch";
	foreach my $ext (@video_files) {
	    $clean_xbmc = 1; ## only clean if we care
	    if ($name =~ /\.$ext/i) {    push (@remove_list,$name);	}
	}
	#&notifyProwl('removed',$log);
	## remove watch - if directory
    } elsif ( $event->IN_DELETE) {
	
	## this is a file - so nothign really to do other than notify
	$log = "FILE: $name removed";
	## group deletes together -- otherwise.. flurry of prowl notify
	foreach my $ext (@video_files) {
	    $log = "FILE: VIDEO $name removed - will notify prowl";
	    $clean_xbmc = 1;
	    if ($name =~ /\.$ext/i) {    push (@remove_list,$name);	}
	}
    } else {		
	if (-d $name) {
	    $inotify->watch($name,  IN_CLOSE_WRITE | IN_CREATE | IN_MOVED_TO , \&MyWatch);
	    $log = "DIR: $name created -- adding to watchlist";
	    &notifyProwl('new',$log);
	} elsif (-f $name && $event->IN_CREATE) {
	    ## file is created, but has not finished writing - do not update xbmc
	    ### this is where we would notify on start sync -- only notify on files we watch
	    $log = "$name is FILE -- in_create called.. waiting for IN_CLOSE_WRITE to process";
	    foreach my $ext (@video_files) {
		## push new files to start_list (sync started notifications)
		if ($file_name =~ /\.$ext/i) {    push (@start_list,$name);	}
	    }
	} else {
	    $log = "$name is IN_CLOSE_WRITE" if $event->IN_CLOSE_WRITE;
	    $log = "$name is IN_CREATE" if $event->IN_CREATE;
	    $log = "$name is IN_MOVED_TO" if $event->IN_MOVED_TO;
	    $log = "events for $name have been lost\n" if $event->IN_Q_OVERFLOW;
	    if ($file_name =~ /^\..*\.\w{5}$/) {
		&log("$file_name must be rsync - skip it");  
	    } else {
		## only update video files
		foreach my $ext (@video_files) {
		    ## push new files to finish_list (sync finished notifications)
		    if ($file_name =~ /\.$ext$/i) {
			## SIZE OF FILE
			my $human = Number::Bytes::Human->new();
			my $size = stat($name)->size;
			my $hsize = $human->format($size);
			push (@finish_list,"[$hsize] $name");
			&log("$name $ext is video file -- update xbmc");
			$update_xbmc = 1; ## new videos - set update xbmc
		    }
		}
		if (!$update_xbmc) {&log("$file_name is NOT a video file -- skipping update xbmc");   }
	    }	
	}
    }
    &log($log);
}


sub log() {
    my $msg = shift;
    my $print= shift;
    if ($debug || $print) {
	print localtime . ": $msg\n";    
    }
    system("logger  -t $0\[$pid\] \"$msg\"");
}


sub jsonXBMC() {
    my $method = shift;
    my $tmp = shift;
    my %params;
    if ($tmp) {
	%params = %$tmp;
    }
    # Construct JSON RPC URL.
    my $jsonUrl = sprintf("http://%s:%s@%s/jsonrpc",
			  $jsonUser,
			  $jsonPass,
			  $jsonHost,);
    
    my %hash = ("jsonrpc" => "2.0",
		"method" => "$method",
		"params" => \%params,
		"id" => $method,
	);

    #my $json = encode_json \%hash;
    my $js = JSON::PP->new;
    $js->canonical(1);
    my $json = $js->encode(\%hash);
    
    # Setup a new HTTP Request object.
    my $http = HTTP::Request->new('POST', $jsonUrl);
    
    $http->header('Content-Type' => 'application/json');
    $http->content($json);
    
    # Fire off the request.
    my $ua  = LWP::UserAgent->new;
    my $res = $ua->request($http);
    
    # Return the HTTP status and exit.
    my $result = sprintf("XBMC JSON-RPC $method: host:$jsonHost, user:$jsonUser, pass:<hidden>, Result: %s\n", $res->status_line);
    &log($result);
    if ($res->status_line !~ /200\s+OK/i) {
	&notifyProwl('XBMC JSON: ' . $method,$res->status_line);
    }
}


sub notifyProwl() {
    
    if (defined($prowl->{enabled}) && $prowl->{enabled} == 1) {
	my ($event,$msg) = @_;
	my $cmd = sprintf("perl %s -apikey='%s' -application='%s' -event='%s' -notification='%s'",$prowl->{script}, $prowl->{api_key}, $prowl->{app_name}, $event, $msg);
	my $res = `$cmd`;
	&log("Notify Prowl Event: $cmd\n");
	&log("Notify: Prowl Result: $res\n");
    }

}

