#!/usr/bin/perl
# mythexport-daemon v2.2.3
# By: John Baab
# Email: rhpot1991@ubuntu.com
# Purpose: daemon for exporting mythtv recordings into formats used by portable devices.
# Requirements: perl and the DBI & DBD::mysql modules, MythTV perl bindings, AtomicParsley
#
# License:
#
# This Package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this package; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# On Debian & Ubuntu systems, a complete copy of the GPL can be found under
# /usr/share/common-licenses/GPL-3, or (at your option) any later version

use strict;
use POSIX qw(setsid);
use DBI;
use DBD::mysql;
use Config::Simple;
use MythTV;
use Proc::Daemon;
use Proc::PID::File;
use Log::Dispatch;
use Log::Dispatch::File;
use Date::Format;
use File::Spec;
use File::Copy;
use XML::Writer;
use IO::File;

use lib '/usr/share/mythexport';
use lib '/usr/share/mythexport/configs';

&startDaemon;

my $debug = "";
my $level = "notice";

foreach (@ARGV){
    if ($_ =~ m/debug/) {
        $debug = 1;
    }
}

if($debug == 1){
    $level = "debug";
}

use constant LOG_DIR    => '/var/log/mythtv/';
use constant LOG_FILE   => 'mythexport.log';

our $HOSTNAME = `hostname`;
chomp $HOSTNAME;
my $log = new Log::Dispatch(
      callbacks => sub { my %h=@_; return Date::Format::time2str('%B %e %T', time)." ".$HOSTNAME." $0\[$$]: ".$h{message}."\n"; }
);
$log->add( Log::Dispatch::File->new( name      => 'file1',
                                     min_level => $level,
                                     mode      => 'append',
                                     filename  => File::Spec->catfile(LOG_DIR, LOG_FILE),
                                   )
);
$log->warning("Starting Processing:  ".time());
open STDERR, '>>', File::Spec->catfile(LOG_DIR, LOG_FILE);
my $connect = undef;
my $myth = undef;

my $tries = 5;
while ($connect == undef && --$tries > 0) {
    eval {
        $myth = new MythTV();
        # connect to database
        $connect = $myth->{'dbh'};
        1;
    } or do {
        logerror("Can't connect to MythTV: $@");
        logdebug("Sleeping 5 seconds...");
        sleep(5);
    };
}
if ($connect == undef) {
    die "Couldn't connect to MythTV.";
}


my $keep_going = 1;
$SIG{HUP}  = sub { $log->warning("Caught SIGHUP:  exiting gracefully"); $keep_going = 0; };
$SIG{INT}  = sub { $log->warning("Caught SIGINT:  exiting gracefully"); $keep_going = 0; };
$SIG{QUIT} = sub { $log->warning("Caught SIGQUIT:  exiting gracefully"); $keep_going = 0; };
$SIG{TERM} = sub { $log->warning("Caught SIGTERM:  exiting gracefully"); $keep_going = 0; };

sub startDaemon{
    Proc::Daemon::Init;
    dienice("Already running!") if Proc::PID::File->running(dir => "/var/run/mythtv",name => "mythexport");
}

sub dienice($) {
    my ($package, $filename, $line) = caller;
    $log->critical("$_[0] at line $line in $filename");
    die $_[0];
}

sub logerror($) {
    my ($package, $filename, $line) = caller;
    $log->critical("$_[0] at line $line in $filename");
}

sub logdebug($){
    my ($package, $filename, $line) = caller;
    $log->debug("$_[0] at line $line in $filename");
}

sub getExportDir(){
    # Set default values
    my $cfg = new Config::Simple();
    $cfg->read('/etc/mythtv/mythexport/mythexport.cfg') || logerror("Cannot read config file: /etc/mythtv/mythexport/mythexport.cfg");
    my $exportdir = $cfg->param("dir");
    $exportdir =~ s/\/$//;

    return $exportdir;
}

sub createXML($){
    my @params = split('&',$_[0]);
    my ($exportdir, @starttime, @chanid, $config, $sql_where) = "";

    foreach (@params){
        if ($_ =~ m/exportdir/) {
            $exportdir = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/starttime/) {
            @starttime = split('\|',(split(/\=/,$_))[1]);
        }
        elsif ($_ =~ m/chanid/) {
            @chanid = split('\|',(split(/\=/,$_))[1]);
        }
        elsif ($_ =~ m/config/) {
            $config = (split(/\=/,$_))[1];
        }
    }
    
    # debugging logging
    logdebug("exportdir = $exportdir");
    logdebug("starttime = @starttime");
    logdebug("chanid = @chanid");
    logdebug("config = $config");
    
    $exportdir =~ s/\/$//;

    require "$config.pm";

    my $object = new $config();
    my $extension = $object->Extension;
    
    #test that the directory has the correct permissions
    -w $exportdir || logerror("ERROR: Directory $exportdir is not writeable.\n");
    my $output = new IO::File(">$exportdir/mythimport.xml");
    
    # create sql where clause
    foreach my $i (0..scalar(@chanid)-1)
    {
        $sql_where .= "(rec.chanid=\'$chanid[$i]\' and rec.starttime=\'$starttime[$i]\') or ";
    }
    $sql_where =~ s/\sor\s$//;
    
    print $output "<?xml-stylesheet type=\"text/xsl\" href=\"mythimport.xslt\"?>";
    my $writer = new XML::Writer(OUTPUT => $output);
    $writer->startTag("channel");
    
    my $query = "SELECT rec.title, rec.subtitle, rec.description, pg.syndicatedepisodenumber, pg.showtype, rec.programid, rec.basename, rec.chanid, rec.starttime
        FROM recorded rec LEFT JOIN program pg ON pg.starttime = rec.starttime AND pg.chanid = rec.chanid WHERE $sql_where";
    my $query_handle = $connect->prepare($query);
    logdebug("query = $query");
    $query_handle->execute() || logerror("Unable to query mythexport table");
    
    $log->notice("Creating xml file");
    while ( my ($title,$subtitle,$description,$syndicatedepisodenumber,$showtype,$programid,$basename,$currentchanid,$currentstarttime) = $query_handle->fetchrow_array() ) {
    
        # FIND OUT THE CHANNEL NAME
        my $subquery = "SELECT callsign FROM channel WHERE chanid=?";
        my $subquery_handle = $connect->prepare($subquery);
        $subquery_handle->execute($currentchanid) || logerror("ERROR: Unable to query settings table");

        my $channame = $subquery_handle->fetchrow_array;

        # replace non-word characters in channame with dashes
        $channame =~ s/\W+/-/g;

        # replace non-word characters in title with underscores
        $title =~ s/\W+/_/g;
        # replace non-word characters in subtitle with underscores
        $subtitle =~ s/\W+/_/g;

        # Remove non alphanumeric chars from $starttime & $endtime
        $currentstarttime =~ s/[|^\W|\s|-|]//g;
        my $newfilename = $channame."-".$title."-".$subtitle."-".$currentstarttime;
        $newfilename =~ s/\..*?$//;
        
        $writer->startTag("item");
        
        $writer->startTag("title");
        $writer->characters("$title - $subtitle");
        $writer->endTag("title");
        
        $writer->startTag("description");
        $writer->characters("$description");
        $writer->endTag("description");
        
        $writer->startTag("link");

        $writer->characters("$newfilename.$extension");
        $writer->endTag("link");
        
        $writer->startTag("guid");
        $writer->characters("$newfilename");
        $writer->endTag("guid");
        
        $writer->endTag("item");

        $log->notice("Creating mysql dump");
        export("starttime=$currentstarttime&chanid=$currentchanid&config=$config&otg=true");
    }
    $writer->endTag("channel");
    $writer->end();
    $output->close();
    
    copy("/usr/share/mythtv/mythimport.xslt","$exportdir/mythimport.xslt") || logerror("ERROR: Cannot copy /usr/share/mythtv/mythimport.xslt to $exportdir/mythimport.xslt");
}

sub createSQL($){
    my @params = split('&',$_[0]);
    my ($exportdir, @starttime, @chanid, $config, $sql_where) = "";
    
    foreach (@params){
        if ($_ =~ m/exportdir/) {
            $exportdir = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/starttime/) {
            @starttime = split('\|',(split(/\=/,$_))[1]);
        }
        elsif ($_ =~ m/chanid/) {
            @chanid = split('\|',(split(/\=/,$_))[1]);
        }
        elsif ($_ =~ m/config/) {
            $config = (split(/\=/,$_))[1];
        }
    }
    
    # debugging logging
    logdebug("exportdir = $exportdir");
    logdebug("starttime = @starttime");
    logdebug("chanid = @chanid");
    logdebug("config = $config");
    
    $exportdir =~ s/\/$//;
    
    my $dbuser = $myth->{'db_user'};
    my $dbpass = $myth->{'db_pass'};
    my $dbhost = $myth->{'db_host'};
    my $dbname = $myth->{'db_name'};
    my $dbport = $myth->{'db_port'}; 
    
    # debugging logging
    logdebug("dbuser = $dbuser");
    logdebug("dbpass = $dbpass");
    logdebug("dbhost = $dbhost");
    logdebug("dbname = $dbname");
    logdebug("dbport = $dbport");

    #test that the directory has the correct permissions
    -w $exportdir || logerror("ERROR: Directory $exportdir is not writeable.\n");
    
    # create sql where clause
    foreach my $i (0..scalar(@chanid)-1)
    {
        $sql_where .= "(chanid=\'$chanid[$i]\' and starttime=\'$starttime[$i]\') or ";
    }
    
    $sql_where =~ s/\sor\s$//;

    my $command = "mysqldump -h$dbhost -u$dbuser -p$dbpass -P$dbport $dbname recorded recordedseek recordedrating recordedprogram recordedmarkup recordedcredits --where=\"$sql_where\" --no-create-db --no-create-info > $exportdir/mythimport.sql 2>&1";
    
    $log->notice("Creating mysql dump");
    logdebug("mysqldump command = $command");
    system($command) == 0 || logerror("ERROR: $command failed.");
    
    my $query = "SELECT basename FROM recorded where $sql_where";
    my $query_handle = $connect->prepare($query);
    logdebug("query = $query");
    $query_handle->execute()  || logerror("Unable to query mythexport table");

    my $schemaVer = $myth->backend_setting('DBSchemaVer');

    #copy files
    while ( my $basename = $query_handle->fetchrow_array() ) {
        my $dir = 'UNKNOWN';
        if ($schemaVer < 1171)
        {
            $dir = $myth->backend_setting('RecordFilePrefix');
        }
        else
        {
            my $storagegroup = new MythTV::StorageGroup();
            $dir = $storagegroup->FindRecordingDir($basename);
        }
        $log->notice("Copying $dir/$basename to $exportdir/$basename");
        copy("$dir/$basename","$exportdir/$basename") || logerror("Unable to copy $dir/$basename to $exportdir/$basename");
    }
}

sub export($){
    my @params = split("&",$_[0]);
    my ($debug,$starttime,$chanid,$config,$deleteperiod,$otg,$podcastname) = "";
    
    foreach (@params){
        if ($_ =~ m/starttime/) {
            $starttime = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/chanid/) {
            $chanid = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/config/) {
            $config = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/deleteperiod/) {
            $deleteperiod = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/otg/) {
            $otg = (split(/\=/,$_))[1];
        }
        elsif ($_ =~ m/podcastname/) {
            $podcastname = (split(/\=/,$_))[1];
        }
    }
    
    my ($title, $subtitle, $description, $syndicatedepisodenumber, $showtype, $programid, $basename, $airdate) = "";

    my $exportdir = getExportDir();

    # debugging logging
    logdebug("exportdir = $exportdir");
    logdebug("starttime = $starttime");
    logdebug("chanid = $chanid");
    logdebug("config = $config");

    #test that the directory has the correct permissions
    -w $exportdir || logerror("ERROR: Directory $exportdir is not writeable.\n");

    my $query = "SELECT rec.title, rec.subtitle, rec.description, pg.syndicatedepisodenumber, pg.showtype, rec.programid, rec.basename, rec.starttime
    FROM recorded rec LEFT JOIN program pg ON pg.starttime = rec.starttime AND pg.chanid = rec.chanid
    WHERE rec.chanid=? AND rec.starttime=?";
    my $query_handle = $connect->prepare($query);
    logdebug("query = $query");
    $query_handle->execute($chanid,$starttime) || logerror("ERROR: Cannot connect to database \n");

    $query_handle->bind_columns(undef, \$title, \$subtitle, \$description, \$syndicatedepisodenumber, \$showtype, \$programid, \$basename, \$airdate);
    $query_handle->fetch();

    my $schemaVer = $myth->backend_setting('DBSchemaVer');
    # Storage Groups were added in DBSchemaVer 1171
    # FIND WHERE THE RECORDINGS LIVE
    my $dir = 'UNKNOWN';
    if ($schemaVer < 1171)
    {
        logdebug("Using compatibility mode");
        $dir = $myth->backend_setting('RecordFilePrefix');
    }
    else
    {
        logdebug("Going into new mode\n");
        my $storagegroup = new MythTV::StorageGroup();
        $dir = $storagegroup->FindRecordingDir($basename);
    }

    # FIND OUT THE CHANNEL NAME
    $query = "SELECT callsign FROM channel WHERE chanid=?";
    $query_handle = $connect->prepare($query);
    logdebug("query = $query");
    $query_handle->execute($chanid) || logerror("ERROR: Unable to query channel table");

    my $channame = $query_handle->fetchrow_array;

    # replace non-word characters in channame with dashes
    $channame =~ s/\W+/-/g;

    # replace non-word characters in title with underscores
    my $title_old = $title;
    $title =~ s/\W+/_/g;
    # replace non-word characters in subtitle with underscores
    my $subtitle_old = $subtitle;
    $subtitle =~ s/\W+/_/g;

    # Remove non alphanumeric chars from $starttime & $endtime
    my $newstarttime = $starttime;
    $newstarttime =~ s/[|^\W|\s|-|]//g;
    my $filename = $dir."/".$basename;
    my $newfilename = $exportdir."/".$channame."-".$title."-".$subtitle."-".$newstarttime;
    $newfilename =~ s/\..*?$//;

    $syndicatedepisodenumber =~ m/^.*?(\d*)(\d{2})$/;
    my $seasonnumber = $1;
    my $episodenumber = $2;
    
    require "$config.pm";

    # need the extension to calculate our file name
    my $object = new $config();
    my $extension = $object->Extension();

    # Trim any characters over 63, 
    # it seems iTunes does not like files with lengths this long.
    my $x = 63 - $extension;
    $newfilename =~ s/^(.*\/(.{1,$x})).*$/$1/g;
    my $webfilename = "$2";

    if ($debug) {
        print "\n\n Source filename:$filename \nDestination filename:$newfilename\n \n";
    }

    # move to the export directory incase any logs get written
    chdir $exportdir;

    # create new object with our file names
    my $object = new $config($filename, $newfilename);
    $object->export();

    my $returnCode = $object->checkOutput();

    # only continue if encoding didn't fail
    if ($returnCode == 1){

        if (!$debug){
            # clean up any log files left behind by ffmpeg
            system "rm *.log";
            system "rm *.mbtree";
        }
        
        if($otg ne "true"){
            # Save Data
            $query = "INSERT into mythexport VALUES(NULL,?,?,?,?,NOW(),DATE_ADD(NOW(),INTERVAL ? DAY),?,?)";
            $query_handle = $connect->prepare($query);
            logdebug("query = $query");
            $query_handle->execute("$webfilename$extension",$title_old,$subtitle,$description,$deleteperiod,$airdate,$podcastname) || logerror("ERROR: Unable to update table.");
        }
    }
    else{
        logerror("ERROR: No resulting file from ffmpeg, most likely your ffmpeg failed.  Enable debugging and test by hand.");
    }
}

sub delete($){
    my $param = $_[0];

    my $cfg = new Config::Simple();
    $cfg->read('/etc/mythtv/mythexport/mythexport.cfg') || logerror("Cannot read config file: /etc/mythtv/mythexport/mythexport.cfg");

    my $dir = $cfg->param("dir");
    $dir =~ s/\/$//;
    
    my $delete_query = "SELECT file FROM mythexport where id=?";
    my $delete_query_handle = $connect->prepare($delete_query);
    logdebug("query = $delete_query");
    $delete_query_handle->execute($param) || logerror("ERROR: Unable to query mythexport table");

    my $file = $delete_query_handle->fetchrow_array();
    my $location = "$dir\/$file";
    
    $log->notice("Deleting $file");
    logdebug("file = $location");
    unlink($location) || logerror("ERROR: Unable to find file: $location");

    my $query = "delete from mythexport where id=?";
    my $query_handle = $connect->prepare($query);
    logdebug("query = $query");
    $query_handle->execute($param) || logerror("Unable to delete from mythexport table");
}


# our infinite loop
while($keep_going) {
        my $query = "select id, type, param from mythexport_job_queue order by id";
        my $query_handle = $connect->prepare($query);
        logdebug("query = $query");
        $query_handle->execute() || logerror("Unable to query mythexport_job_queue table");
        
        while(my ($id, $type, $param) = $query_handle->fetchrow_array()){
            if($type eq "delete"){
                &delete($param);
            }
            elsif($type eq "export"){
                &export($param);
            }
            elsif($type eq "otg-lightweight"){
                createXML($param);
            }
            elsif($type eq "otg-full"){
                createSQL($param);
            }
            else{
                logerror("ERROR: job type unknown.");
            }
            
            my $job_query = "delete from mythexport_job_queue where id=?";
            my $job_query_handle = $connect->prepare($job_query);
            logdebug("query = $job_query");
            $job_query_handle->execute($id) || logerror("Unable to delete from mythexport_job_queue table");

            # remove lock file so the userjob knows that it's work is done
            my $lockfile = getExportDir() . "/mythexport.$id";
            if (-e $lockfile){
                unlink($lockfile);
            }
        }
   
    # wait for 60 seconds
    sleep(60);
}

$log->warning("Stopping Processing:  ".time());
