#!/usr/bin/perl -w

use strict;

#
# 2005/03/22: new version uses directly the genreric xml file to get channel information.
#             It also add ShortTitle use. Now all identical channels name are filled, even if
#             twice or more occurency. You may need a channels.trans for channels name
#             translation between xmltvfile and channels.conf.
#				it looks like :	
#				channel_conf_name:xmltvfile_channel_name
#
# xml.pl : new version of xmltv2vdr
# ./xml.pl -c channels.conf -x xmltvfile.xml -l channels.trans
# 
#
# xmltv2vdr.pl
#
# Converts data from an xmltv output file to VDR - tested with 1.2.6
#
# The TCP SVDRSend and Receive functions have been used from the getskyepg.pl
# Plugin for VDR.
#
# This script requires: -
#
# The PERL module date::manip (required for xmltv anyway)
#
# You will also need xmltv installed to get the channel information:
# http://sourceforge.net/projects/xmltv
#
# This software is released under the GNU GPL
#
# See the README file for copyright information and how to reach the author.

# $Id: xmltv2vdr.pl 1.0.6 2004/04/19 20:01:04 psr Exp $

# Perl modules
use Time::Local;
use Date::Manip;
# Long options
use Getopt::Long;
# Socket methods
use IO::Socket;
use IO::Socket::INET;
# I/O
use FileHandle;
# Stringified perl data structures
use Data::Dumper;

# Force a flush after every write or print
STDOUT->autoflush(1);
STDERR->autoflush(1);


# Prototypes
sub displayError($$);
sub displayDebug($$$);
sub displayResults(\@$);

sub sendOnly($$);
sub sendReceive($$$);
sub sendAnalyse($$$);
sub receiveResults($$);
sub analyseResults(\@$);

sub getChannels($$$\%);
sub getPrograms($\%\%\%$);
sub updateEPG(\%\%\%$);

sub xmltvTranslate($);
sub xmltime2vdr($$);
sub xmltime2string($);

sub vdrError($$$);

sub openSocket($$$$);

sub NameTranslate($);



# Debugging levels
use constant DEBUG_NONE     => 0;
use constant DEBUG_ANALYSIS => 1;
use constant DEBUG_FULL     => 2;
use constant DEBUG_ALL      => 3;




#---------------------------------------------------------------------------
# Main
#---------------------------------------------------------------------------

my $Usage = qq {
Usage: $0 [options] -c <channels.conf file> -l <channels.trans file> -x <xmltv datafile> 

Options:
	 -h			            Show help text
	 --debug n              Display debug information
	 -a (+,-) mins        	Adjust the time from xmltv that is fed
                                into VDR (in minutes) (default: 0)
	 -c channels init file  Channels.conf file
	 -d hostname            Destination hostname (default: localhost)
     -p port                SVDRP port number (default: 2001)
	 -l trans file			Name of the channels.trans file
	 -s                     Simulation Mode (Print info to stdout)
     -t timeout             The time this program has to give all info to
                                VDR (default: 300s)
	 -v                     Show warning messages
	 -x xmltv output        File containing xmltv data


};


# Options
my $sim;
my $adjust = 0;

# log files for using report
my $channels_xml_nu = "channels.xml_nu.log";
my $channels_conf_ok = "channels.conf_ok.log";
my $channels_conf_nok = "channels.conf_nok.log";

my $channelsFile = "channels.conf";
my $host   = "localhost";
my $svdrpport   = 2001;
my $descVerbosity = 0;
my $transFile = "channels.trans";
my $timeout = 300; # max. seconds to wait for response
my $xmltvFile;
my $help;
my $verbose;
my $debug = -1;
my %opt = ('h'     => \$help,
	       'help'  => \$help,
	       'debug' => \$debug,
           'v'     => \$verbose,
	       's'     => \$sim,
	       'a'     => \$adjust,
	       'c'     => \$channelsFile,
	       'd'     => \$host,
	       'p'     => \$svdrpport,
           'l'     => \$transFile,
	       't'     => \$timeout,
	       'x'     => \$xmltvFile
	  );

if (GetOptions(\%opt, 'h|help', 'debug:i', 'a:i', 'c:s', 'd:i', 'p:i',
	       'l:s', 't:i', 'x:s', 's', 'v:i') == 0)
{
    die $Usage;
}

die $Usage if (defined($help));


# Mandatory parameter
defined($xmltvFile) || die "$Usage Need to specify an XMLTV file";


# Start process

# Get channel information
printf("Reading file '".$channelsFile."'...\n");
my %channelsHash;
getChannels($channelsFile, $xmltvFile, $transFile, %channelsHash);

# Get program information
printf("Reading file '".$xmltvFile."'...\n");
my %programsHash;
my %programsRepeat;
getPrograms($xmltvFile, %channelsHash, %programsHash, %programsRepeat, $debug);
printf("All information imported...\n");

# 
#print(Dumper(\%programsHash)."\n");
#

# Do the EPG stuff
printf("Updating EPG...\n");
if (updateEPG(%channelsHash, %programsHash, %programsRepeat, $debug))
{
    print("Some errors occured during EPG update...\n");
}




#---------------------------------------------------------------------------
# Functions
#---------------------------------------------------------------------------

sub displayError($$)
#
# Display error message, close socket and exit
#
{
    # sock   : socket to close
    # $msg   : error message
    my ($sock, $msg) = @_;

    print STDERR "$msg\n";
    if (!defined($sim) && defined($sock))
    {
		$sock->close();
    }
    exit(1);
}

sub displayDebug($$$)
{
    # $debug     : current debug level
    # $debugLevel: debug level required to display message
    # $msg       : message to be displayed
    my ($debug, $debugLevel, $msg) = @_;

    if ($debug >= $debugLevel)
    {
		print STDERR $msg;
    }
}

sub displayResults(\@$)
#
# Display results
#
{
    # $res   : results
    # $debug : debug level
    my ($res, $debug) = @_;

    if ($debug >= DEBUG_FULL)
    # Depending of debug level
    {
		foreach my $row (@$res)
		{
			printf STDERR ($row);
		}
    }
}





sub sendOnly($$)
#
# Send command and DON'T get result
#
{
    # sock   : socket used to communicate
    # $cmd   : command to send
    my ($sock, $cmd) = @_;

    if (!defined($sim))
    {
		displayDebug($debug, DEBUG_ALL, $cmd . "\n");
		print $sock "$cmd\r\n";
    }
    else
    {
		print "$cmd\n";
    }
}

sub sendReceive($$$)
#
# Send command, get result and return it
#
{
    # sock   : socket used to communicate
    # $cmd   : command to send
    # $debug : debug level
    my ($sock, $cmd, $debug) = @_;

    my @res;

    if (!defined($sim))
    {
		sendOnly($sock, "$cmd");
		#print $sock "$cmd\r\n";
		@res=receiveResults($sock, $debug);
    }
    else
    {
		print "$cmd\r\n";
    }

    return @res;
}

sub sendAnalyse($$$)
#
# Send command, get result, analyse it and return it
#
{
    # sock         : socket used to communicate
    # $cmd         : command to send
    # $debug       : debug level
    my ($sock, $cmd, $debug) = @_;

    my @res;

    if (!defined($sim))
    {
		@res = sendReceive($sock, $cmd, $debug);
	
		if (analyseResults(@res, $debug))
		{
			vdrError($sock, "VDR error", $debug);
		}
    }
    else
    {
		print $cmd . "\n";
    }

    return @res;
}



sub receiveResults($$)
{
#
# Get results (i.e. read socket)
#
    # sock         : socket used to communicate
    # $debug       : debug level
    my ($sock, $debug) = @_;

    my @res;

    if (!defined($sim))
    {
		while (<$sock>)
		{
			push(@res, $_);
			last if substr($_, 3, 1) ne "-";
		}
	
		# Display results (depending of debug level)
		displayResults(@res, $debug);
    }

    return @res;
}

sub analyseResults(\@$)
#
# Analyse results
#
{
    # $res         : results
    # $debug       : debug level
    my ($res, $debug) = @_;

    # SVDRP Reply Codes:
    # r_code = 0 : no error
    # r_code = 1 : error
    # r_code = 2 : local error
    my %reply_codes = (
		       '214' => {
			   'msg' => "Help message",
			   'r_code' => 0
		       },
		       '215' => {
			   'msg' => "EPG data record",
			   'r_code' => 0
		       },
		       '220' => {
			   'msg' => "VDR service ready",
			   'r_code' => 0
		       },
		       '221' => {
			   'msg' => "VDR service closing transmission channel",
			   'r_code' => 0
		       },
		       '250' => {
			   'msg' => "Requested VDR action okay, completed",
			   'r_code' => 0
		       },
		       '354' => {
			   'msg' => "Start sending EPG data",
			   'r_code' => 0
		       },
		       '451' => {
			   'msg' => "Requested action aborted: local error in processing",
			   'r_code' => 2
		       },
		       '500' => {
			   'msg' => "Syntax error, command unrecognized",
			   'r_code' => 1
		       },
		       '501' => {
			   'msg' => "Syntax error in parameters or arguments",
			   'r_code' => 1
		       },
		       '502' => {
			   'msg' => "Command not implemented",
			   'r_code' => 1
		       },
		       '504' => {
			   'msg' => "Command parameter not implemented",
			   'r_code' => 1
		       },
		       '550' => {
			   'msg' => "Requested action not taken",
			   'r_code' => 1
		       },
		       '554' => {
			   'msg' => "Transaction failed",
			   'r_code' => 1
		       }
		       );

    my $return_code = 0;

    foreach my $row (@$res)
    {
		# Search for return code
		if ($row =~ /^([0-9]{3}) (.*)$/)
		{
			my $code = $1;
			my $msg = $2;
	
			# Error ?
			if (($reply_codes{$code}{r_code} == 1))
			{
				$return_code = 1;
			}
	
			# Display error depending of debug level
			if ($debug >= DEBUG_ANALYSIS)
			{
				printf (STDERR "%s: %s\n", $reply_codes{$code}{msg}, $msg);
			}
		}
    }

    return $return_code;
}




sub getChannels($$$\%)
#
# Read channel parameters and store it.
#
{
    # $channelsFile: channels description file (telecable.ini)
    # $channelsHash: channels description hash (out).
    #                The hash key is the channel number (from the website).
    # $debug       : debug level
    my ($channelsFile, $xmltvFile, $transFile, $channelsHash) = @_;

	my $isChannel=0;
	my $ChannelNumber=0;
	my $ChannelName;
	my $xmlNumber=0;
	my %transHash;
	my %xmlHash;
	my $fh = new FileHandle;
	my $log2 = new FileHandle;
	my $log1 = new FileHandle;
		

	# Reading the transcription channels file
	if ($fh->open($transFile))
	{
		while (my $Line=<$fh>)
		{
	
			next if ($Line =~ /^#/);
			
			# CHASSE ET PECHE:CHASSE & PECHE
			if ($Line =~ /([^:]+):(.*)/)
			{
				my $xmlName = NameTranslate($2);
				my $confName = NameTranslate($1);
				$transHash{$confName} = $xmlName;
			}		
		}
		$fh->close();
	}

	
	# Reading the xmltvfile to get channels names

	$fh->open($xmltvFile) || die "Cannot open file '$xmltvFile'";
	
	while (my $Line=<$fh>)
	{
	
		last if ($Line =~/<programme start/);
		
		# <channel id="C451.lesagencestv.com">
		if ($Line =~ /<channel id="C([0-9]+)[^"]+">/)
		{
			$isChannel = 1;
			$ChannelNumber = $1;
		}
		
		# <display-name>Ushuaïa TV</display-name>
		if (($isChannel==1) && ($Line =~/<display-name>([^<]+)<\/display-name>/))
		{
			$ChannelName = $1;		
		}
		
		# </channel>
		if (($isChannel==1) && ($Line =~/<\/channel>/))
		{
			$isChannel = 0;
			my $identification=NameTranslate($ChannelName);
			$xmlHash{$identification}{'Number'}=$ChannelNumber;
			$xmlHash{$identification}{'Name'}=$ChannelName;
			$xmlHash{$identification}{'Used'}=0;			
		}		
	}
	
	$fh->close();

	# Reading the channels.conf
	$fh->open($channelsFile) || die "Cannot open file '$channelsFile'";
	
	my $ChannelConfNumber = 1;
	
	# Starting creation of the channels-trans.log
	if (defined($verbose))
	{	
		$log1->open(">".$channels_conf_nok) || die "Cannot open file '$channels_conf_nok'";
		
		print $log1 "# Liste des chaines du channels.conf n'ayant pas de correspondance dans le fichier au format XML\n";
		print $log1 "#\n";
		print $log1 "# format : Nom_de_chaine_channels_conf:Nom_de_chaine_XML\n";
		print $log1 "# les lignes précédées du caractère # sont traitée en commentaire\n";
		print $log1 "#\n";
	
		$log2->open(">".$channels_conf_ok) || die "Cannot open file '$channels_conf_ok'";
		
		print $log2 "# Liste des chaines du channels.conf qui ont été mises à jour avec succès\n";
		print $log2 "#\n";
		print $log2 "# format : Numéro_de_chaineVDR		Nom_de_chaine_VDR\n";
		print $log2 "# les lignes précédées du caractère # sont traitée en commentaire\n";
		print $log2 "#\n";
	}
	
	while (my $Line=<$fh>)
	{
		#last if ($xmlLine =~/<programme start/);
		
		# :@10 cable
		if ($Line =~ /:@([0-9]+)/)
		{
			if ($1 > $ChannelConfNumber)
			{
				$ChannelConfNumber = $1;				
			}		
			next
		}
		
		# M6 Boutique la chaîne;TPS:155:I1C0M64:C:6875:1120:1130:0:500:1411:176:11400:0
		if ($Line =~ /^([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):(.+)$/)
		{
			(my $OriginalName) = split(/;/,$1);
			my $NewName = NameTranslate($OriginalName);
			
			if ((exists $transHash{$NewName}))
			{
				if (exists $xmlHash{$transHash{$NewName}})	
				{ 
					$xmlNumber = $xmlHash{$transHash{$NewName}}{'Number'};					
					$xmlHash{$transHash{$NewName}}{'Used'}=1;
				}
				else 
				{ 
					if (defined($verbose))
					{
						(my $logevent) = split(/;/,$1);
						$logevent=$ChannelConfNumber."\t".$logevent."\n";
						print $log1 $logevent;			
					}
					$ChannelConfNumber++;
					next;
				}				
			}
			elsif (exists $xmlHash{$NewName})
			{			
				$xmlNumber = $xmlHash{$NewName}{'Number'};
				$xmlHash{$NewName}{'Used'}=1;
			}			
			else
			{	
				if (defined($verbose))
				{
					(my $logevent) = split(/;/,$1);
					$logevent=$ChannelConfNumber."\t".$logevent."\n";
					print $log1 $logevent;			
				}
				$ChannelConfNumber++;
				next;
			}

			if (defined($verbose))
			{
				(my $logevent) = split(/;/,$1);
				$logevent=$ChannelConfNumber."\t".$logevent."\n";
				print $log2 $logevent;						
			}
			
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'channelName'} = $OriginalName; 
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'transponderFrequency'} = $2;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'transponderPolarization'} = $3;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'source'} = $4;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'SR'} = $5;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'videoPid'} = $6;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'audioPid'} = $7;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'textPid'}=$8;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'conditionalAccess'} = $9;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'SID'} = $10;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'NID'} = $11;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'TID'} = $12;
			$channelsHash{$xmlNumber}{$ChannelConfNumber}{'RID'} = $13;
	
			if ( $channelsHash{$xmlNumber}{$ChannelConfNumber}{'source'} eq 'T' )
			{
				$channelsHash{$xmlNumber}{$ChannelConfNumber}{'transponderFrequency'} =
					substr($channelsHash{$xmlNumber}{$ChannelConfNumber}{'transponderFrequency'}, 0, 3);
			}
			
			$ChannelConfNumber++;
		}			
	}

	if (defined($verbose))
	{
		$log1->close();

		$log2->open(">".$channels_xml_nu) || die "Cannot open file '$channels_xml_nu'";
		print $log2 "# Liste des chaines du fichier xmltv n'ayant pas été utilisées\n";
		print $log2 "#\n";
		print $log2 "#Vous pouvez utiliser ce fichier et le compléter pour créer un fichier channels.trans plus complet \n";
		print $log2 "# format : Nom_de_chaine_channels_conf:Nom_de_chaine_XML\n";
		print $log2 "# les lignes précédées du caractère # sont traitée en commentaire\n";
		print $log2 "#\n";
		
		for my $xmlChannel (sort keys %xmlHash)
		{
			if ($xmlHash{$xmlChannel}{'Used'}==0)
			{
				print $log2 "#channel_conf_name:".$xmlHash{$xmlChannel}{'Name'}."\n";
			}
		}
		
		$log2->close();
	}
	
	$fh->close();
}


sub getPrograms($\%\%\%$)
#
# Read program information and store it
#
{
    # $xmltvFile: the XML file containing program information (telecable.xml)
    # $channelsHash: channels description
    # $programsHash: programs description (out)
    # $descCount   : requested description verbosity
    # $debug       : debug level
    my ($xmltvFile, $channelsHash, $programsHash, $programsRepeat, $debug) = @_;

    # Open and parse XML file
    my $fh = new FileHandle;
    $fh->open($xmltvFile) || die "Cannot open file '$xmltvFile'";

    # Do we detect an event ?
    my $chanEvent = 0;
    # Current description mode (ativated or not)
    my $desc = 0;
    # The current channel
    my $channelNumber = -1;
    # Uniq event entry
    my $hashKey;
	my $descriptionLine;
	my $date;
	my $title;
		
    while (my $xmlLine = <$fh>)
    {
		# Remove ending "\n"
		chomp $xmlLine;
		 
		# Translate some special characters
		$xmlLine = xmltvTranslate($xmlLine);
	
		# New XML Program - doesn't handle split programs yet
		if ( ($xmlLine =~ /\<programme/ ) && ( $chanEvent == 0 ) )
		{
			# Get channel number
			($channelNumber) = ($xmlLine =~ m%channel=\"C([0-9]*)%);
			if (defined($channelNumber) && defined($channelsHash->{$channelNumber}))
			# We don't store information for unconfigured channels
			{
				# New event
				$chanEvent = 1;
				# Get start and end time
				# <programme start="20050126123000 +0100" stop="20050126141000 +0100" channel="C35.lesagencestv.com">
				my ( $xmlStartTime, $xmlEndTime) = ($xmlLine =~ m%\"([^\"]*)\"[^\"]*\"([^\"]*)\"%);
				# Compute vdr dates
				my $vdrStartTime = xmltime2vdr($xmlStartTime, $adjust);
				my $vdrEndTime   = xmltime2vdr($xmlEndTime, $adjust);
				# Prepare the event date to complete the repeat broadcast list				
				$date = xmltime2string($xmlStartTime);
			   
				# $xmlStartTime.$xmlEndTime should be a perfect uniq hash key ;-)
				$hashKey = $xmlStartTime.$xmlEndTime;
				$programsHash->{$channelNumber}->{$hashKey}->{'vdrStartTime'} = $vdrStartTime;
				$programsHash->{$channelNumber}->{$hashKey}->{'vdrLength'} = $vdrEndTime - $vdrStartTime;
				$programsHash->{$channelNumber}->{$hashKey}->{'vdrId'} = $vdrStartTime / 60 % 0xFFFF;
			}
		}
		
		# XML program Title
		if ( ($xmlLine =~ /\<title/ ) && ( $chanEvent == 1 ) )
		{
			my @keys = keys %{$channelsHash{$channelNumber}};
			my $realChannelName = $channelsHash{$channelNumber}{pop(@keys)}{'channelName'};
			
			($title) = ($xmlLine =~ m%>(.*)</%);	
			($programsHash->{$channelNumber}->{$hashKey}->{'vdrTitle'}) = $title;
			
			# Add the current event to the repeat broadcast list
			if (defined($programsRepeat->{$title}))
			{
				$programsRepeat->{$title}->{'dates'} .= "|$date sur $realChannelName";
				$programsRepeat->{$title}->{'count'}++;
			}
			else
			{
				$programsRepeat->{$title}->{'dates'} = "$date sur $realChannelName";
				$programsRepeat->{$title}->{'count'} = 1;
			}
		}
	
		# XML program description at required verbosity
		if ( ($xmlLine =~ /\<desc/ ) && ( $chanEvent == 1 ) && ( $desc == 0) )
		{
			# Enter in the description mode
			$desc = 1;	 	    
			$descriptionLine = "";
		}
		
		# Description is not of the required verbosity
		if ( ( $chanEvent == 1 ) && ( $desc != 0 ) )
		{
			if ( ($xmlLine =~/\<\/desc/) )
			{
				# Flush the description into de field of array
				($programsHash->{$channelNumber}->{$hashKey}->{'vdrDesc'}) = ($descriptionLine);
			
				# Leave the description mode		
				$desc = 0;
			}
			else
			{					
				if ($desc == 2)
				{
					($programsHash->{$channelNumber}->{$hashKey}->{'vdrShortTitle'}) = ($xmlLine);					
				}
				if  ($desc > 2)
				{
					$descriptionLine = "$descriptionLine|$xmlLine";
				}
				$desc++;
			}
		}
	
		if ( ($xmlLine =~ /\<\/programme/ ) && ( $chanEvent == 1 ) )
		{
			# Reinit for next '<programme > </programme>' entry
			$chanEvent = 0 ;	    
		}
    }

    # File parsed
    $fh->close();
}

sub updateEPG(\%\%\%$)
#
# Process program information and send it to VDR
# using the SVDRP protocol
#
{
    # Verbosity
    my ($channelsHash, $programsHash, $programsRepeat, $debug) = @_;

    my $reportStatus = 0;
	
	my $desc;
	
	my $title;

    # Socket used to communicate with VDR (svdrp)
    my $sock;

    # Max. seconds to wait for response
    # To avoid to block forever
    # Set alarm handler

    $sock = openSocket($host, $svdrpport, 'tcp', $debug);

    $SIG{ALRM} = sub { displayError($sock, "timeout"); };
    alarm($timeout);

    # Clear EPG data
    sendAnalyse($sock, "CLRE", $debug);


    # Parcours l'ensemble des chaines
    foreach my $channelNumber (keys %$channelsHash)
    {
		foreach my $realChannel (keys %{$channelsHash{$channelNumber}})
		{
			print "Channel: ".$channelsHash{$channelNumber}{$realChannel}{'channelName'}."\n";
		
			# Enter VDR EPG mode
			sendAnalyse($sock, "PUTE", $debug);
				
			# Send a Channel entry
			if ($channelsHash{$channelNumber}{$realChannel}{'NID'} > 0)
			{
				sendOnly($sock, "C " .
					 $channelsHash{$channelNumber}{$realChannel}{'source'} . "-" .
					 $channelsHash{$channelNumber}{$realChannel}{'NID'} . "-" .
					 $channelsHash{$channelNumber}{$realChannel}{'TID'} . "-" .
					 $channelsHash{$channelNumber}{$realChannel}{'SID'} . " " .
					 $channelsHash{$channelNumber}{$realChannel}{'channelName'});
			}
			else 
			{
				sendOnly($sock, "C " .
					 $channelsHash{$channelNumber}{$realChannel}{'source'} . "-" .
					 $channelsHash{$channelNumber}{$realChannel}{'NID'} . "-" .
					 $channelsHash{$channelNumber}{$realChannel}{'transponderFrequency'} . "-" .
					 $channelsHash{$channelNumber}{$realChannel}{'SID'} . " " .
					 $channelsHash{$channelNumber}{$realChannel}{'channelName'});
			}
		
		
			foreach my $key (keys %{$programsHash->{$channelNumber}})
			{
				print "*";
		
				# Send VDR Event
				sendOnly($sock,
					 "E $programsHash->{$channelNumber}->{$key}->{'vdrId'} ".
					 "$programsHash->{$channelNumber}->{$key}->{'vdrStartTime'} ".
					 "$programsHash->{$channelNumber}->{$key}->{'vdrLength'} 0");
		
				# XML Program Title
				$title = $programsHash->{$channelNumber}->{$key}->{'vdrTitle'};
				sendOnly($sock, "T $title");
				
				# XML Program Short Title
				if (defined($programsHash->{$channelNumber}->{$key}->{'vdrShortTitle'}))
				{
					sendOnly($sock, "S $programsHash->{$channelNumber}->{$key}->{'vdrShortTitle'}");
				}
		
				# XML program description
				if (!defined($programsHash->{$channelNumber}->{$key}->{'vdrDesc'}))
				{
					sendOnly($sock, "D Info Not Available");
				}
				else
				{
					$desc = $programsHash->{$channelNumber}->{$key}->{'vdrDesc'};
					
					#if (defined($programsRepeat->{$title}))
					#{
						if (($programsRepeat->{$title}->{'count'}) > 1)
						{
							$desc = "$desc||Diffusions:|$programsRepeat->{$title}->{'dates'}";
						}
					#}
						
					sendOnly($sock, "D $desc");					
				}
				  
				# Send VDR end of event
				sendOnly($sock, "e");
			}
			print "\n";
	
			# Send end of channel and end of EPG data
			sendOnly($sock, "c");
			# Sometimes, a strange  error occurs (451: local error in processing)
			my @updateStatus = sendReceive($sock, ".", $debug);
			if (analyseResults(@updateStatus, $debug) == 2)
			{
				print("Ignoring '".$channelsHash{$channelNumber}{$realChannel}{'channelName'}."': processing error!\n");
				$reportStatus = 1;
			}
			# It seems that vdr-1.3.18 dies when updating EPG too quickly
			#sleep(1);
		}
    }

    # Lets get out of here! :-)
    sendAnalyse($sock, "QUIT", $debug);

    if (!defined($sim))
    {
		$sock->close();
    }

    return  $reportStatus;
}



sub xmltvTranslate($)
#
# Translate HTML/XML encodings into normal characters
# For some German problems, and also English
#
{
    # $line: the line to translate
    my $line = shift;

    # German Requests - mail me with updates if some of these are wrong..
    $line=~s/ und uuml;/ü/g;
    $line=~s/ und auml;/ä/g; 
    $line=~s/ und ouml;/ö/g;
    $line=~s/ und quot;/\"/g; 
    $line=~s/ und szlig;/ß/g; 
    $line=~s/ und amp;/\&/g; 
    $line=~s/ und middot;/·/g; 
    $line=~s/ und Ouml;/Ö/g; 
    $line=~s/ und Auml;/Ä/g;
    $line=~s/ und Uuml;/Ü/g ;
    $line=~s/ und eacute;/é/g;
    $line=~s/ und aacute;/á/g;
    $line=~s/ und deg;/°/g;
    $line=~s/ und ordm;/º/g;
    $line=~s/ und ecirc;/ê/g;
    $line=~s/ und ecirc;/ê/g;
    $line=~s/ und ccedil;/ç/g;
    $line=~s/ und curren;/¤/g;
    $line=~s/und curren;/¤/g;
    $line=~s/und Ccedil;/Ç/g;
    $line=~s/ und ocirc;/ô/g;
    $line=~s/ und egrave;/è/g;
    $line=~s/ und agrave;/à/g;
    $line=~s/und quot;/\"/g;
    $line=~s/und Ouml;/Ö/g;
    $line=~s/und Uuml;/Ü/g;
    $line=~s/und Auml;/Ä/g;
    $line=~s/und ouml;/ö/g;
    $line=~s/und uuml;/ü/g;
    $line=~s/und auml;/ä/g;
 
    # English - only ever seen a problem with the Ampersand character..

    $line=~s/&amp;/&/g;
 
    return $line;
}

sub xmltime2vdr($$)
#
# Convert XMLTV time format (YYYYMMDDmmss ZZZ) into
# VDR time format (secs since epoch)
#
{
    # $xmltime: XMTV time
    # $skew   : number of minutes to add to
    #           adjust time
    my ($xmltime, $skew) = @_;

    my $secs = Date::Manip::UnixDate($xmltime, "%s");
    return $secs + ( $skew * 60 );
}


sub xmltime2string($)
#
# Convert XMLTV time format (YYYYMMDDmmss) into
# a string time format (DD-MM à hh:mm)
#
{
	my $line = shift;
	my ($themonth, $theday, $thehour, $theminutes) = ($line =~ m%[0-9]{4}([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})%);
	
	return "le ${theday}-${themonth} à ${thehour}h${theminutes}";
}


sub vdrError($$$)
#
# Display error message, close connection and exit
#
{
    my ($sock, $msg, $debug) = @_;
    # sock   : socket to close
    # $msg   : error message
    # $debug : debug level

    print STDERR "$msg\n";
    sendReceive($sock, "quit", $debug);
    if (!defined($sim))
    {
		$sock->close();
    }

    exit 1;
}



sub openSocket($$$$)
#
# Try to open a new socket and return the descriptor.
#
{
    # $host    : remote host (to connect)
    # $port    : remote port
    # $protocol: tcp or udp
    # $debug   : debug level
    my ($host, $port, $protocol, $debug) = @_;

    my $sock;

    $SIG{ALRM} = sub { displayError($sock, "timeout"); };
    alarm(60);


    # Try to connect $MAX_ATTEMPT times
    # Useful if VDR process not yet started...
    my $cpt = 0;
    my $MAX_ATTEMPT=60;

    if (!defined($sim))
    {
		while ($cpt < $MAX_ATTEMPT)
		{
			# Try to open connection
			$sock = IO::Socket::INET->new(PeerAddr => $host,
						  PeerPort => $port,
						  Proto    => $protocol);
	
			if (defined($sock) && defined($sock->connected()))
			{
				last;
			}
	
			# Not yet connected
			printf STDERR "connect: $!\n";
			sleep(1);
			$cpt++;
		}
	
		# Not yet connected -> stop trying
		if ($cpt == $MAX_ATTEMPT)
		{
			displayError($sock, "connect: $!");
		}
	}

    # Get connection result
    my @result = receiveResults($sock, $debug);
    if (analyseResults(@result, $debug))
    {
	vdrError($sock, "VDR error", $debug);
    }

    return $sock;
}




sub NameTranslate($)
#
# Translate Channels Name in a way to simplification
#
{
    my $line = shift;

	$line = uc $line;
	
    # French name simplification
	$line =~ s/[é,è,ê,ë]/E/g;		
	$line =~ s/ç/C/g;
	$line =~ s/[à,â]/A/g;
	$line =~ s/[ù,û]/U/g;
	$line =~ s/[ô,ö]/U/g;
	$line =~ s/[ï,î]/I/g;
	$line =~ s/°/O/g;
	$line =~ s/ //g;
	$line =~ s/\///g;
	$line =~ s/-//g;
	$line =~ s/&amp;/&/g;
	$line =~ s/&gt;/>/g;
	$line =~ s/&lt;/</g;
	$line =~ s/&apos;/'/g;
	$line =~ s/&quot;/"/g;	
	
     return $line;
}
