#! /usr/bin/perl -w

use Getopt::Long;
use Pod::Usage;

my $help = 0;
my $usage = 0;
my $dryrun = 0;
my $verbose = 0;
my $path = '';
my $annex = '';
my $home = $ENV{'HOME'};

sub main() {
    checkargs();
    if (!$path) {
        $path = $home . '/.xbmc/userdata/Database';
    }
    print("# checking XBMC directory '$path'\n") if ($verbose);
    $dbpath = finddb($path);
    if (!$dbpath) {
        pod2usage("$0: can't find a XBMC database in '$path'.");
    }
    print("# using database '$dbpath'\n") if ($verbose);
    checkdb();
}

# list videos database, find the latest one
# modified version of
# http://stackoverflow.com/questions/4651092/getting-the-list-of-files-sorted-by-modification-date-in-perl
sub finddb($) {
    my $path = shift(@_);
    opendir my($dirh), $path or die "can't opendir $path: $!";
    my @flist = sort {  -M $a <=> -M $b } # Sort by modification time
        map  { "$path/$_" } # We need full paths for sorting
        grep { /^MyVideos.*\.db$/ }
        readdir $dirh;
    closedir $dirh;
    if ($#flist > 0) {
        return $flist[0];
    }
    else {
        return 0;
    }
}

sub checkargs() {
    pod2usage(1) if $help;
    pod2usage(-exitval => 0, -verbose => 2) if $usage;

    GetOptions('h|?' => \$help,
               'help|usage' => \$usage,
               # we want to operate on relative links, so set this to
               # the common annex to the git annex repo
               'annex=s' => \$annex,
               'path=s' => \$path,
               'home=s' => \$home,
               'dryrun|n' => \$dryrun,
               'verbose|v' => \$verbose,
        )
        or die("Error parsing commandline\n");
}

sub checkdb() {
    my @lines = `echo 'SELECT playCount, path.strPath, files.strFileName FROM movie JOIN files ON files.idFile=movie.idFile JOIN path ON path.idPath=files.idPath;' | sqlite3 $dbpath`;
    print "# finding files...\n" if $verbose;
    for (@lines) {
        my ($count, $dir, $file) = split /\|/;
        chomp $file;
        # empty or non-numeric count is zero
        if ($count !~ /[0-9]/) {
            $count = 0;
        }
        print "# $dir/$file\n" if $verbose;
        if ($file =~ s#stack://##) {
            for (split /,/, $file) {
                s/$annex//;
                s/^ //;
                s/ $//;
                my @cmd = (qw(git annex metadata --set), "playCount=$count", $_);
                if ($dryrun) {
                    print join(' ', @cmd) . "\n";
                }
                else {
                    system(@cmd);
                }
            }
        }
        else {
            $dir =~ s/$annex//;
            my @cmd = (qw(git annex metadata --set), "playCount=$count", "$dir$file");
            if ($dryrun) {
                print join(' ', @cmd) . "\n";
            }
            else {
                system(@cmd);
            }
        }
    }
}

main();

__END__
=encoding utf8

=head1 NAME

git-annex-xbmc-playcount - register XBMC playcounts as git-annex metadata

=head1 SYNOPSIS

git-annex-xbmc-playcount [--path .xbmc/userdata/Database]

 Options:
  -h         short usage
  --help     complete help
  --dryrun, -n do nothing and show the commands that would be ran
  --annex    path to the git-annex repo
  --home     the home directory where the .xbmc directory is located
  --path     the location of the Database directory of XBMC, overrides --home
  --verbose  show interaction details with the database

=head1 DESCRIPTION

This program will look into the XBMC database for the "playcount"
field to register that number as metadata in the git-annex repository.

=head1 OPTIONS

=over 8

=item B<--dryrun>

Do nothing but show all the steps that would be ran. The output can be
piped through a POSIX shell after inspection. B<-n> is an alias of
this command. Example:

    git-annex-xbmc-playcount -n | tee runme
    # inspect the output
    sh < runme

=item B<--annex>

This option allows the user to specify the root of the git-annex
repository, which is then stripped off the paths found in the XBMC
database.

=item B<--home>

Home of the user running XBMC. If not specified, defaults to the $HOME
environment variables. The script will look into
B<$home/.xbmc/userdata/Database> for a file matching
B<^MyVideos.*\.db$> and will fail if none is found.

=item B<--path>

Manually specify the path to B<.xbmc/userdata/Database>. This
overrides B<--home>.

Note that this doesn't point directly to the database itself, because
there are usually many database files and we want to automatically
find the latest. This may be a stupid limitation.

=item B<--verbose>

Show more information about path discovery. Doesn't obstruct
B<--dryrun> output because lines are prefixed with C<#>.

=back

=head1 EXAMPLES

You have a git annex in B</srv/video> and XBMC is ran as the
B<video> user and you want to be cautious:

    $ ./git-annex-xbmc-playcount.pl --home /home/video/ -n --annex /srv/video/ | tee set-metadata
    git annex metadata --set playCount=0 films/Animal.Farm.1954.DVDRip.DivX-MDX.avi

This looks about right, set the metadata:

    $ git annex metadata --set playCount=0 films/Animal.Farm.1954.DVDRip.DivX-MDX.avi
    metadata films/Animal.Farm.1954.DVDRip.DivX-MDX.avi
      lastchanged=2014-10-04@22-17-42
      playCount=0
      playCount-lastchanged=2014-10-04@22-17-42
    ok
    (Recording state in git...)

=head1 ENVIRONMENT

B<$HOME> is looked into to find the B<.xbmc> home directory if none of
B<--home> or B<--path> is specified.

=head1 FILES

=over 8

=item B<$HOME/.xbmc/userdata/Database/MyVideos.*\.db>

This is where we assume the SQLite database of videos XBMC uses is
stored.

=back

=head1 BUGS

If there are pipes (C<|>) in filenames, the script may fail to find
the files properly. We would need to rewrite the database code to use
B<DBD::SQLite>(3pm) instead of a pipe to B<sqlite3>(1).

=head1 LIMITATIONS

It took longer writing this help than writing the stupid script.

The script will not tag files not yet detected by XBMC.

The script is not incremental, so it will repeatedly add the same
counts to files it has already found.

=head1 SEE ALSO

B<git-annex>(1), B<xbmc>(1)

=head1 AUTHOR

Written by Antoine Beaupré <anarcat@debian.org>