summaryrefslogtreecommitdiff
path: root/vdradmind.pl
diff options
context:
space:
mode:
authorAndreas Mair <amair.sob@googlemail.com>2005-03-06 08:11:12 +0100
committerAndreas Mair <amair.sob@googlemail.com>2005-03-06 08:11:12 +0100
commit7525bed2d315a25ac2caf95ff0bf44c905d58a7e (patch)
tree64f68331dd109cf5c92182d10bb53c614db4a73b /vdradmind.pl
downloadvdradmin-am-0.97-am1.tar.gz
vdradmin-am-0.97-am1.tar.bz2
2005-03-06: 0.97-am1 "initial release"v0.97-am1
This is mainly the lastest vdradmin (v0.97) with different patches applied: - vdradmin-0.97 has been taken from linvdr-0.7. - xpix's BigPatch_0.9pre5 (ported from vdradmin-0.95 to vdradmin-0.97 (see HISTORY.bigpatch). - included changes from vdradmin-0.95-ct-10 (see HISTORY.ct). - included vdradmin-0.95_0.9_pre5_fb1.diff (see HISTORY.macfly). - included vdradmin-0.96-rename.diff which also needs an applied "vdr-aio21_svdrprename.patch" patch (don't know the author right now). My own changes: - included missing "Was läuft heute?" template (found at www.vdr-portal.de). - fixed some rendering problems with "New Timer" and "New Autotimer" on KDE's Konqueror. - Beautified recordings listing (at least in my eyes ;-) - Added "Size" selectbox to TV template.
Diffstat (limited to 'vdradmind.pl')
-rwxr-xr-xvdradmind.pl3894
1 files changed, 3894 insertions, 0 deletions
diff --git a/vdradmind.pl b/vdradmind.pl
new file mode 100755
index 0000000..faa157c
--- /dev/null
+++ b/vdradmind.pl
@@ -0,0 +1,3894 @@
+#!/usr/bin/perl
+
+#
+# vdradmin.pl by Thomas Koch <tom@linvdr.org>
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+#
+# This program 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 program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+#
+# 08.10.2001
+#
+
+
+my $BASENAME;
+BEGIN {
+ $0 =~ /(^.*\/)/;
+ $BASENAME = $1;
+ unshift(@INC, "/usr/share/vdradmin/lib");
+ unshift(@INC, $BASENAME . "lib/");
+}
+
+use CGI qw(:no_debug);
+use IO::Socket;
+use HTML::Template::Expr();
+use Template;
+use Time::Local qw(timelocal);
+use POSIX ":sys_wait_h", qw(strftime mktime);
+use MIME::Base64();
+
+$SIG{CHLD} = sub { wait };
+
+use strict;
+#use warnings;
+
+my $SEARCH_FILES_IN_SYSTEM = (-d '/usr/share/vdradmin/lib' ? 1 : 0); # for distribution
+
+sub true () { 1 };
+sub false () { 0 };
+sub CRLF () { "\r\n" };
+sub LOG_ACCESS () { 1 };
+sub LOG_SERVERCOM () { 2 };
+sub LOG_VDRCOM () { 4 };
+sub LOG_STATS () { 8 };
+sub LOG_AT () { 16 };
+sub LOG_CHECKTIMER () { 32 };
+sub LOG_FATALERROR () { 64 };
+sub LOG_DEBUG () { 32768 };
+
+my %CONFIG;
+$CONFIG{VDR_HOST} = "localhost";
+$CONFIG{VDR_PORT} = 2001;
+$CONFIG{USERNAME} = "linvdr";
+$CONFIG{PASSWORD} = "linvdr";
+$CONFIG{GUEST_ACCOUNT} = 0;
+$CONFIG{LANGUAGE} = "Deutsch";
+$CONFIG{LOGLEVEL} = 81; # 32799
+$CONFIG{CACHE_TIMEOUT} = 60;
+$CONFIG{CACHE_LASTUPDATE} = 0;
+$CONFIG{AT_FUNC} = 1;
+$CONFIG{AT_TIMEOUT} = 120;
+$CONFIG{AT_LIFETIME} = 99;
+$CONFIG{AT_PRIORITY} = 99;
+$CONFIG{TM_LIFETIME} = 99;
+$CONFIG{TM_PRIORITY} = 99;
+$CONFIG{TM_MARGIN_BEGIN} = 10;
+$CONFIG{TM_MARGIN_END} = 10;
+$CONFIG{ST_FUNC} = 1;
+$CONFIG{ST_URL} = "";
+$CONFIG{LOGINPAGE} = 0;
+$CONFIG{LOGGING} = 0;
+$CONFIG{MOD_GZIP} = 0;
+#
+$CONFIG{LOGFILE} = "vdradmind.log";
+$CONFIG{SERVERPORT} = 8001;
+$CONFIG{RECORDINGS} = 1;
+$CONFIG{ZEITRAHMEN} = 1;
+$CONFIG{TIMES} = '18:00, 20:00, 21:00, 22:00';
+$CONFIG{EPG_DIRECT} = 1;
+$CONFIG{EPG_FILENAME} = "/video/epg.data";
+$CONFIG{SKIN} = 'bilder';
+
+my $VERSION = "0.97-am1";
+my $SERVERVERSION = "vdradmind/$VERSION";
+my $VIDEODIR = "/video";
+my $DONE = &DONE_Read || {};
+
+my($TEMPLATEDIR, $CONFFILE, $LOGFILE, $PIDFILE, $AT_FILENAME, $DONE_FILENAME, $BL_FILENAME);
+if(!$SEARCH_FILES_IN_SYSTEM) {
+ $TEMPLATEDIR = "${BASENAME}template";
+ $CONFFILE = "${BASENAME}vdradmind.conf";
+ $LOGFILE = "${BASENAME}$CONFIG{LOGFILE}";
+ $PIDFILE = "${BASENAME}vdradmind.pid";
+ $AT_FILENAME = "${BASENAME}vdradmind.at";
+ $DONE_FILENAME = "${BASENAME}vdradmind.done";
+ $BL_FILENAME = "${BASENAME}vdradmind.bl";
+} else {
+ $TEMPLATEDIR = "/usr/share/vdradmin/template";
+ $CONFFILE = "/etc/vdradmin/vdradmind.conf";
+ $LOGFILE = "/var/log/$CONFIG{LOGFILE}";
+ $PIDFILE = "/var/run/vdradmind.pid";
+ $AT_FILENAME = "/etc/vdradmin/vdradmind.at";
+ $DONE_FILENAME = "/etc/vdradmin/vdradmind.done";
+ $BL_FILENAME = "/etc/vdradmin/vdradmind.bl";
+}
+
+# IMHO a better Template Modul ;-)
+# some useful options (see below for full list)
+my $Xconfig = {
+ START_TAG => '\<\?\%', # Tagstyle
+ END_TAG => '\%\?\>', # Tagstyle
+ INCLUDE_PATH => $TEMPLATEDIR, # or list ref
+ INTERPOLATE => 1, # expand "$var" in plain text
+ PRE_CHOMP => 1, # cleanup whitespace
+ POST_CHOMP => 1, # cleanup whitespace
+ EVAL_PERL => 1, # evaluate Perl code blocks
+ CACHE_SIZE => 10000, # Tuning for Templates
+ COMPILE_EXT => 'cache', # Tuning for Templates
+ COMPILE_DIR => '/tmp', # Tuning for Templates
+
+};
+
+# create Template object
+my $Xtemplate = Template->new($Xconfig);
+# ---- End new template section ----
+
+my $I18NFILE = "i18n.pl";
+my $USE_SHELL_GZIP = true; # set on false to use the gzip library
+
+if($CONFIG{MOD_GZIP}) {
+ # lib gzipping
+ use Compress::Zlib;
+}
+
+my($DEBUG) = 0;
+my(%EPG, @CHAN, $q, $ACCEPT_GZIP, $SVDRP, $HOST);
+my(%mimehash) = (
+ html => "text/html",
+ png => "image/png",
+ gif => "image/gif",
+ jpg => "image/jpeg",
+ css => "text/css",
+ ico => "image/x-icon",
+ js => "application/x-javascript",
+ swf => "application/x-shockwave-flash"
+);
+my @LOGINPAGES = qw(prog_list prog_list2 prog_summary prog_timeline timer_list rec_list);
+
+
+$SIG{INT} = \&Shutdown;
+$SIG{TERM} = \&Shutdown;
+$SIG{HUP} = \&HupSignal;
+$SIG{PIPE} = 'IGNORE';
+
+#
+my $DAEMON = 1;
+for(my $i = 0; $i < scalar(@ARGV); $i++) {
+ $_ = $ARGV[$i];
+ if(/-h|--help/) {
+ print("Usage $0 [OPTION]...\n");
+ print("A perl client for the Linux Video Disk Recorder.\n\n");
+ print(" -nf --nofork don't fork\n");
+ print(" -c --config run configuration dialog\n");
+ print(" -k --kill kill a fork'ed vdradmin\n");
+ print(" -h --help this here\n");
+ print("\nReport bugs to <vdradmin\@linvdr.org>.\n");
+ exit(0);
+ }
+ if(/--nofork|-nf/) { $DAEMON = 0; last; }
+ if(/--config|-c/) {
+ $CONFIG{VDR_HOST} = Question("What's your VDR hostname (e.g video.intra.net)?", "localhost");
+ $CONFIG{VDR_PORT} = Question("What's the port VDR listen to SVDRP query's?", "2001");
+ $CONFIG{SERVERHOST} = Question("On which address should vdradmin listen (0.0.0.0 for any)?", "0.0.0.0");
+ $CONFIG{SERVERPORT} = Question("On which port should vdradmin answer?", "8001");
+ $CONFIG{USERNAME} = Question("Username?", "linvdr");
+ $CONFIG{PASSWORD} = Question("Password?", "linvdr");
+ $CONFIG{EPG_FILENAME} = Question("Where is your epg.data?", "/video/epg.data");
+ $CONFIG{EPG_DIRECT} = ($CONFIG{EPG_FILENAME} and -e $CONFIG{EPG_FILENAME} ? 1 : 0);
+
+ open(CONF, ">$CONFFILE") || die "Cannot open $CONFFILE: $!\n";
+ for(keys(%CONFIG)) {
+ print(CONF "$_ = $CONFIG{$_}\n");
+ }
+ close(CONF);
+
+ print("Config file sucessfull written.\n");
+ exit(0);
+ }
+ if(/--kill|-k/) {
+ kill(2, getPID($PIDFILE));
+ unlink($PIDFILE);
+ exit(0);
+ }
+ if(/--displaycall|-i/) {
+ for(my $z = 0; $z < 5; $z++) {
+ DisplayMessage($ARGV[$i+1]);
+ sleep(3);
+ }
+ CloseSocket();
+ exit(0);
+ }
+ if(/--message|-m/) {
+ DisplayMessage($ARGV[$i+1]);
+ CloseSocket();
+ exit(0);
+ }
+}
+
+ReadConfig();
+
+
+if(-e "$PIDFILE") {
+ print "There's already an copy of this program running! (pid: " . getPID($PIDFILE) . ")\n";
+ print "If you feel this is a error, remove $PIDFILE!\n";
+ exit(0);
+}
+
+if($DAEMON) {
+ my($pid) = fork;
+ if($pid != 0) {
+ print("vdradmind.pl $VERSION started with pid $pid.\n");
+ writePID($pid);
+ exit(0);
+ }
+}
+
+
+my($Socket) = IO::Socket::INET->new(
+ Proto => 'tcp',
+ LocalPort => $CONFIG{SERVERPORT},
+ LocalAddr => $CONFIG{SERVERHOST},
+ Listen => 10,
+ Reuse => 1
+);
+die("can't start server: $!\n") if (!$Socket);
+$Socket->timeout($CONFIG{AT_TIMEOUT} * 60) if($CONFIG{AT_FUNC});
+$CONFIG{CACHE_LASTUPDATE} = 0;
+
+#
+my(@I18N_Days, @I18N_Month, %ERRORMESSAGE, %COMMONMESSAGE,
+ @LOGINPAGES_DESCRIPTION, %HELP);
+LoadTranslation();
+
+UptoDate();
+
+##
+# Mainloop
+##
+my($Client, $MyURL, $Referer, $Request, $Query, $Guest);
+my @GUEST_USER = qw(prog_detail prog_list prog_list2 prog_timeline timer_list at_timer_list
+ prog_summary rec_list rec_detail show_top toolbar show_help);
+my @TRUSTED_USER = (@GUEST_USER, qw(at_timer_edit at_timer_new at_timer_save
+ at_timer_delete timer_new_form timer_add timer_delete timer_toggle rec_delete rec_rename rec_edit
+ conf_list prog_switch rc_show rc_hitk grab_picture at_timer_toggle tv_show
+ live_stream rec_stream force_update));
+
+# Force Update at start
+UptoDate(1);
+
+
+while(true) {
+ $Client = $Socket->accept();
+
+ #
+ if(!$Client) {
+ UptoDate(1);
+ next;
+ }
+
+ my $peer = $Client->peerhost;
+ my @Request = ParseRequest($Client);
+ my $raw_request = $Request[0];
+
+ $ACCEPT_GZIP = 0;
+
+ if($raw_request =~ /^GET (\/[\w\.\/-\:]*)([\?[\w=&\.\+\%-\:\!]*]*)[\#\d ]+HTTP\/1.\d$/) {
+ ($Request, $Query) = ($1, $2 ? substr($2, 1, length($2)) : undef);
+ } else {
+ Error("404", "Not Found", "The requested URL /bad_request was not found on this server.");
+ close($Client);
+ next;
+ }
+
+ # parse header
+ my($username, $password, $http_useragent);
+ for my $line (@Request) {
+ if($line =~ /Referer: (.*)/) {
+ $Referer = $1;
+ }
+ if($line =~ /Host: (.*)/) {
+ $HOST = $1;
+ }
+ if($line =~ /Authorization: basic (.*)/i) {
+ ($username, $password) = split(":", MIME::Base64::decode_base64($1), 2);
+ }
+ if($line =~ /User-Agent: (.*)/i) {
+ $http_useragent = $1;
+ }
+ if($line =~ /Accept-Encoding: (.*)/i) {
+ if($1 =~ /gzip/) {
+ $ACCEPT_GZIP = 1;
+ }
+ }
+ }
+
+ # authenticate
+ if($CONFIG{USERNAME} eq $username && $CONFIG{PASSWORD} eq $password) {
+ $Guest = 0;
+ } elsif(($CONFIG{USERNAME_GUEST} eq $username && $CONFIG{PASSWORD_GUEST} eq $password) && $CONFIG{GUEST_ACCOUNT}) {
+ $Guest = 1;
+ } else {
+ headerNoAuth();
+ close($Client);
+ next;
+ }
+
+
+ # serve request
+ $SVDRP = SVDRP->new;
+ my ($http_status, $bytes_transfered);
+ $MyURL = "." . $Request;
+ if($Request eq "/vdradmin.pl") {
+ $q = CGI->new($Query);
+ my $aktion;
+
+ my @ALLOWED_FUNCTIONS;
+ $Guest ? (@ALLOWED_FUNCTIONS = @GUEST_USER) : (@ALLOWED_FUNCTIONS = @TRUSTED_USER);
+
+ for(@ALLOWED_FUNCTIONS) {
+ ($aktion = $q->param("aktion")) if($q->param("aktion") eq $_);
+ }
+ if($aktion) {
+ eval("(\$http_status, \$bytes_transfered) = $aktion();");
+ } else {
+ # XXX redirect to no access template
+ Error("403", "Forbidden", "You don't have permission to access this function.");
+ next;
+ }
+ } elsif($Request eq "/") {
+ $MyURL = "./vdradmin.pl";
+ ($http_status, $bytes_transfered) = show_index();
+ } else {
+ ($http_status, $bytes_transfered) = SendFile($Request);
+ }
+ Log(LOG_ACCESS, access_log($Client->peerhost, $username, time(), $raw_request,
+ $http_status, $bytes_transfered, $Request, $http_useragent));
+ close($Client);
+ $SVDRP->close;
+}
+
+#############################################################################
+#############################################################################
+sub GetChannelDesc {
+ my(%hash);
+ for(@CHAN) {
+ $hash{$_->{id}} = $_->{name};
+ }
+ return(%hash);
+}
+
+sub GetChannelDescByNumber {
+ my $vdr_id = shift;
+
+ if($vdr_id) {
+ for(@CHAN) {
+ if($_->{vdr_id} == $vdr_id) {
+ return($_->{name});
+ }
+ }
+ } else { return(0); }
+}
+
+sub include {
+ my $file = shift;
+ if($file) {
+ eval(ReadFile($file));
+ }
+}
+
+sub ReadFile {
+ my $file = shift;
+ return if(!$file);
+
+ open(I18N, $file) || HTMLError("Cannot open $file!");
+ my $buf = join("", <I18N>);
+ close(I18N);
+ return($buf);
+}
+
+sub FullDay {
+ return($I18N_Days[shift]);
+}
+
+sub FullMonth {
+ return($I18N_Month[shift()-1]);
+}
+
+sub GetChannelID {
+ my($sid) = $_[0];
+ for(@CHAN) {
+ if($_->{id} == $sid) {
+ return($_->{number});
+ }
+ }
+}
+
+sub EURL {
+ my($text) = @_;
+ $text =~ s/([^0-9a-zA-Z])/sprintf("%%%2.2x", ord($1))/ge;
+ return($text);
+}
+
+sub HTMLError {
+ my $error = join("", @_);
+ my $template = HTML::Template->new(
+ filename => "$TEMPLATEDIR/$CONFIG{LANGUAGE}/error.html");
+ $template->param(error => $error);
+ $CONFIG{CACHE_LASTUPDATE} = 0;
+ return(header("200", "text/html", $template->output));
+}
+
+
+sub FillInZero {
+ my($str, $length) = @_;
+ while(length($str) < $length) {
+ $str = "0$str";
+ }
+ return($str);
+}
+
+sub MHz {
+ my $frequency = shift;
+ while($frequency > 20000) {
+ $frequency /= 1000;
+ }
+ return(int($frequency));
+}
+
+sub ChanTree {
+ undef(@CHAN);
+ $SVDRP->command("lstc");
+ while($_ = $SVDRP->readoneline) {
+ chomp;
+ my($vdr_id, $temp) = split(/ /, $_, 2);
+ my($name, $frequency, $polarization, $source, $symbolrate, $vpid, $apid,
+ $tpid, $ca, $service_id, $nid, $tid, $rid) = split(/\:/, $temp);
+ $name =~ /(^[^,;]*).*/; #TODO?
+ $name = $1;
+ push(@CHAN, {
+ vdr_id => $vdr_id,
+ name => $name,
+ frequency => MHz($frequency),
+ polarization => $polarization,
+ source => $source,
+ symbolrate => $symbolrate,
+ vpid => $vpid,
+ apid => $apid,
+ tpid => $tpid,
+ ca => $ca,
+ service_id => $service_id,
+ nid => $nid,
+ tid => $tid,
+ rid => $rid
+ });
+ }
+}
+
+sub get_vdrid_from_channelid {
+ my $channel_id = shift;
+ if($channel_id =~ /^(\d*)$/) { # vdr 1.0.x & >= vdr 1.1.15
+ for my $channel (@CHAN) {
+ if($channel->{service_id} == $1) {
+ return($channel->{vdr_id});
+ }
+ }
+ } elsif($channel_id =~ /^(.*)-(.*)-(.*)-(.*)-(.*)$/) {
+ for my $channel (@CHAN) {
+ if($channel->{source} eq $1 &&
+ $channel->{nid} == $2 &&
+ ($channel->{nid} ? $channel->{tid} : $channel->{frequency}) == $3 &&
+ $channel->{service_id} == $4 &&
+ $channel->{rid} == $5) {
+ return($channel->{vdr_id});
+ }
+ }
+ } elsif($channel_id =~ /^(.*)-(.*)-(.*)-(.*)$/) {
+ for my $channel (@CHAN) {
+ if($channel->{source} eq $1 &&
+ $channel->{nid} == $2 &&
+ ($channel->{nid} ? $channel->{tid} : $channel->{frequency}) == $3 &&
+ $channel->{service_id} == $4) {
+ return($channel->{vdr_id});
+ }
+ }
+ } else {
+ print "Can't find channel_id $channel_id\n";
+ }
+}
+
+sub get_name_from_vdrid {
+ my $vdr_id = shift;
+ if($vdr_id) {
+ # Kanalliste nach identischer vdr_id durchsuchen
+ my @C = grep($_->{vdr_id} == $vdr_id, @CHAN);
+ # Es darf nach Spec nur eine ‹bereinstimmung geben
+ if(scalar(@C) == 1) {
+ return $C[0]->{name};
+ }
+ }
+}
+
+sub get_transponder_from_vdrid {
+ my $vdr_id = shift;
+ if($vdr_id) {
+ # Kanalliste nach identischer vdr_id durchsuchen
+ my @C = grep($_->{vdr_id} == $vdr_id, @CHAN);
+ # Es darf nach Spec nur eine ‹bereinstimmung geben
+ if(scalar(@C) == 1) {
+ return("$C[0]->{source}-$C[0]->{frequency}-$C[0]->{polarization}");
+ }
+ }
+}
+
+sub get_ca_from_vdrid {
+ my $vdr_id = shift;
+ if($vdr_id) {
+ # Kanalliste nach identischer vdr_id durchsuchen
+ my @C = grep($_->{vdr_id} == $vdr_id, @CHAN);
+ # Es darf nach Spec nur eine ‹bereinstimmung geben
+ if(scalar(@C) == 1) {
+ return($C[0]->{ca});
+ }
+ }
+}
+
+#############################################################################
+# EPG functions
+#############################################################################
+
+sub EPG_getEntry {
+ my $vdr_id = shift;
+ my $epg_id = shift;
+ if($vdr_id && $epg_id) {
+ for(@{$EPG{$vdr_id}}) {
+ #if($_->{id} == $epg_id) {
+ if($_->{event_id} == $epg_id) {
+ return($_);
+ }
+ }
+ }
+}
+
+sub getNumberOfElements {
+ my $ref = shift;
+ if($ref) {
+ return(@{$ref});
+ } else {
+ return(0);
+ }
+}
+
+sub getElement {
+ my $ref = shift;
+ my $index = shift;
+ if($ref) {
+ return($ref->[$index]);
+ } else {
+ return;
+ }
+}
+
+sub EPG_buildTree {
+ $SVDRP->command("lste");
+ my($i, @events);
+ my($id, $bc) = (1, 0);
+ undef(%EPG);
+ while($_ = $SVDRP->readoneline) {
+ chomp;
+ if(/^C ([^ ]+) *(.*)/) {
+ $bc++;
+ undef(@events);
+ my($channel_id, $channel_name) = ($1, $2);
+ my $vdr_id = get_vdrid_from_channelid($channel_id);
+ while($_ = $SVDRP->readoneline) {
+ if(/^E (.*) (.*) (.*) (.*)/ || /^E (.*) (.*) (.*)/) {
+ my($event_id, $time, $duration) = ($1, $2, $3);
+ my($title, $subtitle, $summary);
+ while($_ = $SVDRP->readoneline) {
+ if(/^T (.*)/) { $title = $1; $title =~ s/\|/<br>/sig }
+ if(/^S (.*)/) { $subtitle = $1; $subtitle =~ s/\|/<br>/sig }
+ if(/^D (.*)/) { $summary = $1; $summary =~ s/\|/<br>/sig }
+ if(/^e/) {
+ #
+ push(@events, {
+ channel_name => $channel_name,
+ start => $time,
+ stop => $time + $duration,
+ duration => $duration,
+ title => $title,
+ subtitle => $subtitle,
+ summary => $summary,
+ id => $id,
+ vdr_id => $vdr_id,
+ event_id => $event_id
+ });
+ $id++;
+ last;
+ }
+ }
+ } elsif(/^c/) {
+ my($last) = 0;
+ my(@temp);
+ for(sort({ $a->{start} <=> $b->{start} } @events)) {
+ next if($last == $_->{start});
+ push(@temp, $_);
+ $last = $_->{start};
+ }
+ $EPG{$vdr_id} = [ @temp ];
+ last;
+ }
+ }
+ }
+ }
+ Log(LOG_STATS, "EPGTree: $id events, $bc broadcasters");
+}
+
+
+#############################################################################
+# Socket functions
+#############################################################################
+
+sub PrintToClient {
+ my $string = join("", @_);
+ return if(!defined($string));
+ print($Client $string) if($Client);
+}
+
+sub ParseRequest {
+ my $Socket = shift;
+ my (@Request, $Line);
+ do {
+ $Line = <$Socket>;
+ $Line =~ s/\r\n//g;
+ push(@Request, $Line);
+ } while($Line);
+ return(@Request);
+}
+
+sub CloseSocket {
+ $SVDRP->close();
+}
+
+sub OpenSocket {
+ $SVDRP = SVDRP->new;
+}
+
+sub SendCMD {
+ my $cmd = join("", @_);
+
+ OpenSocket() if(!$SVDRP);
+
+ my @output;
+ $SVDRP->command($cmd);
+ while($_ = $SVDRP->readoneline) {
+ push(@output, $_);
+ }
+ return(@output);
+}
+
+sub mygmtime() {
+ gmtime;
+}
+
+sub headerTime {
+ my $time = shift;
+ $time = time() if(!$time);
+ my @weekdays = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
+ my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+
+ return(
+ sprintf("%s, %s %s %s %02d:%02d:%02d GMT",
+ $weekdays[my_strfgmtime("%w", $time)],
+ my_strfgmtime("%d", $time),
+ $months[my_strfgmtime("%m", $time) - 1],
+ my_strfgmtime("%Y", $time),
+ my_strfgmtime("%H", $time),
+ my_strfgmtime("%M", $time),
+ my_strfgmtime("%S", $time)
+ )
+ );
+}
+
+sub GZip {
+ my $content = shift;
+ my $filename = "/tmp/vdradmin." . time();
+ open(PIPE, "| gzip -9 - > $filename") || die "cant open pipe to gzip ($!)";
+ print PIPE $$content;
+ close(PIPE);
+
+ open(FILE, $filename) || die "cant open $filename ($1)";
+ my $result = join("", <FILE>);
+ close(FILE);
+
+ unlink($filename);
+
+ #my $pid = open2(*RDFH, *WTFH, "gzip -1 -c -");
+ #print "Write\n";
+ #print WTFH $$content;
+ #print "Done\n";
+ #close(WTFH);
+ #my $result = join("", <RDFH>);
+ #close(RDFH);
+ #waitpid($pid, 0);
+
+ return($result);
+}
+
+sub LibGZip {
+ my $content = shift;
+ return(Compress::Zlib::memGzip($$content));
+}
+
+sub header {
+ my($status, $ContentType, $data, $caching) = @_;
+ Log(LOG_STATS, "Template Error: ".$Xtemplate->error())
+ if($status >= 500);
+ if($ACCEPT_GZIP && $CONFIG{MOD_GZIP}) {
+ if($USE_SHELL_GZIP) {
+ $data = GZip(\$data);
+ } else {
+ $data = LibGZip(\$data);
+ }
+ }
+
+ my $status_text = " OK" if($status eq "200");
+
+ PrintToClient("HTTP/1.0 $status$status_text", CRLF);
+ PrintToClient("Date: ", headerTime(), CRLF);
+ if(!$caching) {
+ PrintToClient("Expires: Mon, 26 Jul 1997 05:00:00 GMT", CRLF);
+ PrintToClient("Cache-Control: max-age=0", CRLF);
+ } else {
+ PrintToClient("Expires: ", headerTime(time() + 3600), CRLF);
+ PrintToClient("Cache-Control: max-age=3600", CRLF);
+ }
+ PrintToClient("Server: $SERVERVERSION", CRLF);
+ PrintToClient("Content-Length: ", length($data), CRLF) if($data);
+ PrintToClient("Connection: close", CRLF);
+ PrintToClient("Content-encoding: gzip", CRLF) if($CONFIG{MOD_GZIP} && $ACCEPT_GZIP);
+ PrintToClient("Content-type: $ContentType", CRLF, CRLF) if($ContentType);
+ PrintToClient($data) if($data);
+ return($status, length($data));
+}
+
+sub headerForward {
+ my $url = shift;
+ PrintToClient("HTTP/1.0 302 Found", CRLF);
+ PrintToClient("Date: ", headerTime(), CRLF);
+ PrintToClient("Server: $SERVERVERSION", CRLF);
+ PrintToClient("Connection: close", CRLF);
+ PrintToClient("Location: $url", CRLF);
+ PrintToClient("Content-type: text/html", CRLF, CRLF);
+ return(302, 0);
+}
+
+sub headerNoAuth {
+ my $template = TemplateNew("noauth.html");
+ my $data = $template->output;
+ PrintToClient("HTTP/1.0 401 Authorization Required", CRLF);
+ PrintToClient("Date: ", headerTime(), CRLF);
+ PrintToClient("Server: $SERVERVERSION", CRLF);
+ PrintToClient("WWW-Authenticate: Basic realm=\"vdradmind\"", CRLF);
+ PrintToClient("Content-Length: ", length($data), CRLF) if($data);
+ PrintToClient("Connection: close", CRLF);
+ PrintToClient("Content-type: text/html", CRLF, CRLF);
+ PrintToClient($data);
+ return(401, length($data));
+}
+
+sub Error {
+ my $template = HTML::Template->new(
+ filename => "$TEMPLATEDIR/$CONFIG{LANGUAGE}/noperm.html");
+ $template->param(
+ title => $_[0],
+ h1 => $_[1],
+ error => $_[2],
+ );
+ return(header("$_[0] $_[1]", "text/html", $template->output));
+}
+
+sub SendFile {
+ my($File) = @_;
+ my($buf, $temp);
+ $File =~ s/^\///;
+ $File =~ s/^bilder/$CONFIG{SKIN}/i
+ if(defined $CONFIG{SKIN});
+ my $FileWithPath = sprintf("%s/%s/%s",
+# my $FileWithPath = sprintf("%s/%s/%s/%s",
+# $BASENAME,
+ $TEMPLATEDIR,
+ $CONFIG{LANGUAGE},
+ $File);
+
+ # Skin css file
+ $FileWithPath = sprintf('%s/%s/%s/%s', $TEMPLATEDIR, $CONFIG{LANGUAGE}, $CONFIG{SKIN}, $File)
+ if((split('[/\.]',$File))[-1] eq 'css' and -e sprintf('%s/%s/%s/%s', $TEMPLATEDIR, $CONFIG{LANGUAGE}, $CONFIG{SKIN}, $File));
+
+ if(-e $FileWithPath) {
+ if(-r $FileWithPath) {
+ $buf = ReadFile($FileWithPath);
+ $temp = $File;
+ $temp =~ /([A-Za-z0-9]+)\.([A-Za-z0-9]+)/;
+ if(!$mimehash{$2}) { die("can't find mime-type \'$2\'\n"); }
+ return(header("200", $mimehash{$2}, $buf, 1));
+ } else {
+ Error("403", "Forbidden", "You don't have permission to access /$File on this server.");
+ }
+ } else {
+ Error("404", "Not Found", "The requested URL /$File was not found on this server.");
+ }
+}
+
+#############################################################################
+# autotimer functions
+#############################################################################
+sub AT_Read {
+ my(@at);
+ if(-e $AT_FILENAME) {
+ open(AT_FILE, $AT_FILENAME) ||
+ HTMLError("Cant open $AT_FILENAME!");
+ while(<AT_FILE>) {
+ chomp;
+ next if($_ eq "");
+ my($active, $pattern, $section, $start, $stop, $episode, $prio, $lft, $channel, $directory, $done) = split(/\:/, $_);
+ push(@at, {
+ active => $active,
+ pattern => $pattern,
+ section => $section,
+ start => $start,
+ stop => $stop,
+ episode => $episode,
+ prio => $prio,
+ lft => $lft,
+ channel => $channel,
+ directory => $directory,
+ done => $done
+ });
+ }
+ close(AT_FILE);
+ }
+ return(@at);
+}
+
+sub AT_Write {
+ my @at = @_;
+ open(AT_FILE, ">" . $AT_FILENAME) ||
+ HTMLError("Cant open $AT_FILENAME!");
+ for(@at) {
+ my $temp;
+ for my $item (qw(active pattern section start stop episode prio lft channel directory done)) {
+ $_->{$item} =~ s/\:/_/g;
+ if(length($temp) == 0) {
+ $temp = $_->{$item};
+ } else {
+ $temp .= ":" . $_->{$item};
+ }
+ }
+ print AT_FILE $temp, "\n";
+ }
+ close(AT_FILE);
+}
+
+sub DONE_Write {
+ my $done = shift || return;
+ open(DONE_FILE, ">" . $DONE_FILENAME) || HTMLError("Cant open $DONE_FILENAME!");
+ foreach my $n (sort keys %$done) {
+ printf DONE_FILE "%s::%d::%s\n", $n, $done->{$n}, scalar localtime($done->{$n});
+ };
+ close(DONE_FILE);
+}
+
+sub DONE_Read {
+ my $done;
+ if(-e $DONE_FILENAME) {
+ open(DONE_FILE, $DONE_FILENAME) || HTMLError("Cant open $AT_FILENAME!");
+ while(<DONE_FILE>) {
+ chomp;
+ next if($_ eq "");
+ my @line = split('\:\:', $_);
+ $done->{$line[0]} = $line[1];
+ }
+ close(DONE_FILE);
+ }
+ return $done;
+}
+
+sub BlackList_Read {
+ my %blacklist;
+ if(-e $BL_FILENAME) {
+ open(BL_FILE, $BL_FILENAME) || HTMLError("Cant open $BL_FILENAME!");
+ while(<BL_FILE>) {
+ chomp;
+ next if($_ eq "");
+ $blacklist{$_} = 1;
+ }
+ close(BL_FILE);
+ }
+ return %blacklist;
+}
+
+
+sub AutoTimer {
+ return if(!$CONFIG{AT_FUNC});
+ Log(LOG_AT, "Auto Timer: Scanning for events...");
+ my($search, $start, $stop) = @_;
+
+ my @at = AT_Read();
+ $DONE = &DONE_Read unless($DONE);
+ my %blacklist = &BlackList_Read;
+
+ # Merken der wanted Channels (geht schneller
+ # bevor das immer wieder in der unteren Schleife gemacht wird).
+ my $wanted;
+ for my $n ( split( ",", $CONFIG{CHANNELS_WANTED} ) ) {
+ $wanted->{$n} = 1;
+ }
+
+ # Die Timerliste holen
+ my $timer;
+ foreach my $t (ParseTimer(0)){
+ my $key = sprintf('%d:%s:%s',
+ $t->{vdr_id},
+ $t->{title}
+ );
+ $timer->{$key} = $t;
+ }
+
+ for my $sender (keys(%EPG)) {
+ for my $event (@{$EPG{$sender}}) {
+ for my $at (@at) {
+ if(!$at->{active}) {
+ next;
+ }
+
+ # Ein Timer der schon programmmiert wurde kann
+ # ignoriert werden
+ next if($event->{event_id} == $timer->{event_id});
+
+ # Wenn CHANNELS_WANTED_AUTOTIMER dann next wenn der Kanal
+ # nicht in der WantedList steht
+ if($CONFIG{CHANNELS_WANTED_AUTOTIMER}) {
+ next unless defined $wanted->{ $event->{vdr_id} };
+ }
+
+ if($at->{channel}) {
+ if($at->{channel} != $event->{vdr_id}) {
+ next;
+ }
+ }
+
+ # Hamwa schon gehabt?
+ my $DoneStr = sprintf('%s~%s',
+ $event->{title},
+ ($event->{subtitle} ? $event->{subtitle} : ''),
+ );
+
+ if(exists $DONE->{$DoneStr}) {
+ Log(LOG_DEBUG, sprintf("Auto Timer: already done \"%s\"", $DoneStr));
+ next;
+ }
+
+ # Wollen wir nicht haben.
+ my $BLStr = $event->{title};
+ $BLStr .= "~" . $event->{subtitle} if $event->{subtitle};
+
+ if($blacklist{$BLStr} eq 1 || $blacklist{$event->{title}} eq 1) {
+ Log(LOG_DEBUG, sprintf("Auto Timer: blacklisted \"%s\"", $event->{title}));
+ next;
+ }
+
+
+
+ my $SearchStr;
+ if($at->{section} & 1) {
+ $SearchStr = $event->{title};
+ }
+ if(($at->{section} & 2) && defined($event->{subtitle})) {
+ $SearchStr .= "~" . $event->{subtitle};
+ }
+ if($at->{section} & 4) {
+ $SearchStr .= "~" . $event->{summary};
+ }
+
+ # Regular Expressions are surrounded by slashes -- everything else
+ # are search patterns
+ if($at->{pattern} =~ /^\/(.*)\/(i?)$/) {
+ # We have a RegExp
+ Log(LOG_DEBUG, sprintf("Auto Timer: Checking RegExp \"%s\"", $at->{pattern}));
+
+ if((! length($SearchStr)) || (! length($1))) {
+ Log(LOG_DEBUG, "No search string or regexp, skipping!");
+ next;
+ }
+
+ next if(!defined($1));
+ # Shall we search case insensitive?
+ if(($2 eq "i") && ($SearchStr !~ /$1/i)) {
+ next;
+ } elsif(($2 ne "i") && ($SearchStr !~ /$1/)) {
+ next;
+ } else {
+ Log(LOG_AT, sprintf("Auto Timer: RegExp \"%s\" matches \"%s\"", $at->{pattern}, $SearchStr));
+ }
+ } else {
+ # We have a search pattern
+ Log(LOG_DEBUG, sprintf("Auto Timer: Checking pattern \"%s\"", $at->{pattern}));
+
+ # Escape special characters within the search pattern
+ my $atpattern = $at->{pattern};
+ #$atpattern =~ s/([\+\?\.\*\^\$\(\)\[\]\{\}\|\\])/\\\1/g;
+ $atpattern =~ s/([\+\?\.\*\^\$\(\)\[\]\{\}\|\\])/\\$1/g;
+
+ Log(LOG_DEBUG, sprintf("Auto Timer: Escaped pattern: \"%s\"", $atpattern));
+
+ if((! length($SearchStr)) || (! length($atpattern))) {
+ Log(LOG_DEBUG, "No search string or pattern, skipping!");
+ next;
+ }
+ # split search pattern at spaces into single sub-patterns, and
+ # test for all of them (logical "and")
+ my $fp = 1;
+ for my $pattern (split(/ +/, $atpattern)) {
+ # search for each sub-pattern, case insensitive
+ if($SearchStr !~ /$pattern/i) {
+ $fp = 0;
+ } else {
+ Log(LOG_DEBUG, sprintf("Auto Timer: Found matching pattern: \"%s\"", $pattern));
+ }
+ }
+ next if(!$fp);
+ }
+
+ Log(LOG_DEBUG, sprintf("Auto Timer: Comparing pattern \"%s\" (%s - %s) with event \"%s\" (%s - %s)",
+ $at->{pattern}, $at->{start}, $at->{stop},
+ $event->{title}, my_strftime("%H%M", $event->{start}), my_strftime("%H%M", $event->{stop})));
+ # Do we have a time slot?
+ if($at->{start}) {
+ # We have a start time and possibly a stop time for the auto timer
+ # Do we have Midnight between AT start and stop time?
+ if(($at->{stop}) && ($at->{stop} < $at->{start})) {
+ # The AT includes Midnight
+ Log(LOG_DEBUG, "922: AT includes Midnight");
+ # Do we have Midnight between Event start and stop?
+ if(my_strftime("%H%M", $event->{stop}) < my_strftime("%H%M", $event->{start})) {
+ # The event includes Midnight
+ Log(LOG_DEBUG, "926: Event includes Midnight");
+ if(my_strftime("%H%M", $event->{start}) < $at->{start}) {
+ Log(LOG_DEBUG, "924: Event starts before AT start");
+ next;
+ }
+ if(my_strftime("%H%M", $event->{stop}) > $at->{stop}) {
+ Log(LOG_DEBUG, "932: Event ends after AT stop");
+ next;
+ }
+ } else {
+ # Normal event not spreading over Midnight
+ Log(LOG_DEBUG, "937: Event does not includes Midnight");
+ if(my_strftime("%H%M", $event->{start}) < $at->{start}) {
+ if(my_strftime("%H%M", $event->{start}) > $at->{stop}) {
+ # The event starts before AT start and after AT stop
+ Log(LOG_DEBUG, "941: Event starts before AT start and after AT stop");
+ next;
+ }
+ if(my_strftime("%H%M", $event->{stop}) > $at->{stop}) {
+ # The event ends after AT stop
+ Log(LOG_DEBUG, "946: Event ends after AT stop");
+ next;
+ }
+ }
+ }
+ } else {
+ # Normal auto timer, not spreading over midnight
+ Log(LOG_DEBUG, "953: AT does not include Midnight");
+ # Is the event spreading over midnight?
+ if(my_strftime("%H%M", $event->{stop}) < my_strftime("%H%M", $event->{start})) {
+ # Event spreads midnight
+ if($at->{stop}) {
+ # We have a AT stop time defined before midnight -- no match
+ Log(LOG_DEBUG, "959: Event includes Midnight, Autotimer not");
+ next;
+ }
+ } else {
+ # We have a normal event, nothing special
+ # Event must not start before AT start
+ if(my_strftime("%H%M", $event->{start}) < $at->{start}) {
+ Log(LOG_DEBUG, "963: Event starts before AT start");
+ next;
+ }
+ # Event must not end after AT stop
+ if(($at->{stop}) && (my_strftime("%H%M", $event->{stop}) > $at->{stop})) {
+ Log(LOG_DEBUG, "968: Event ends after AT stop");
+ next;
+ }
+ }
+ }
+ } else {
+ # We have no AT start time
+ if($at->{stop}) {
+ if(my_strftime("%H%M", $event->{stop}) > $at->{stop}) {
+ Log(LOG_DEBUG, "977: Only AT stop time, Event stops after AT stop");
+ next;
+ }
+ }
+ }
+
+ Log(LOG_AT, sprintf("Auto Timer: Found \"%s\"", $at->{pattern}));
+
+#########################################################################################
+# 20050130: patch by macfly: parse extended EPG information provided by tvm2vdr.pl
+#########################################################################################
+
+ my $title = $event->{title};
+ my %at_details;
+
+ if($at->{directory}) {
+ $title = $at->{directory};
+ $at_details{'title'} = $event->{title};
+ $at_details{'subtitle'} = $event->{subtitle} ? $event->{subtitle} : my_strftime("%Y-%m-%d", $event->{start});
+ $at_details{'date'} = my_strftime("%Y-%m-%d", $event->{start});
+ $at_details{'regie'} = $1 if $event->{summary} =~ m/\|Director: (.*?)\|/;
+ $at_details{'category'} = $1 if $event->{summary} =~ m/\|Category: (.*?)\|/;
+ $at_details{'genre'} = $1 if $event->{summary} =~ m/\|Genre: (.*?)\|/;
+ $at_details{'year'} = $1 if $event->{summary} =~ m/\|Year: (.*?)\|/;
+ $at_details{'country'} = $1 if $event->{summary} =~ m/\|Country: (.*?)\|/;
+ $at_details{'originaltitle'} = $1 if $event->{summary} =~ m/\|Originaltitle: (.*?)\|/;
+ $at_details{'fsk'} = $1 if $event->{summary} =~ m/\|FSK: (.*?)\|/;
+ $at_details{'episode'} = $1 if $event->{summary} =~ m/\|Episode: (.*?)\|/;
+ $at_details{'rating'} = $1 if $event->{summary} =~ m/\|Rating: (.*?)\|/;
+ $title =~ s/%([\w_-]+)%/$at_details{lc($1)}/sieg;
+ } else {
+ $title = $event->{title};
+ if(($at->{episode}) && ($event->{subtitle})) {
+ $title .= "~" . $event->{subtitle};
+ }
+ }
+
+
+ # gemaess vdr.5 alle : durch | ersetzen.
+ $title =~s#:#|#g;
+
+ # sind irgendweche Tags verwendet worden, die leer waren und die doppelte Verzeichnisse erzeugten?
+ $title =~s#~+#~#g;
+
+#########################################################################################
+# 20050130: patch by macfly: parse extended EPG information provided by tvm2vdr.pl
+#########################################################################################
+
+ Log(LOG_AT, sprintf("AutoTimer: Programming Timer \"%s\" (Event-ID %s, %s - %s)", $title, $event->{event_id}, strftime("%Y%m%d-%H%M", localtime($event->{start})), strftime("%Y%m%d-%H%M", localtime($event->{stop}))));
+
+ AT_ProgTimer(0x8001, $event->{event_id}, $event->{vdr_id}, $event->{start}, $event->{stop},
+ $title, $event->{summary}, $at->{prio}, $at->{lft});
+
+ $DONE->{$DoneStr} = $event->{stop}
+ if($at->{done});
+ }
+ }
+ }
+ Log(LOG_AT, "Auto Timer: Done.");
+ Log(LOG_AT, "Auto Timer: Search for old Done Entrys...");
+ for(keys %$DONE) { delete $DONE->{$_} if(time > $DONE->{$_}) }
+ Log(LOG_AT, "Auto Timer: Save done list...");
+ &DONE_Write($DONE) if($DONE);
+ Log(LOG_AT, "Auto Timer: Done.");
+}
+
+
+sub AT_ProgTimer {
+ my($active, $event_id, $channel, $start, $stop, $title, $summary, $prio, $lft) = @_;
+
+ $title =~ s/\:/ /g;
+
+ $start -= ($CONFIG{TM_MARGIN_BEGIN} * 60);
+ $stop += ($CONFIG{TM_MARGIN_END} * 60);
+
+ ($prio = $CONFIG{AT_PRIORITY}) if(!$prio);
+ ($lft = $CONFIG{AT_LIFETIME}) if(!$lft);
+
+ my $found = 0;
+ my $Update = 0;
+ for(ParseTimer(1)) {
+ if(($event_id) && ($_->{event_id} == $event_id) && ($_->{vdr_id} == $channel)) {
+ $found = 1;
+ }
+ if((!$found) && ($_->{vdr_id} == $channel) && ($_->{dor} == my_strftime("%d", $start)) && ($_->{start} eq $start)) {
+ $found = 1;
+ }
+ }
+
+ # we will only programm new timers, CheckTimers is responsible for
+ # updating existing timers
+ if (!$found) {
+ Log(LOG_AT, sprintf("AT_ProgTimer: Programming Timer \"%s\" (Event-ID %s, %s - %s)", $title, $event_id, strftime("%Y%m%d-%H%M", localtime($start)), strftime("%Y%m%d-%H%M", localtime($stop))));
+ ProgTimer(
+ 0,
+ $active,
+ $event_id,
+ $channel,
+ $start,
+ $stop,
+ $prio,
+ $lft,
+ $title,
+ $summary
+ );
+ }
+}
+
+sub PackStatus {
+ # make a 32 bit signed int with high 16 Bit as event_id and low 16 Bit as
+ # active value
+ my($active, $event_id) = @_;
+
+ # we must generate a 32 bit signed int, due perl knows no overflow at 32 bit,
+ # we have to do the overflow manually:
+
+ # is the 16th bit set? then the signed 32 bit int is negative!
+ if ($event_id & 0x8000) {
+ # strip the first bit (by & 0x7FFF) of the event_id, so a 15 bit
+ # (positive) int will remain, then shift the int 16 bits to the left and
+ # add active -- result is a 31 bit (always positive) int.
+ # The 32nd bit is the minus sign, and due the (binary) smallest value
+ # is the (int) lowest possible number, we have to substract the lowest
+ # value + 1 from the 31 bit value -- result is the signed 32 bit int equal
+ # to the (unsigned) 32 bit int.
+ return ($active | (($event_id & 0x7FFF) << 16)) - 0x80000000;
+ }
+ else {
+ return $active | ($event_id << 16);
+ }
+}
+
+sub UnpackActive {
+ my($tmstatus) = @_;
+ # strip the first 16 bit
+ return ($tmstatus & 0xFFFF);
+}
+
+sub UnpackEvent_id {
+ my($tmstatus) = @_;
+ # remove the lower 16 bit by shifting the value 16 bits to the right
+ return $tmstatus >> 16;
+}
+
+sub CheckTimers {
+ my $event;
+
+ for my $timer (ParseTimer(1)) {
+ # only check autotimers (16th bit set) with event_id
+ if( ($timer->{active} & 0x8000) && ($timer->{event_id})) {
+ for $event (@{$EPG{$timer->{vdr_id}}}) {
+ # look for matching event_id on the same channel -- it's unique
+ if($timer->{event_id} == $event->{event_id}) {
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Checking timer \"%s\" (No. %s) for changes by Event-ID", $timer->{title}, $timer->{id}));
+ # update timer if the existing one differs from the EPG
+ #if(($timer->{title} ne ($event->{subtitle} ? ($event->{title} . "~" . $event->{subtitle}) : $event->{title})) ||
+ # (($event->{summary}) && (!$timer->{summary})) ||
+ if((($event->{summary}) && (!$timer->{summary})) ||
+ ($timer->{start} ne ($event->{start} - $CONFIG{TM_MARGIN_BEGIN} * 60)) ||
+ ($timer->{stop} ne ($event->{stop} + $CONFIG{TM_MARGIN_END} * 60))) {
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Timer \"%s\" (No. %s, Event-ID %s, %s - %s) differs from EPG: \"%s\", Event-ID %s, %s - %s)", $timer->{title}, $timer->{id}, $timer->{event_id}, strftime("%Y%m%d-%H%M", localtime($timer->{start})), strftime("%Y%m%d-%H%M", localtime($timer->{stop})), $event->{title}, $event->{event_id}, strftime("%Y%m%d-%H%M", localtime($event->{start})), strftime("%Y%m%d-%H%M", localtime($event->{stop}))));
+ ProgTimer(
+ $timer->{id},
+ $timer->{active},
+ $timer->{event_id},
+ $timer->{vdr_id},
+ $event->{start} - $CONFIG{TM_MARGIN_BEGIN} * 60,
+ $event->{stop} + $CONFIG{TM_MARGIN_END} * 60,
+ $timer->{prio},
+ $timer->{lft},
+ # always add subtitle if there is one
+ #$event->{subtitle} ? ($event->{title} . "~" . $event->{subtitle}) : $event->{title},
+ $timer->{title},
+ # If there already is a summary, the user might have changed it -- leave it untouched.
+ $timer->{summary} ? $timer->{summary} : $event->{summary},
+ );
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Timer %s updated.", $timer->{id}));
+ }
+ }
+ }
+ }
+ # all autotimers without event_id will be updated by channel number and start/stop time
+ elsif( ($timer->{active} & 0x8000) && (!$timer->{event_id})) {
+ # We're checking only timers which doesn't record
+ if ($timer->{start} > time()) {
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Checking timer \"%s\" (No. %s) for changes by recording time", $timer->{title}, $timer->{id}));
+ my @eventlist;
+
+ for my $event (@{$EPG{$timer->{vdr_id}}}) {
+ # look for events within the margins of the current timer
+ if(($event->{start} < $timer->{stop}) && ($event->{stop} > $timer->{start})) {
+ push @eventlist, $event;
+ }
+ }
+ # now we have all events in eventlist that touch the old timer margins
+ # check for each event how probable it is matching the old timer
+ if(scalar(@eventlist) > 0) {
+ my $maxwight = 0;
+ $event = $eventlist[0];
+
+ for (my $i=0; $i < scalar(@eventlist); $i++) {
+ my($start, $stop);
+
+ if($eventlist[$i]->{start} < $timer->{start}) {
+ $start = $timer->{start};
+ } else {
+ $start = $eventlist[$i]->{start};
+ }
+ if($eventlist[$i]->{stop} > $timer->{stop}) {
+ $stop = $timer->{stop};
+ } else {
+ $stop = $eventlist[$i]->{stop};
+ }
+
+ my $wight = ($stop - $start) / ($eventlist[$i]->{stop} - $eventlist[$i]->{start});
+
+ if($wight > $maxwight) {
+ $maxwight = $wight;
+ $event = $eventlist[$i];
+ }
+ }
+ # update timer if the existing one differs from the EPG
+ if((($event->{summary}) && (!$timer->{summary})) ||
+ ($timer->{start} > ($event->{start} - $CONFIG{TM_MARGIN_BEGIN} * 60)) ||
+ ($timer->{stop} < ($event->{stop} + $CONFIG{TM_MARGIN_END} * 60))) {
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Timer \"%s\" (No. %s, Event-ID %s, %s - %s) differs from EPG: \"%s\", Event-ID %s, %s - %s)", $timer->{title}, $timer->{id}, $timer->{event_id}, strftime("%Y%m%d-%H%M", localtime($timer->{start})), strftime("%Y%m%d-%H%M", localtime($timer->{stop})), $event->{title}, $event->{event_id}, strftime("%Y%m%d-%H%M", localtime($event->{start})), strftime("%Y%m%d-%H%M", localtime($event->{stop}))));
+ ProgTimer(
+ $timer->{id},
+ $timer->{active},
+ 0,
+ $timer->{vdr_id},
+ $timer->{start} > ($event->{start} - $CONFIG{TM_MARGIN_BEGIN} * 60) ? $event->{start} - $CONFIG{TM_MARGIN_BEGIN} * 60 : $timer->{start},
+ $timer->{stop} < ($event->{stop} + $CONFIG{TM_MARGIN_END} * 60) ? $event->{stop} + $CONFIG{TM_MARGIN_END} * 60 : $timer->{stop},
+ $timer->{prio},
+ $timer->{lft},
+ # don't touch the title since we're not too sure about the event
+ $timer->{title},
+ # If there already is a summary, the user might have changed it -- leave it untouched.
+ $timer->{summary} ? $timer->{summary} : $event->{summary},
+ );
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Timer %s updated.", $timer->{id}));
+ }
+ }
+ } else {
+ Log(LOG_CHECKTIMER, sprintf("CheckTimers: Skipping Timer \"%s\" (No. %s, %s - %s)", $timer->{title}, $timer->{id}, strftime("%Y%m%d-%H%M", localtime($timer->{start})), strftime("%Y%m%d-%H%M", localtime($timer->{stop}))));
+ }
+ }
+ }
+}
+
+#############################################################################
+# regulary timers
+#############################################################################
+sub my_mktime {
+ my $sec = 0;
+ my $min = shift;
+ my $hour = shift;
+ my $mday = shift;
+ my $mon = shift;
+ my $year = shift() - 1900;
+
+ #my $time = mktime($sec, $min, $hour, $mday, $mon, $year, 0, 0, (localtime(time))[8]);
+ my $time = mktime($sec, $min, $hour, $mday, $mon, $year, 0, 0, -1);
+}
+
+sub ParseTimer {
+ my $pc = shift;
+ my $tid = shift;
+ my $entry = 1;
+
+ my @temp;
+ for(SendCMD("lstt")) {
+ last if(/^No timers defined/);
+ chomp;
+ my($id, $temp) = split(/ /, $_, 2);
+ my($tmstatus, $vdr_id, $dor, $start, $stop, $prio, $lft, $title, $summary) = split(/\:/, $temp, 9);
+
+ my($startsse, $stopsse, $weekday, $off, $perrec, $length, $first);
+
+ my($active, $event_id);
+ $active = UnpackActive($tmstatus);
+ $event_id = UnpackEvent_id($tmstatus);
+
+ # direct recording (menu, red)
+ $active = 1 if($active == 3);
+
+ if(length($dor) == 7) { # repeating timer
+ $startsse = my_mktime(substr($start, 2, 2), substr($start, 0, 2),
+ my_strftime("%d"), (my_strftime("%m") - 1), my_strftime("%Y"));
+ $stopsse = my_mktime(substr($stop, 2, 2), substr($stop, 0, 2),
+ my_strftime("%d"), (my_strftime("%m") - 1), my_strftime("%Y"));
+ if($stopsse < $startsse) {
+ $stopsse += 86400;
+ }
+ $weekday = ((localtime(time))[6] + 6) % 7;
+ $perrec = join("", substr($dor, $weekday), substr($dor, 0, $weekday));
+ $perrec =~ m/^-+/g;
+
+ $off = (pos $perrec) * 86400;
+ if($off == 0 && $stopsse < time) {
+ #$weekday = ($weekday + 1) % 7;
+ $perrec = join("", substr($dor, ($weekday + 1) % 7), substr($dor, 0, ($weekday + 1) % 7));
+ $perrec =~ m/^-+/g;
+ $off = ((pos $perrec) + 1) * 86400;
+ }
+ $startsse += $off;
+ $stopsse += $off;
+ } elsif(length($dor) == 18) { # first-day timer
+ $dor =~ /.{7}\@(\d\d\d\d)-(\d\d)-(\d\d)/;
+ $startsse = my_mktime(substr($start, 2, 2),
+ substr($start, 0, 2), $3, ($2 - 1), $1);
+ # 31 + 1 = ??
+ $stopsse = my_mktime(substr($stop, 2, 2),
+ substr($stop, 0, 2), $stop > $start ? $3 : $3 + 1,
+ ($2 - 1), $1);
+ } else { # regular timer
+ $startsse = my_mktime(substr($start, 2, 2),
+ substr($start, 0, 2), $dor, (my_strftime("%m") - 1),
+ my_strftime("%Y"));
+
+ $stopsse = my_mktime(substr($stop, 2, 2),
+ substr($stop, 0, 2), $stop > $start ? $dor : $dor + 1,
+ (my_strftime("%m") - 1), my_strftime("%Y"));
+ }
+
+ # move timers which have expired one month into the future
+ if(length($dor) != 7 && $stopsse < time) {
+ $startsse = my_mktime(substr($start, 2, 2),
+ substr($start, 0, 2), $dor, (my_strftime("%m") % 12),
+ (my_strftime("%Y") + (my_strftime("%m") == 12 ? 1 : 0)));
+
+ $stopsse = my_mktime(substr($stop, 2, 2),
+ substr($stop, 0, 2), $stop > $start ? $dor : $dor + 1,
+ (my_strftime("%m") % 12),
+ (my_strftime("%Y") + (my_strftime("%m") == 12 ? 1 : 0)));
+ }
+
+ if($CONFIG{RECORDINGS} && length($dor) == 7) { # repeating timer
+ # generate repeating timer entries for up to 28 days
+ $first = 1;
+ for($weekday += $off / 86400, $off = 0; $off < 28; $off++) {
+ $perrec = join("", substr($dor, ($weekday + $off) % 7), substr($dor, 0, ($weekday + $off) % 7));
+ $perrec =~ m/^-+/g;
+ if ((pos $perrec) != 0) {
+ next;
+ }
+
+ $length = push(@temp, {
+ id => $id,
+ vdr_id => $vdr_id,
+ start => $startsse,
+ stop => $stopsse,
+ startsse => $startsse + $off * 86400,
+ stopsse => $stopsse + $off * 86400,
+ active => $active,
+ event_id => $event_id,
+ cdesc => get_name_from_vdrid($vdr_id),
+ transponder => get_transponder_from_vdrid($vdr_id),
+ ca => get_ca_from_vdrid($vdr_id),
+ dor => $dor,
+ prio => $prio,
+ lft => $lft,
+ title => $title,
+ summary => $summary,
+ collision => 0,
+ critical => 0,
+ first => $first
+ });
+ $first = 0;
+ }
+ } else {
+ $length = push(@temp, {
+ id => $id,
+ vdr_id => $vdr_id,
+ start => $startsse,
+ stop => $stopsse,
+ startsse => $startsse,
+ stopsse => $stopsse,
+ active => $active,
+ event_id => $event_id,
+ cdesc => get_name_from_vdrid($vdr_id),
+ transponder => get_transponder_from_vdrid($vdr_id),
+ ca => get_ca_from_vdrid($vdr_id),
+ dor => $dor,
+ prio => $prio,
+ lft => $lft,
+ title => $title,
+ summary => $summary,
+ collision => 0,
+ critical => 0,
+ first => -1
+ });
+ }
+
+ # save index of entry with specific timer id for later use
+ if($tid && $tid == $id) {
+ $entry = $length;
+ }
+ }
+
+ if($tid) {
+ return($temp[$entry - 1]);
+ } else {
+ return(@temp);
+ }
+}
+
+#############################################################################
+# Tools
+#############################################################################
+sub DisplayMessage {
+ my $message = shift;
+ SendCMD(sprintf("mesg %s", $message));
+}
+
+sub LoadTranslation {
+ undef @I18N_Days;
+ undef @I18N_Month;
+ undef %ERRORMESSAGE;
+ undef %COMMONMESSAGE;
+ undef %HELP;
+ undef @LOGINPAGES_DESCRIPTION;
+ include("$TEMPLATEDIR/$CONFIG{LANGUAGE}/$I18NFILE");
+}
+
+sub HelpURL {
+ my $area = shift;
+ return(sprintf("%s?aktion=show_help&area=%s", $MyURL, $area));
+}
+
+sub ProgTimer {
+ # $start and $stop are expected as seconds since 00:00:00 1970-01-01 UTC.
+ my($timer_id, $active, $event_id, $channel, $start, $stop, $prio, $lft, $title, $summary, $dor) = @_;
+
+ $title =~ s/\://g;
+
+ if(($CONFIG{NO_EVENTID} == 1) && ($event_id > 0)) {
+ $event_id = 0;
+ Log(LOG_CHECKTIMER, sprintf("ProgTimer: Event-ID removed for recording \"%s\"", $title));
+ } else {
+ for my $n (split(",", $CONFIG{NO_EVENTID_ON})) {
+ if(($n == $channel) && ($event_id > 0)) {
+ $event_id = 0;
+ Log(LOG_CHECKTIMER, sprintf("ProgTimer: Event-ID removed for recording \"%s\" on channel %s", $title, $channel));
+ }
+ }
+ }
+
+ Log(LOG_AT, sprintf("ProgTimer: Programming Timer \"%s\" (Channel %s, Event-ID %s, %s - %s)", $title, $channel, $event_id, my_strftime("%Y%m%d-%H%M", $start), my_strftime("%Y%m%d-%H%M", $stop)));
+
+ my $return = SendCMD(
+ sprintf("%s %s:%s:%s:%s:%s:%s:%s:%s:%s",
+ $timer_id ? "modt $timer_id" : "newt",
+ # only autotimers with 16th bit set will be extended by the event_id
+ $active & 0x8000 ? PackStatus($active, $event_id) : $active,
+ $channel,
+ $dor ? $dor : RemoveLeadingZero(strftime("%d", localtime($start))),
+ strftime("%H%M", localtime($start)),
+ strftime("%H%M", localtime($stop)),
+ $prio,
+ $lft,
+ $title,
+ $summary
+ )
+ );
+
+ return $return;
+}
+
+sub RedirectToReferer {
+ my $url = shift;
+ if($Referer =~ /vdradmin\.pl\?.*$/) {
+ return(headerForward($Referer));
+ } else {
+ return(headerForward($url));
+ }
+}
+
+sub salt {
+ $_ = $_[0];
+ my $string;
+ my($offset1, $offset2);
+ if(length($_) > 8) {
+ $offset1 = length($_) - 9;
+ $offset2 = length($_) - 1;
+ } else {
+ $offset1 = 0;
+ $offset2 = length($_) - 1;
+ }
+ $string = substr($_, $offset1, 1);
+ $string .= substr($_, $offset2, 1);
+ return($string);
+}
+
+sub Shutdown {
+ unlink($PIDFILE);
+ exit(0)
+};
+
+sub getPID {
+ open(PID, shift);
+ $_ = <PID>;
+ close(PID);
+ return($_);
+}
+
+sub writePID {
+ open(FILE, ">$PIDFILE");
+ print FILE shift;
+ close(FILE);
+}
+
+sub HupSignal {
+ UptoDate(1);
+}
+
+sub UptoDate {
+ my $force = shift;
+ if(((time() - $CONFIG{CACHE_LASTUPDATE}) >= ($CONFIG{CACHE_TIMEOUT} * 60)) || $force) {
+ OpenSocket();
+ ChanTree();
+ EPG_buildTree();
+ CheckTimers();
+ AutoTimer();
+ CloseSocket();
+ $CONFIG{CACHE_LASTUPDATE} = time();
+ }
+ return(0);
+}
+
+sub Log {
+ if($#_ >= 1) {
+ my $level = shift;
+ my $message = join("", @_);
+ print $message . "\n" if $DEBUG;
+ if($CONFIG{LOGGING}) {
+ if($CONFIG{LOGLEVEL} & $level) {
+ open(LOGFILE, ">>" . $LOGFILE);
+ print LOGFILE sprintf("%s: %s\n", my_strftime("%d.%m.%Y %H:%M:%S"), $message);
+ close(LOGFILE);
+ }
+ }
+ } else {
+ Log(LOG_FATALERROR, "bogus Log() call");
+ }
+}
+
+sub TemplateNew {
+ my $file = shift;
+ $CONFIG{LANGUAGE} = "English" if(!$CONFIG{LANGUAGE});
+ $file = "$TEMPLATEDIR/$CONFIG{LANGUAGE}/$file";
+ if(!-e $file) {
+ Log(LOG_FATALERROR, "Fatal! Can't find $file!");
+ }
+ my $template = HTML::Template::Expr->new(
+ die_on_bad_params => 0,
+ loop_context_vars => 1,
+ filename => $file);
+ return $template;
+}
+
+sub my_strftime {
+ my $format = shift;
+ my $time = shift;
+ return(strftime($format, $time ? localtime($time) : localtime(time)));
+}
+
+sub my_strfgmtime {
+ my $format = shift;
+ my $time = shift;
+ return(strftime($format, $time ? gmtime($time) : gmtime(time)));
+}
+
+sub GetFirstChannel {
+ return($CHAN[0]->{service_id});
+}
+
+sub ChannelHasEPG {
+ my $service_id = shift;
+ for my $event (@{$EPG{$service_id}}) {
+ return(1);
+ }
+ return(0);
+}
+
+sub Encode_Referer {
+ if($_[0]) { $_ = $_[0]; } else { $_ = $Referer; }
+ return(MIME::Base64::encode_base64($_));
+}
+
+sub Decode_Referer {
+ return(MIME::Base64::decode_base64(shift));
+}
+
+sub encode_ref {
+ my($tmp) = $_[0]->url(-relative=>1,-query=>1);
+ my(undef, $query) = split(/\?/, $tmp, 2);
+ return(MIME::Base64::encode_base64($query));
+}
+
+sub decode_ref {
+ return(MIME::Base64::decode_base64($_[0]));
+}
+
+sub access_log {
+ my $ip = shift;
+ my $username = shift;
+ my $time = shift;
+ my $rawrequest = shift;
+ my $http_status = shift;
+ my $bytes_transfered = shift;
+ my $request = shift;
+ my $useragent = shift;
+ return sprintf("%s - %s [%s +0100] \"%s\" %s %s \"%s\" \"%s\"",
+ $ip,
+ $username,
+ my_strftime("%d/%b/%Y:%H:%M:%S", $time),
+ $rawrequest,
+ $http_status,
+ $bytes_transfered,
+ $request,
+ $useragent
+ );
+}
+
+sub ReadConfig {
+ if(-e $CONFFILE) {
+ open(CONF, $CONFFILE);
+ while(<CONF>) {
+ chomp;
+ my($key, $value) = split(/ \= /, $_, 2);
+ $CONFIG{$key} = $value;
+ }
+ close(CONF);
+ } else {
+ print "$CONFFILE doesn't exist. Please run \"$0 --config\"\n";
+ print "Exitting!\n";
+ exit(1);
+ #open(CONF, ">$CONFFILE");
+ #for(keys(%CONFIG)) {
+ # print(CONF "$_ = $CONFIG{$_}\n");
+ #}
+ #close(CONF);
+ #return(1);
+ }
+ return(0);
+}
+
+sub Question {
+ my($quest, $default) = @_;
+ print("$quest [$default]: ");
+ my($answer);
+ $answer = <STDIN>;
+ if($answer eq "\n") {
+ return($default);
+ } else {
+ return($answer);
+ }
+}
+
+sub RemoveLeadingZero {
+ my($str) = @_;
+ while(substr($str, 0, 1) == 0) {
+ $str = substr($str, 1, (length($str) - 1));
+ }
+ return($str);
+}
+
+sub csvAdd {
+ my $csv = shift;
+ my $add = shift;
+
+ my $found = 0;
+ for my $item (split(",", $csv)) {
+ $found = 1 if($item eq $add);
+ }
+ $csv = join(",", (split(",", $csv), $add)) if(!$found);
+ return($csv);
+}
+
+sub csvRemove {
+ my $csv = shift;
+ my $remove = shift;
+
+ my $newcsv;
+ for my $item (split(",", $csv)) {
+ if($item ne $remove) {
+ my $found = 0;
+ if(defined($newcsv)) {
+ for my $dup (split(",", $newcsv)) {
+ $found = 1 if($dup eq $item);
+ }
+ }
+ $newcsv = join(",", (split(",", $newcsv), $item)) if(!$found);
+ }
+ }
+ return($newcsv);
+}
+
+sub Einheit {
+ my @einheiten = qw(MB GB TB);
+ my $einheit = 0;
+ my $zahl = shift;
+ while($zahl > 1024) {
+ $zahl /= 1024;
+ $einheit++;
+ }
+ return(int($zahl) . $einheiten[$einheit]);
+}
+
+sub MBToMinutes {
+ my $mb = shift;
+ my $minutes = $mb / 25.75;
+ my $hours = $minutes / 60;
+ $minutes %= 60;
+ return(sprintf("%2d:%02d", $hours, $minutes));
+}
+
+sub VideoDiskFree {
+ $_ = join("", SendCMD("stat disk"));
+ if(/^(\d+)MB (\d+)MB (\d+)%$/) {
+ return(Einheit($1), MBToMinutes($1), Einheit($2), MBToMinutes($2), $3);
+ } elsif(/^Command unrecognized: "stat"$/) {
+ #print "VDR doesnt know about this extension\n";
+ } else {
+ print "Unknown response $_\n";
+ }
+ return undef;
+}
+
+#############################################################################
+# frontend
+#############################################################################
+sub show_index {
+ my $template = TemplateNew("index.html");
+ my $page;
+ if(defined($CONFIG{LOGINPAGE})) {
+ $page = $LOGINPAGES[$CONFIG{LOGINPAGE}];
+ } else {
+ $page = $LOGINPAGES[0];
+ }
+ $template->param(
+ loginpage => "$MyURL?aktion=$page",
+ version => $VERSION,
+ host => $CONFIG{VDR_HOST},
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub toolbar {
+ my $template = TemplateNew("toolbar.html");
+
+ my @channel;
+ for my $channel (@CHAN) {
+ # if its wished, display only wanted channels
+ if($CONFIG{CHANNELS_WANTED_PRG}) {
+ my $found = 0;
+ for my $n (split(",", $CONFIG{CHANNELS_WANTED})) {
+ ($found = 1) if($n eq $channel->{vdr_id});
+ }
+ next if(!$found);
+ }
+
+ # skip channels without EPG data
+ if(ChannelHasEPG($channel->{vdr_id})) {
+ push(@channel, {
+ name => $channel->{name},
+ vdr_id => $channel->{vdr_id},
+ #current => ($vdr_id == $channel->{vdr_id}) ? 1 : 0,
+ });
+ }
+ }
+
+ $template->param(
+ url => $MyURL,
+ chanloop => \@channel
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+# obsolete?
+sub show_top {
+ my $template = TemplateNew("top.html");
+ return(header("200", "text/html", $template->output));
+}
+
+sub prog_switch {
+ my $channel = $q->param("channel");
+ if($channel) {
+ SendCMD("chan $channel");
+ }
+ SendFile($BASENAME . "/images/switch_channel.gif");
+}
+
+sub prog_detail {
+ return if(UptoDate());
+ my $vdr_id = $q->param("vdr_id");
+ my $epg_id = $q->param("epg_id");
+
+ my($channel_name, $title, $subtitle, $start, $stop, $date, $text);
+
+ if($vdr_id && $epg_id) {
+ for(@{$EPG{$vdr_id}}) {
+ #if($_->{id} == $epg_id) { #XXX
+ if($_->{event_id} == $epg_id) {
+ $channel_name = $_->{channel_name};
+ $title = $_->{title};
+ $subtitle = $_->{subtitle};
+ $start = my_strftime("%H:%M", $_->{start});
+ $stop = my_strftime("%H:%M", $_->{stop});
+ $text = $_->{summary};
+ $date = sprintf("%s., %s. %s %s",
+ substr(FullDay(my_strftime("%w", $_->{start})), 0, 2),
+ my_strftime("%d", $_->{start}),
+ FullMonth(my_strftime("%m", $_->{start})),
+ my_strftime("%Y", $_->{start}));
+ last;
+ }
+ }
+ }
+
+ my $displaytext = $text;
+ my $displaytitle = $title;
+ my $displaysubtitle = $subtitle;
+
+ $displaytext =~ s/\n/<br>\n/g;
+ $displaytext =~ s/\|/<br>\n/g;
+ $displaytitle =~ s/\n/<br>\n/g;
+ $displaytitle =~ s/\|/<br>\n/g;
+ $displaysubtitle =~ s/\n/<br>\n/g;
+ $displaysubtitle =~ s/\|/<br>\n/g;
+
+ my $template = TemplateNew("prog_detail.html");
+ $template->param(
+ title => $displaytitle ? $displaytitle : undef,
+ recurl => sprintf("%s?aktion=timer_new_form&epg_id=%s&vdr_id=%s", $MyURL, $epg_id, $vdr_id),
+ switchurl => sprintf("%s?aktion=prog_switch&channel=%s", $MyURL, $vdr_id),
+ channel_name => $channel_name,
+ subtitle => $displaysubtitle,
+ start => $start,
+ stop => $stop,
+ text => $displaytext ? $displaytext : undef,
+ date => $date
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+
+#############################################################################
+# program listing
+#############################################################################
+sub prog_list {
+ return if(UptoDate());
+ my $vdr_id = $q->param("vdr_id");
+
+ # called without vdr_id, redirect to the first known channel
+ if(!$vdr_id) {
+ return(headerForward("$MyURL?aktion=prog_list&vdr_id=1"));
+ }
+
+ #
+ my @channel;
+ for my $channel (@CHAN) {
+ # if its wished, display only wanted channels
+ if($CONFIG{CHANNELS_WANTED_PRG}) {
+ my $found = 0;
+ for my $n (split(",", $CONFIG{CHANNELS_WANTED})) {
+ ($found = 1) if($n eq $channel->{vdr_id});
+ }
+ next if(!$found);
+ }
+
+ # skip channels without EPG data
+ if(ChannelHasEPG($channel->{vdr_id})) {
+ push(@channel, {
+ name => $channel->{name},
+ vdr_id => $channel->{vdr_id},
+ current => ($vdr_id == $channel->{vdr_id}) ? 1 : 0,
+ });
+ }
+ }
+
+ # find the next/prev channel
+ my $ci = 0;
+ for(my $i = 0; $i <= $#channel; $i++) {
+ ($ci = $i) if($vdr_id == $channel[$i]->{vdr_id});
+ }
+ my ($next_channel, $prev_channel);
+ ($prev_channel = $channel[$ci - 1]->{vdr_id}) if($ci > 0);
+ ($next_channel = $channel[$ci + 1]->{vdr_id}) if($ci < $#channel);
+
+ #
+ my(@show, $progname, $cnumber);
+ my $day = 0;
+ for my $event (@{$EPG{$vdr_id}}) {
+ if(my_strftime("%d", $event->{start}) != $day) {
+ # new day
+ push(@show, { endd => 1 }) if(scalar(@show) > 0);
+ push(@show, {
+ title => $event->{channel_name} . " | " .
+ FullDay(my_strftime("%w", $event->{start})) . ", " .
+ my_strftime("%d.%m.%Y", $event->{start}),
+ newd => 1,
+ next_channel => $next_channel ? "$MyURL?aktion=prog_list&vdr_id=$next_channel" : undef,
+ prev_channel => $prev_channel ? "$MyURL?aktion=prog_list&vdr_id=$prev_channel" : undef,
+ });
+ $day = strftime("%d", localtime($event->{start}));
+ }
+ push(@show, {
+ ssse => $event->{start},
+ emit => my_strftime("%H:%M", $event->{start}),
+ duration => my_strftime("%H:%M", $event->{stop}),
+ title => $event->{title},
+ subtitle => $event->{subtitle},
+ recurl => sprintf("%s?aktion=timer_new_form&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}),
+ infurl => $event->{summary} ? sprintf("%s?aktion=prog_detail&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}) : undef,
+ newd => 0,
+ anchor => $event->{event_id}
+ });
+ $progname = $event->{progname};
+ $cnumber = $event->{cnumber};
+ }
+ if(scalar(@show)) {
+ push(@show, { endd => 1 });
+ }
+
+
+ #
+ my($template) = TemplateNew("prog_list.html");
+ $template->param(
+ url => $MyURL,
+ loop => \@show,
+ chanloop => \@channel,
+ progname => GetChannelDescByNumber($vdr_id),
+ switchurl => "$MyURL?aktion=prog_switch&channel=" . $vdr_id,
+ streamurl => "$MyURL?aktion=live_stream&channel=" . $vdr_id,
+ toolbarurl => "$MyURL?aktion=toolbar"
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+
+
+
+#############################################################################
+# program listing 2
+# "What's up today" extension.
+#
+# Contributed by Thomas Blon, 6. Mar 2004
+#############################################################################
+sub prog_list2 {
+ return if(UptoDate());
+
+ #
+ my $vdr_id;
+ my @channel;
+
+ for my $channel (@CHAN) {
+ # if its wished, display only wanted channels
+ if($CONFIG{CHANNELS_WANTED_PRG}) {
+ my $found = 0;
+ for my $n (split(",", $CONFIG{CHANNELS_WANTED})) {
+ ($found = 1) if($n eq $channel->{vdr_id});
+ }
+ next if(!$found);
+ }
+
+ # skip channels without EPG data
+ if(ChannelHasEPG($channel->{vdr_id})) {
+ push(@channel, {
+ name => $channel->{name},
+ vdr_id => $channel->{vdr_id},
+ current => ($vdr_id == $channel->{vdr_id}) ? 1 : 0,
+ });
+ }
+ }
+
+ my(@show, $progname, $cnumber);
+
+ foreach (@channel) { # loop through all channels
+ $vdr_id = $_->{vdr_id};
+
+ # find the next/prev channel
+ my $ci = 0;
+ for(my $i = 0; $i <= $#channel; $i++) {
+ ($ci = $i) if($vdr_id == $channel[$i]->{vdr_id});
+ }
+ my ($next_channel, $prev_channel);
+ ($prev_channel = $channel[$ci - 1]->{vdr_id}) if($ci > 0);
+ ($next_channel = $channel[$ci + 1]->{vdr_id}) if($ci < $#channel);
+
+
+ my $day = 0;
+ my $dayflag = 0;
+
+
+ for my $event (@{$EPG{$vdr_id}}) {
+ if(my_strftime("%d", $event->{start}) != $day) { # new day
+ $day = strftime("%d", localtime($event->{start}));
+ $dayflag++;
+ }
+
+ if($dayflag == 1) {
+ push(@show, {
+ title => $event->{channel_name} . " | " .
+ FullDay(my_strftime("%w", $event->{start})) . ", " .
+ my_strftime("%d.%m.%Y", $event->{start}),
+ newd => 1,
+ undef,
+ undef,
+ });
+
+ $dayflag++;
+ }
+
+ if($dayflag == 2) {
+ push(@show, {
+ ssse => $event->{start},
+ emit => my_strftime("%H:%M", $event->{start}),
+ duration => my_strftime("%H:%M", $event->{stop}),
+ title => $event->{title},
+ subtitle => $event->{subtitle},
+ recurl => sprintf("%s?aktion=timer_new_form&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}),
+ infurl => $event->{summary} ? sprintf("%s?aktion=prog_detail&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}) : undef,
+ newd => 0,
+ anchor => $event->{event_id}
+ });
+ $progname = $event->{progname};
+ $cnumber = $event->{cnumber};
+ }
+ }
+ push(@show, { endd => 1 });
+ } # end: for $vdr_id
+
+
+ #
+ my($template) = TemplateNew("prog_list2.html");
+ $template->param(
+ url => $MyURL,
+ loop => \@show,
+ chanloop => \@channel,
+ progname => GetChannelDescByNumber($vdr_id),
+ switchurl=> "$MyURL?aktion=prog_switch&channel=" . $vdr_id,
+ toolbarurl => "$MyURL?aktion=toolbar"
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+
+#############################################################################
+# regular timers
+#############################################################################
+sub timer_list {
+ return if(UptoDate());
+
+ #
+ my $desc;
+ if(!defined($q->param("desc"))) {
+ $desc = 1;
+ } else {
+ $q->param("desc") ? ($desc = 1) : ($desc = 0);
+ }
+ my $sortby = $q->param("sortby");
+ ($sortby = "day") if(!$sortby);
+
+ #
+ my @timer;
+ my @timer2;
+ my @days;
+
+ my ($TagAnfang, $TagEnde);
+ for my $timer (ParseTimer(0)) {
+ if($timer->{startsse} < time() && $timer->{stopsse} > time() && ($timer->{active} & 1)) {
+ $timer->{recording} = 1;
+ }
+ if($timer->{active} & 1) {
+ if($timer->{active} & 0x8000) {
+ $timer->{active} = 0x8001;
+ } else {
+ $timer->{active} = 1;
+ }
+ } else {
+ $timer->{active} = 0;
+ }
+ $timer->{delurl} = $MyURL . "?aktion=timer_delete&timer_id=" . $timer->{id},
+ $timer->{modurl} = $MyURL . "?aktion=timer_new_form&timer_id=" . $timer->{id},
+ $timer->{toggleurl} = sprintf("%s?aktion=timer_toggle&active=%s&id=%s", $MyURL, ($timer->{active} & 1) ? 0 : 1, $timer->{id}),
+ $timer->{dor} = my_strftime("%d.%m", $timer->{startsse});
+
+ $TagAnfang=my_mktime(0,0,my_strftime("%d", $timer->{start}),my_strftime("%m", $timer->{start}),my_strftime("%Y", $timer->{start}));
+ $TagEnde=my_mktime(0,0,my_strftime("%d", $timer->{stop}),my_strftime("%m", $timer->{stop}),my_strftime("%Y", $timer->{stop}));
+
+ $timer->{startlong} = ((my_mktime(my_strftime("%M", $timer->{start}),my_strftime("%H", $timer->{start}),my_strftime("%d", $timer->{start}),my_strftime("%m", $timer->{start}),my_strftime("%Y", $timer->{start})))-$TagAnfang)/60;
+ $timer->{stoplong} = ((my_mktime(my_strftime("%M", $timer->{stop}),my_strftime("%H", $timer->{stop}),my_strftime("%d", $timer->{stop}),my_strftime("%m", $timer->{stop}),my_strftime("%Y", $timer->{stop})))-$TagEnde)/60;
+ $timer->{starttime} = my_strftime("%y%m%d", $timer->{startsse});
+ $timer->{stoptime} = my_strftime("%y%m%d", $timer->{stopsse});
+ $timer->{sortfield} = $timer->{cdesc} . $timer->{startsse};
+ $timer->{infurl} = $timer->{event_id} ? sprintf("%s?aktion=prog_detail&epg_id=%s&vdr_id=%s", $MyURL, $timer->{event_id}, $timer->{vdr_id}) : undef,
+
+ $timer->{start} = my_strftime("%H:%M", $timer->{start});
+ $timer->{stop} = my_strftime("%H:%M", $timer->{stop});
+ $timer->{sortbyactive} = 1 if($sortby eq "active");
+ $timer->{sortbychannel} = 1 if($sortby eq "channel");
+ $timer->{sortbyname} = 1 if($sortby eq "name");
+ $timer->{sortbystart} = 1 if($sortby eq "start");
+ $timer->{sortbystop} = 1 if($sortby eq "stop");
+ $timer->{sortbyday} = 1 if($sortby eq "day");
+ push(@timer, $timer);
+ }
+ @timer = sort({ $a->{startsse} <=> $b->{startsse} } @timer);
+
+ #
+ if($CONFIG{RECORDINGS}) {
+ my($ii, $jj, $timer, $last);
+ for($ii = 0; $ii < @timer; $ii++) {
+ if($timer[$ii]->{first} == -1 || $timer[$ii]->{first} == 1) {
+ $last = $ii;
+ }
+
+ # Liste der benutzten Transponder
+ my @Transponder = (get_transponder_from_vdrid($timer[$ii]->{vdr_id}));
+ $timer[$ii]->{collision} = 0;
+
+ for($jj = 0; $jj < $ii; $jj++) {
+ if($timer[$ii]->{startsse} >= $timer[$jj]->{startsse} &&
+ $timer[$ii]->{startsse} < $timer[$jj]->{stopsse}) {
+ if($timer[$ii]->{active} && $timer[$jj]->{active}) {
+ # Timer kollidieren zeitlich. Pruefen, ob die Timer evtl. auf
+ # gleichem Transponder oder CAM liegen und daher ohne Probleme
+ # aufgenommen werden koennen
+ Log(LOG_DEBUG, sprintf("Kollission: %s (%s, %s) -- %s (%s, %s)\n",
+ substr($timer[$ii]->{title},0,15), $timer[$ii]->{vdr_id},
+ get_transponder_from_vdrid($timer[$ii]->{vdr_id}),
+ get_ca_from_vdrid($timer[$ii]->{vdr_id}),
+ substr($timer[$jj]->{title},0,15), $timer[$jj]->{vdr_id},
+ get_transponder_from_vdrid($timer[$jj]->{vdr_id}),
+ get_ca_from_vdrid($timer[$jj]->{vdr_id})));
+
+ if($timer[$ii]->{vdr_id} != $timer[$jj]->{vdr_id} &&
+ get_ca_from_vdrid($timer[$ii]->{vdr_id}) ==
+ get_ca_from_vdrid($timer[$jj]->{vdr_id}) &&
+ get_ca_from_vdrid($timer[$ii]->{vdr_id}) >= 100) {
+ # Beide Timer laufen auf dem gleichen CAM auf verschiedenen
+ # Kanaelen, davon kann nur einer aufgenommen werden
+ Log(LOG_DEBUG, "Beide Kanaele gleiches CAM");
+ ($timer[$ii]->{collision}) = $CONFIG{RECORDINGS};
+ # Nur Kosmetik: Transponderliste vervollstaendigen
+ push(@Transponder, get_transponder_from_vdrid($timer[$jj]->{vdr_id}));
+ } else {
+ # "grep" prueft die Bedingung fuer jedes Element, daher den
+ # Transponder vorher zwischenspeichern -- ist effizienter
+ my $t = get_transponder_from_vdrid($timer[$jj]->{vdr_id});
+ if(scalar(grep($_ eq $t, @Transponder)) == 0) {
+ ($timer[$ii]->{collision})++;
+ push(@Transponder, get_transponder_from_vdrid($timer[$jj]->{vdr_id}));
+ }
+ }
+ }
+ }
+ }
+ }
+ splice(@timer, $last + 1);
+ for ($ii = 0; $ii < @timer; $ii++) {
+ $timer[$ii]->{critical} = $timer[$ii]->{collision} >= $CONFIG{RECORDINGS};
+ if ($timer[$ii]->{critical} > 0) {
+ for ($jj = $ii - 1; $jj >= 0; $jj--) {
+ if ($timer[$jj]->{stopsse} > $timer[$ii]->{startsse}) {
+ $timer[$jj]->{critical} = 1;
+ }
+ else {
+ last;
+ }
+ }
+ }
+ $timer[$ii]->{collision} = $timer[$ii]->{collision} >= ($CONFIG{RECORDINGS} - 1);
+ if ($timer[$ii]->{collision} > 0) {
+ for ($jj = $ii - 1; $jj >= 0; $jj--) {
+ if ($timer[$jj]->{stopsse} > $timer[$ii]->{startsse}) {
+ $timer[$jj]->{collision} = 1;
+ }
+ else {
+ last;
+ }
+ }
+ }
+ $timer[$ii]->{collision} |= ($timer[$ii]->{ca} >= 100);
+ }
+ }
+
+ #
+ my($ii, $jj, $kk, $current, $title);
+
+ for ($ii = 0; $ii < @timer; $ii++)
+ {
+ if($ii==0)
+ {
+ if(!defined($q->param("timer")))
+ {
+ $current=my_strftime("%y%m%d", $timer[$ii]->{startsse});
+ $title=FullDay(my_strftime("%w", $timer[$ii]->{startsse})) . ", " . my_strftime("%d.%m.%Y", $timer[$ii]->{startsse});
+ }
+ else
+ {
+ $current=$q->param("timer");
+ $kk = my_mktime(0,0,substr($current, 4, 2),substr($current, 2, 2)-1,"20".substr($current, 0, 2));
+ $title=FullDay(my_strftime("%w", $kk)) . ", " . my_strftime("%d.%m.%Y", $kk);
+ }
+ }
+
+ $jj=0;
+ for ($kk = 0; $kk < @days; $kk++)
+ {
+ if($days[$kk]->{day} == my_strftime("%d.%m", $timer[$ii]->{startsse}))
+ {
+ $jj=1;
+ last;
+ }
+ }
+ if($jj==0) {
+ push(@days, {
+ day => my_strftime("%d.%m", $timer[$ii]->{startsse}),
+ sortfield => my_strftime("%y%m%d", $timer[$ii]->{startsse}),
+ current => ($current == my_strftime("%y%m%d", $timer[$ii]->{startsse})) ? 1 : 0,
+ });
+ }
+
+ $jj=0;
+ for ($kk = 0; $kk < @days; $kk++)
+ {
+ if($days[$kk]->{day} == my_strftime("%d.%m", $timer[$ii]->{stopsse}))
+ {
+ $jj=1;
+ last;
+ }
+ }
+ if($jj==0) {
+ push(@days, {
+ day => my_strftime("%d.%m", $timer[$ii]->{stopsse}),
+ sortfield => my_strftime("%y%m%d", $timer[$ii]->{stopsse}),
+ current => ($current == my_strftime("%y%m%d", $timer[$ii]->{stopsse})) ? 1 : 0,
+ });
+ }
+ }
+
+ @days = sort({ $a->{sortfield} <=> $b->{sortfield} } @days);
+
+
+ #
+ if($sortby eq "active") {
+ if(!$desc) {
+ @timer = sort({ $b->{active} <=> $a->{active} } @timer);
+ } else {
+ @timer = sort({ $a->{active} <=> $b->{active} } @timer);
+ }
+ } elsif($sortby eq "channel") {
+ if(!$desc) {
+ @timer = sort({ lc($b->{cdesc}) cmp lc($a->{cdesc}) } @timer);
+ } else {
+ @timer = sort({ lc($a->{cdesc}) cmp lc($b->{cdesc}) } @timer);
+ }
+ } elsif($sortby eq "name") {
+ if(!$desc) {
+ @timer = sort({ lc($b->{title}) cmp lc($a->{title}) } @timer);
+ } else {
+ @timer = sort({ lc($a->{title}) cmp lc($b->{title}) } @timer);
+ }
+ } elsif($sortby eq "start") {
+ if(!$desc) {
+ @timer = sort({ $b->{start} <=> $a->{start} } @timer);
+ } else {
+ @timer = sort({ $a->{start} <=> $b->{start} } @timer);
+ }
+ } elsif($sortby eq "stop") {
+ if(!$desc) {
+ @timer = sort({ $b->{stop} <=> $a->{stop} } @timer);
+ } else {
+ @timer = sort({ $a->{stop} <=> $b->{stop} } @timer);
+ }
+ } elsif($sortby eq "day") {
+ if(!$desc) {
+ @timer = sort({ $b->{startsse} <=> $a->{startsse} } @timer);
+ } else {
+ @timer = sort({ $a->{startsse} <=> $b->{startsse} } @timer);
+ }
+ }
+ $desc ? ($desc = 0) : ($desc = 1);
+ @timer2=@timer;
+ @timer2=sort({ lc($a->{sortfield}) cmp lc($b->{sortfield}) } @timer2);
+
+ my $template = TemplateNew("timer_list.html");
+ my $vars = {
+ sortbydayurl => "$MyURL?aktion=timer_list&sortby=day&desc=$desc",
+ sortbychannelurl => "$MyURL?aktion=timer_list&sortby=channel&desc=$desc",
+ sortbynameurl => "$MyURL?aktion=timer_list&sortby=name&desc=$desc",
+ sortbyactiveurl => "$MyURL?aktion=timer_list&sortby=active&desc=$desc",
+ sortbystarturl => "$MyURL?aktion=timer_list&sortby=start&desc=$desc",
+ sortbystopurl => "$MyURL?aktion=timer_list&sortby=stop&desc=$desc",
+ sortbyday => ($sortby eq "day") ? 1 : 0,
+ sortbychannel => ($sortby eq "channel") ? 1 : 0,
+ sortbyname => ($sortby eq "name") ? 1 : 0,
+ sortbyactive => ($sortby eq "active") ? 1 : 0,
+ sortbystart => ($sortby eq "start") ? 1 : 0,
+ sortbystop => ($sortby eq "stop") ? 1 : 0,
+ desc => $desc,
+ timer_loop => \@timer,
+ timers => \@timer2,
+ day_loop => \@days,
+ nturl => $MyURL . "?aktion=timer_new_form",
+ url => $MyURL,
+ help_url => HelpURL("timer_list"),
+ current => $current,
+ title => $title,
+ };
+
+ $template->param( $vars );
+ # New Template
+ my $output;
+ my $out = $template->output;
+ $Xtemplate->process(\$out, $vars, \$output)
+ || return(header("200", "text/html", $Xtemplate->error()));
+
+ return(header("200", "text/html", $output));
+}
+
+sub timer_toggle {
+ UptoDate();
+ my $active = $q->param("active");
+ my $id = $q->param("id");
+ # XXX check return
+ SendCMD(sprintf("modt %s %s", $id, $active ? "on" : "off"));
+ return(headerForward(RedirectToReferer("$MyURL?aktion=timer_list")));
+}
+
+sub timer_new_form {
+ UptoDate();
+
+ my $epg_id = $q->param("epg_id");
+ my $vdr_id = $q->param("vdr_id");
+ my $timer_id = $q->param("timer_id");
+
+ my $this_event;
+ if($epg_id) { # new timer
+ my $this = EPG_getEntry($vdr_id, $epg_id);
+ $this_event->{active} = 0x8001;
+ $this_event->{event_id} = $this->{event_id};
+ $this_event->{start} = $this->{start} - ($CONFIG{TM_MARGIN_BEGIN} * 60);
+ $this_event->{stop} = $this->{stop} + ($CONFIG{TM_MARGIN_END} * 60);
+ $this_event->{dor} = $this->{dor};
+ $this_event->{title} = $this->{title};
+ $this_event->{summary}= $this->{summary};
+ $this_event->{vdr_id} = $this->{vdr_id};
+ } elsif($timer_id) { # edit existing timer
+ $this_event = ParseTimer(0, $timer_id);
+ } else { # none of the above
+ $this_event->{start} = time();
+ $this_event->{stop} = 0;
+ $this_event->{active} = 1;
+ $this_event->{vdr_id} = 1;
+ }
+
+ my @channels;
+ for my $channel (@CHAN) {
+ ($channel->{vdr_id} == $this_event->{vdr_id}) ? ($channel->{current} = 1) : ($channel->{current} = 0);
+ push(@channels, $channel);
+ }
+
+ # determine referer (redirect to where we come from)
+ my $ref;
+ if(defined($epg_id)) {
+ if($Referer =~ /(.*)\#\d+$/) {
+ $ref = sprintf("%s#%s", $1, $epg_id);
+ } else {
+ $ref = sprintf("%s#%s", $Referer, $epg_id);
+ }
+ }
+
+ # check if we may use Event-IDs in general or not
+ if($CONFIG{NO_EVENTID} == 1) {
+ # OK, remove Event-ID
+ $this_event->{event_id} = 0;
+ } else {
+ # check if the current channel is on the Event-ID-blacklist
+ for my $n (split(",", $CONFIG{NO_EVENTID_ON})) {
+ if($n == $this_event->{vdr_id}) {
+ # OK, remove Event-ID, on this channel no recording may have one.
+ $this_event->{event_id} = 0;
+ }
+ }
+ }
+
+ my $template = TemplateNew("timer_new.html");
+ $template->param(
+ url => $MyURL,
+ active => $this_event->{active} & 1,
+ event_id => ($this_event->{event_id} << 1) + (($this_event->{active} & 0x8000) >> 15),
+ starth => my_strftime("%H", $this_event->{start}),
+ startm => my_strftime("%M", $this_event->{start}),
+ stoph => $this_event->{stop} ? my_strftime("%H", $this_event->{stop}) : "00",
+ stopm => $this_event->{stop} ? my_strftime("%M", $this_event->{stop}) : "00",
+ dor => (length($this_event->{dor}) == 7) ? $this_event->{dor} : my_strftime("%d", $this_event->{start}),
+ prio => $this_event->{prio} ? $this_event->{prio} : $CONFIG{TM_PRIORITY},
+ lft => $this_event->{lft} ? $this_event->{lft} : $CONFIG{TM_LIFETIME},
+ title => $this_event->{title},
+ summary => $this_event->{summary},
+ timer_id => $timer_id ? $timer_id : 0,
+ channels => \@channels,
+ newtimer => $timer_id ? 0 : 1,
+ referer => Encode_Referer($ref),
+ help_url => HelpURL("timer_new"),
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub timer_add {
+ my $timer_id = $q->param("timer_id");
+
+ my $data;
+
+ if($q->param("save")) {
+
+ if($q->param("starth") =~ /\d+/ && $q->param("starth") < 24 && $q->param("starth") >= 0) {
+ $data->{start} = $q->param("starth");
+ } else { print "Help!\n"; }
+ if($q->param("startm") =~ /\d+/ && $q->param("startm") < 60 && $q->param("startm") >= 0) {
+ $data->{start} .= $q->param("startm");
+ } else { print "Help!\n"; }
+
+ if($q->param("stoph") =~ /\d+/ && $q->param("stoph") < 24 && $q->param("stoph") >= 0) {
+ $data->{stop} = $q->param("stoph");
+ } else { print "Help!\n"; }
+ if($q->param("stopm") =~ /\d+/ && $q->param("stopm") < 60 && $q->param("stopm") >= 0) {
+ $data->{stop} .= $q->param("stopm");
+ } else { print "Help!\n"; }
+
+ if($q->param("prio") =~ /\d+/) {
+ $data->{prio} = $q->param("prio");
+ }
+
+ if($q->param("lft") =~ /\d+/) {
+ $data->{lft} = $q->param("lft");
+ }
+
+ if($q->param("active") == 0 || $q->param("active") == 1) {
+ $data->{active} = $q->param("active");
+ }
+
+ if($q->param("event_id") == 0) {
+ $data->{event_id} = 0;
+ }
+
+ # if($q->param("event_id") == 1 && $data->{active} == 1) {
+ if($q->param("event_id") == 1) {
+ $data->{event_id} = 0;
+ $data->{active} |= 0x8000;
+ }
+
+ # if($q->param("event_id") > 1 && $data->{active} == 1) {
+ if($q->param("event_id") > 1) {
+ $data->{event_id} = ($q->param("event_id") >> 1);
+ $data->{active} |= 0x8000;
+ }
+
+ if($q->param("dor") =~ /[0-9MTWTWFSS-]+/) {
+ $data->{dor} = $q->param("dor");
+ }
+
+ if($q->param("channel") =~ /\d+/) {
+ $data->{channel} = $q->param("channel");
+ }
+
+ if(length($q->param("title")) > 0) {
+ $data->{title} = $q->param("title");
+ }
+
+ if(length($q->param("summary")) > 0) {
+ $data->{summary} = $q->param("summary");
+ $data->{summary} =~ s/\://g;
+ $data->{summary} =~ s/[\r\n]//g;
+ }
+
+ my $dor = $data->{dor};
+ if(length($data->{dor}) == 7) {
+ # dummy
+ $dor = 1;
+ }
+ $data->{startsse} = my_mktime(substr($data->{start}, 2, 2),
+ substr($data->{start}, 0, 2), $dor,
+ (my_strftime("%m") - 1), my_strftime("%Y"));
+
+ $data->{stopsse} = my_mktime(substr($data->{stop}, 2, 2),
+ substr($data->{stop}, 0, 2),
+ $data->{stop} > $data->{start} ? $dor : $dor + 1,
+ (my_strftime("%m") - 1), my_strftime("%Y"));
+
+ my $return = ProgTimer(
+ $timer_id,
+ $data->{active},
+ $data->{event_id},
+ $data->{channel},
+ $data->{startsse},
+ $data->{stopsse},
+ $data->{prio},
+ $data->{lft},
+ $data->{title},
+ $data->{summary},
+ ($dor == 1) ? $data->{dor} : undef
+ );
+
+ }
+
+ #XXX
+ if($q->param("referer")) {
+ return(headerForward(Decode_Referer($q->param("referer"))));
+ } else {
+ return(headerForward("$MyURL?aktion=timer_list"));
+ }
+}
+
+sub timer_delete {
+ my($timer_id) = $q->param('timer_id');
+ if($timer_id) {
+ my($result) = SendCMD("delt $timer_id");
+ if($result =~ /Timer "$timer_id" is recording/i) {
+ SendCMD("modt $timer_id off");
+ sleep(1);
+ SendCMD("delt $timer_id");
+ }
+ } else {
+ my @sorted;
+ for($q->param) {
+ if(/xxxx_(.*)/) {
+ push(@sorted, $1);
+ }
+ }
+ @sorted = sort({ $b <=> $a } @sorted);
+ for my $t (@sorted) {
+ my($result) = SendCMD("delt $t");
+ if($result =~ /Timer "$t" is recording/i) {
+ SendCMD("modt $t off");
+ sleep(1);
+ SendCMD("delt $t");
+ }
+ }
+ CloseSocket();
+ }
+ return(headerForward(RedirectToReferer("$MyURL?aktion=timer_list")));
+}
+
+sub rec_stream {
+ my($id) = $q->param('id');
+ my($i, $title, $newtitle);
+ my $data;
+ my $f;
+ my(@tmp, $dirname, $name, $parent, @files);
+ my( $date, $time, $day, $month, $hour, $minute);
+
+ for(SendCMD("lstr")) {
+ ($i, $date, $time, $title) = split(/ +/, $_, 4);
+ last if($id == $i);
+ }
+ $time = substr($time, 0, 5); # needed for wareagel-patch
+ if ( $id == $i) {
+ chomp($title);
+ ($day, $month)= split( /\./, $date);
+ ($hour, $minute)= split( /:/, $time);
+ # escape characters
+ $title =~ s/~/\//g;
+ $title =~ s/\ /\_/g;
+ for ( $i=0 ;$ i < length($title); $i++) {
+ unless ( substr($title,$i,1) =~ /[öäüßÖÄÜA-Za-z0123456789_!@\$%&\(\)\+,\-;=]/) {
+ $newtitle.= sprintf( "#%02X", ord( substr($title,$i,1)));
+ } else {
+ $newtitle.= substr($title,$i,1);
+ }
+ }
+ $title=$newtitle;
+ @files= `find $VIDEODIR/$title/????-$month-$day.$hour?$minute.??.??.rec -name "???.vdr" | sort -r`;
+ foreach (@files) {
+ $_ =~ s/$VIDEODIR//;
+ $data= $CONFIG{ST_URL}."$_\n$data";
+ }
+ }
+ return(header("200", "audio/x-mpegurl", $data));
+}
+
+#############################################################################
+# live streaming
+#############################################################################
+sub live_stream {
+ my $channel = $q->param("channel");
+ my ($data, $ifconfig, $ip);
+
+ if ( $CONFIG{VDR_HOST} eq "localhost") {
+ $ifconfig=`/sbin/ifconfig eth0`;
+ if ( $ifconfig =~ /inet.+:(\d+\.\d+\.\d+\.\d+)\s+Bcast/) {
+ $ip=$1;
+ }
+ } else {
+ $ip= $CONFIG{VDR_HOST};
+ }
+ $data="http://$ip:3000/$channel";
+ return(header("200", "audio/x-mpegurl", $data));
+}
+
+#############################################################################
+# automatic timers
+#############################################################################
+sub at_timer_list {
+ return if(UptoDate());
+
+ #
+ my $desc;
+ if(!defined($q->param("desc"))) {
+ $desc = 1;
+ } else {
+ $q->param("desc") ? ($desc = 1) : ($desc = 0);
+ }
+ my $sortby = $q->param("sortby");
+ ($sortby = "pattern") if(!$sortby);
+
+ #
+ my @at;
+ my $id = 0;
+ for(AT_Read()) {
+ $id++;
+ if($_->{start}) {
+ $_->{start} = substr($_->{start}, 0, 2) . ":" . substr($_->{start}, 2, 5);
+ }
+ if($_->{stop}) {
+ $_->{stop} = substr($_->{stop}, 0, 2) . ":" . substr($_->{stop}, 2, 5);
+ }
+ $_->{modurl} = $MyURL . "?aktion=at_timer_edit&id=$id";
+ $_->{delurl} = $MyURL . "?aktion=at_timer_delete&id=$id";
+ $_->{prio} = $_->{prio} ? $_->{prio} : $CONFIG{AT_PRIORITY};
+ $_->{lft} = $_->{lft} ? $_->{lft} : $CONFIG{AT_LIFETIME};
+ $_->{id} = $id;
+ $_->{channel} = GetChannelDescByNumber($_->{channel});
+ $_->{sortbyactive} = 1 if($sortby eq "active");
+ $_->{sortbychannel} = 1 if($sortby eq "channel");
+ $_->{sortbypattern} = 1 if($sortby eq "pattern");
+ $_->{sortbystart} = 1 if($sortby eq "start");
+ $_->{sortbystop} = 1 if($sortby eq "stop");
+ $_->{toggleurl} = sprintf("%s?aktion=at_timer_toggle&active=%s&id=%s", $MyURL, ($_->{active} & 1) ? 0 : 1, $_->{id}),
+ push(@at, $_);
+ }
+ my @timer = sort({ lc($a->{pattern}) cmp lc($b->{pattern}) } @at);
+
+ #
+ if($sortby eq "active") {
+ if(!$desc) {
+ @timer = sort({ $b->{active} <=> $a->{active} } @timer);
+ } else {
+ @timer = sort({ $a->{active} <=> $b->{active} } @timer);
+ }
+ } elsif($sortby eq "channel") {
+ if(!$desc) {
+ @timer = sort({ lc($b->{channel}) cmp lc($a->{channel}) } @timer);
+ } else {
+ @timer = sort({ lc($a->{channel}) cmp lc($b->{channel}) } @timer);
+ }
+ } elsif($sortby eq "pattern") {
+ if(!$desc) {
+ @timer = sort({ lc($b->{pattern}) cmp lc($a->{pattern}) } @timer);
+ } else {
+ @timer = sort({ lc($a->{pattern}) cmp lc($b->{pattern}) } @timer);
+ }
+ } elsif($sortby eq "start") {
+ if(!$desc) {
+ @timer = sort({ $b->{start} <=> $a->{start} } @timer);
+ } else {
+ @timer = sort({ $a->{start} <=> $b->{start} } @timer);
+ }
+ } elsif($sortby eq "stop") {
+ if(!$desc) {
+ @timer = sort({ $b->{stop} <=> $a->{stop} } @timer);
+ } else {
+ @timer = sort({ $a->{stop} <=> $b->{stop} } @timer);
+ }
+ }
+ $desc ? ($desc = 0) : ($desc = 1);
+
+ #
+ my $template = TemplateNew("at_timer_list.html");
+ $template->param(
+ sortbychannelurl => "$MyURL?aktion=at_timer_list&sortby=channel&desc=$desc",
+ sortbypatternurl => "$MyURL?aktion=at_timer_list&sortby=pattern&desc=$desc",
+ sortbyactiveurl => "$MyURL?aktion=at_timer_list&sortby=active&desc=$desc",
+ sortbystarturl => "$MyURL?aktion=at_timer_list&sortby=start&desc=$desc",
+ sortbystopurl => "$MyURL?aktion=at_timer_list&sortby=stop&desc=$desc",
+ sortbychannel => ($sortby eq "channel") ? 1 : 0,
+ sortbypattern => ($sortby eq "pattern") ? 1 : 0,
+ sortbyactive => ($sortby eq "active") ? 1 : 0,
+ sortbystart => ($sortby eq "start") ? 1 : 0,
+ sortbystop => ($sortby eq "stop") ? 1 : 0,
+ desc => $desc,
+ at_timer_loop => \@timer,
+ naturl => $MyURL . "?aktion=at_timer_new",
+ #naturl => $MyURL . "?aktion=at_timer_new&active=1",
+ url => $MyURL,
+ force_update_url => "$MyURL?aktion=force_update",
+ help_url => HelpURL("at_timer_list"),
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub at_timer_toggle {
+ UptoDate();
+ my $active = $q->param("active");
+ my $id = $q->param("id");
+
+ my(@at, $z);
+
+ for(AT_Read()) {
+ $z++;
+ if($z == $id) {
+ $_->{active} = $active;
+ }
+ push(@at, $_);
+ }
+ AT_Write(@at);
+
+ return(headerForward(RedirectToReferer("$MyURL?aktion=at_timer_list")));
+}
+
+sub at_timer_edit {
+ my $id = $q->param("id");
+
+ my @at = AT_Read();
+
+ #
+ my @chans;
+ for my $chan (@CHAN) {
+ if($chan->{vdr_id}) {
+ $chan->{cur} = ($chan->{vdr_id} == $at[$id-1]->{channel}) ? 1 : 0;
+ push(@chans, $chan);
+ }
+ }
+
+ my $template = TemplateNew("at_new.html");
+ $template->param(
+ channels => \@chans,
+ id => $id,
+ url => $MyURL,
+ prio => $at[$id-1]->{prio} ? $at[$id-1]->{prio} : $CONFIG{AT_PRIORITY},
+ lft => $at[$id-1]->{lft} ? $at[$id-1]->{lft} : $CONFIG{AT_LIFETIME},
+ active => $at[$id-1]->{active},
+ done => $at[$id-1]->{done},
+ episode => $at[$id-1]->{episode},
+ pattern => $at[$id-1]->{pattern},
+ starth => (length($at[$id-1]->{start}) >= 4) ? substr($at[$id-1]->{start}, 0, 2) : undef,
+ startm => (length($at[$id-1]->{start}) >= 4) ? substr($at[$id-1]->{start}, 2, 5) : undef,
+ stoph => (length($at[$id-1]->{stop}) >= 4) ? substr($at[$id-1]->{stop}, 0, 2) : undef,
+ stopm => (length($at[$id-1]->{stop}) >= 4) ? substr($at[$id-1]->{stop}, 2, 5) : undef,
+ title => ($at[$id-1]->{section} & 1) ? 1 : 0,
+ subtitle => ($at[$id-1]->{section} & 2) ? 1 : 0,
+ description => ($at[$id-1]->{section} & 4) ? 1 : 0,
+ directory => $at[$id-1]->{directory},
+ newtimer => 0,
+ help_url => HelpURL("at_timer_new")
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub at_timer_new {
+ my $template = TemplateNew("at_new.html");
+ $template->param(
+ url => $MyURL,
+ active => $q->param("active"),
+ done => $q->param("done"),
+ title => 1,
+ channels => \@CHAN,
+ prio => $CONFIG{AT_PRIORITY},
+ lft => $CONFIG{AT_LIFETIME},
+ newtimer => 1,
+ help_url => HelpURL("at_timer_new"),
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub at_timer_save {
+ my $id = $q->param("id");
+
+ if($q->param("save")) {
+ if(!$id) {
+ my @at = AT_Read();
+ my $section = 0;
+ ($section += 1) if($q->param("title"));
+ ($section += 2) if($q->param("subtitle"));
+ ($section += 4) if($q->param("description"));
+ push(@at, {
+ episode => $q->param("episode") ? $q->param("episode") : 0,
+ active => $q->param("active"),
+ done => $q->param("done"),
+ pattern => $q->param("pattern"),
+ section => $section,
+ start => $q->param("starth") . $q->param("startm"),
+ stop => $q->param("stoph") . $q->param("stopm"),
+ prio => $q->param("prio"),
+ lft => $q->param("lft"),
+ channel => $q->param("channel"),
+ directory => $q->param("directory")
+ });
+ AT_Write(@at);
+ } else {
+ my $z = 0;
+ my @at;
+ for(AT_Read()) {
+ $z++;
+ if($z != $id) {
+ push(@at, $_);
+ } else {
+ my $section = 0;
+ ($section += 1) if($q->param("title"));
+ ($section += 2) if($q->param("subtitle"));
+ ($section += 4) if($q->param("description"));
+ push(@at, {
+ episode => $q->param("episode") ? $q->param("episode") : 0,
+ active => $q->param("active"),
+ done => $q->param("done"),
+ pattern => $q->param("pattern"),
+ section => $section,
+ start => $q->param("starth") . $q->param("startm"),
+ stop => $q->param("stoph") . $q->param("stopm"),
+ prio => $q->param("prio"),
+ lft => $q->param("lft"),
+ channel => $q->param("channel"),
+ directory => $q->param("directory")
+ });
+ }
+ }
+ AT_Write(@at);
+ }
+
+ $CONFIG{CACHE_LASTUPDATE} = 0;
+ UptoDate();
+ }
+ headerForward("$MyURL?aktion=at_timer_list");
+}
+
+sub at_timer_delete {
+ my $id = $q->param("id");
+ my $z = 0;
+
+ my @at = AT_Read();
+ my @new;
+ if($id) {
+ for(@at) {
+ $z++;
+ push(@new, $_) if($id != $z);
+ }
+ AT_Write(@new);
+ } else {
+ my @sorted;
+ for($q->param) {
+ if(/xxxx_(.*)/) {
+ push(@sorted, $1);
+ }
+ }
+ @sorted = sort({ $b <=> $a } @sorted);
+ my $z = 0;
+ for my $at (@at) {
+ $z++;
+ my $push = 1;
+ for my $sorted (@sorted) {
+ ($push = 0) if($z == $sorted);
+ }
+ push(@new, $at) if($push);
+ }
+ AT_Write(@new);
+ }
+ headerForward("$MyURL?aktion=at_timer_list");
+}
+
+
+#############################################################################
+# timeline
+#############################################################################
+sub prog_timeline {
+ return if(UptoDate());
+ my $time = $q->param("time");
+
+ # zeitpunkt bestimmen
+ my $event_time;
+ my $event_time_to;
+
+ if($time ne "") {
+ my ($hour, $minute);
+ if($time =~ /(\d{1,2})(\D?)(\d{1,2})/) {
+ if(length($1) == 1 && length($3) == 1 && !$2) {
+ $hour = $1 . $3;
+ } else {
+ ($hour, $minute) = ($1, $3);
+ }
+ } elsif($time =~ /\d/) {
+ $hour = $time;
+ }
+
+ if($hour <= my_strftime("%H") && $minute < my_strftime("%M")) {
+ $event_time = timelocal(
+ 0,
+ $minute,
+ $hour,
+ my_strftime("%d", time + 86400),
+ (my_strftime("%m", time + 86400) - 1),
+ my_strftime("%Y")
+ ) + 1;
+ } else {
+ $event_time = timelocal(
+ 0,
+ $minute,
+ $hour,
+ my_strftime("%d"),
+ (my_strftime("%m") - 1),
+ my_strftime("%Y")
+ ) + 1;
+ }
+ } else {
+ $event_time = time();
+ }
+
+ $event_time_to = $event_time + ($CONFIG{ZEITRAHMEN} * 3600);
+
+ # Timer parsen, und erstmal alle rausschmeissen die nicht in der Zeitzone liegen
+ my $TIM;
+ for my $timer (ParseTimer(0)) {
+ next if($timer->{stopsse} < $event_time or $timer->{startsse} > $event_time_to);
+ my $title = (split(/\~/, $timer->{title}))[-1];
+ $TIM->{$title} = $timer;
+ }
+
+ my(@show, @shows, @temp);
+ my $shows;
+
+ my @epgChannels = split(/\,/, $CONFIG{CHANNELS_WANTED});
+ @epgChannels = keys(%EPG)
+ unless scalar @epgChannels;
+
+ foreach(@epgChannels) { # Sender durchgehen
+ next unless(ChannelHasEPG($_));
+ foreach my $event (sort {$a->{start} <=> $b->{start} } @{$EPG{$_}}) { # Events durchgehen
+ next if($event->{stop} < $event_time or $event->{start} > $event_time_to );
+
+ push(@show, {
+ date => my_strftime("%d.%m.%y", $event->{start}),
+ longdate => sprintf("%s., %s. %s %s",
+ substr(FullDay(my_strftime("%w", $event->{start}), $event->{start}), 0, 2),
+ my_strftime("%d", $event->{start}),
+ FullMonth(my_strftime("%m", $event->{start})),
+ my_strftime("%Y", $event->{start})),
+ start => $event->{start},
+ stop => $event->{stop},
+ title => $event->{title},
+ subtitle => (length($event->{subtitle}) > 30 ? substr($event->{subtitle}, 0, 30) . "..." : $event->{subtitle}),
+ progname => $event->{channel_name},
+ summary => $event->{summary},
+ vdr_id => $event->{vdr_id},
+ proglink => sprintf("%s?aktion=prog_list&vdr_id=%s", $MyURL, $event->{vdr_id}),
+ switchurl=> sprintf("%s?aktion=prog_switch&channel=%s", $MyURL, $event->{vdr_id}),
+ infurl => ($event->{summary} ? sprintf("%s?aktion=prog_detail&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}) : undef),
+ recurl => sprintf("%s?aktion=timer_new_form&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}),
+ anchor => $event->{event_id},
+ timer => ( defined $TIM->{ $event->{title} } && $TIM->{ $event->{title} }->{vdr_id} == $event->{vdr_id} ? 1 : 0 ),
+ });
+ }
+ # needed for vdr 1.0.x, dunno why
+ @show = sort({ $a->{vdr_id} <=> $b->{vdr_id} } @show);
+ push(@{ $shows->{ $EPG{$_}->[0]->{vdr_id} } }, @show)
+ if @show;
+ undef @show;
+ }
+
+ my $vars = {
+ shows => $shows,
+ now_sec => $event_time,
+ now => strftime("%H:%M", localtime($event_time)),
+ datum => sprintf("%s., %s. %s %s",
+ substr(FullDay(my_strftime("%w", time), time), 0, 2),
+ my_strftime("%d", time),
+ FullMonth(my_strftime("%m", time)),
+ my_strftime("%Y", time)),
+ nowurl => $MyURL . "?aktion=prog_timeline",
+ url => $MyURL,
+ config => \%CONFIG,
+ };
+
+ my $template = TemplateNew("prog_timeline.html");
+ $template->param( $vars );
+
+ # New Template
+ my $output;
+ my $out = $template->output;
+ $Xtemplate->process(\$out, $vars, \$output)
+ || return(header("500", "text/html", $Xtemplate->error()));
+
+ return(header("200", "text/html", $output));
+}
+
+
+#############################################################################
+# summary
+#############################################################################
+sub prog_summary {
+ return if(UptoDate());
+ my $time = $q->param("time");
+ my $search = $q->param("search");
+
+ # zeitpunkt bestimmen
+ my $event_time;
+ if($time ne "") {
+ my ($hour, $minute);
+ if($time =~ /(\d{1,2})(\D?)(\d{1,2})/) {
+ if(length($1) == 1 && length($3) == 1 && !$2) {
+ $hour = $1 . $3;
+ } else {
+ ($hour, $minute) = ($1, $3);
+ }
+ } elsif($time =~ /\d/) {
+ $hour = $time;
+ }
+
+ if($hour <= my_strftime("%H") && $minute < my_strftime("%M")) {
+ $event_time = timelocal(
+ 0,
+ $minute,
+ $hour,
+ my_strftime("%d", time + 86400),
+ (my_strftime("%m", time + 86400) - 1),
+ my_strftime("%Y")
+ ) + 1;
+ } else {
+ $event_time = timelocal(
+ 0,
+ $minute,
+ $hour,
+ my_strftime("%d"),
+ (my_strftime("%m") - 1),
+ my_strftime("%Y")
+ ) + 1;
+ }
+ } else {
+ $event_time = time();
+ }
+
+ my(@show, @shows, @temp);
+ for(keys(%EPG)) {
+ for my $event (@{$EPG{$_}}) {
+ if(!$search) {
+ if($CONFIG{CHANNELS_WANTED_SUMMARY}) {
+ my $f = 0;
+ for my $n (split(/\,/, $CONFIG{CHANNELS_WANTED})) {
+ ($f = 1) if($n eq $event->{vdr_id});
+ }
+ next if(!$f);
+ }
+ next if($event_time > $event->{stop});
+ } else {
+ my($found);
+ for my $word (split(/ +/, $search)) {
+ $found = 0;
+ for my $section (qw(title subtitle summary)) {
+ if($event->{$section} =~ /$word/i) {
+ $found = 1;
+ }
+ }
+ if(!$found) {
+ last;
+ }
+ }
+ next if(!$found);
+ }
+
+ push(@show, {
+ date => my_strftime("%d.%m.%y", $event->{start}),
+ longdate => sprintf("%s., %s. %s %s",
+ substr(FullDay(my_strftime("%w", $event->{start}), $event->{start}), 0, 2),
+ my_strftime("%d", $event->{start}),
+ FullMonth(my_strftime("%m", $event->{start})),
+ my_strftime("%Y", $event->{start})),
+ start => my_strftime("%H:%M", $event->{start}),
+ stop => my_strftime("%H:%M", $event->{stop}),
+ title => $event->{title},
+ subtitle => length($event->{subtitle}) > 30 ? substr($event->{subtitle}, 0, 30) . "..." : $event->{subtitle},
+ progname => $event->{channel_name},
+ summary => length($event->{summary}) > 120 ? substr($event->{summary}, 0, 120) . "..." : $event->{summary},
+ vdr_id => $event->{vdr_id},
+ proglink => sprintf("%s?aktion=prog_list&vdr_id=%s", $MyURL, $event->{vdr_id}),
+ switchurl=> sprintf("%s?aktion=prog_switch&channel=%s", $MyURL, $event->{vdr_id}),
+ streamurl=> sprintf("%s?aktion=live_stream&channel=%s", $MyURL, $event->{vdr_id}),
+ infurl => $event->{summary} ? sprintf("%s?aktion=prog_detail&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}) : undef,
+ recurl => sprintf("%s?aktion=timer_new_form&epg_id=%s&vdr_id=%s", $MyURL, $event->{event_id}, $event->{vdr_id}),
+ anchor => $event->{event_id}
+ });
+ last if(!$search);
+ }
+ }
+
+ # needed for vdr 1.0.x, dunno why
+ @show = sort({ $a->{vdr_id} <=> $b->{vdr_id} } @show);
+
+ #
+ my @status;
+ my $spalten = 3;
+ for(my $i = 0; $i <= $#show; $i++) {
+ undef(@temp);
+ undef(@status);
+ for(my $z = 0; $z < $spalten; $i++, $z++) {
+ push(@temp, $show[$i]);
+ push(@status, $show[$i]);
+ }
+ $i--;
+ push(@shows, { day => [ @temp ], status => [ @status ] });
+ }
+
+ #
+ my $template = TemplateNew("prog_summary.html");
+ $template->param(
+ rows => \@shows,
+ now => strftime("%H:%M", localtime($event_time)),
+ nowurl => $MyURL . "?aktion=prog_summary",
+ url => $MyURL
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+
+#############################################################################
+# recordings
+#############################################################################
+sub rec_list {
+ my(@all_recordings, @recordings);
+
+ #
+ my $ffserver = `ps -ef | grep ffserver | wc -l`;
+ my $desc;
+ if(!defined($q->param("desc"))) {
+ $desc = 1;
+ } else {
+ $q->param("desc") ? ($desc = 1) : ($desc = 0);
+ }
+ my $sortby = $q->param("sortby");
+ ($sortby = "name") if(!$sortby);
+ my $parent = $q->param("parent");
+ if(!$parent) {
+ $parent = 0;
+ }
+
+ my(@response) = SendCMD("lstr");
+ for my $recording (@response) {
+ chomp($recording);
+ next if(length($recording) == 0);
+ if($recording =~ /^No recordings available/) {
+ last;
+ }
+ my($new);
+ my($id, $date, $time, $name) = split(/ +/, $recording, 4);
+
+ #
+ if(substr($time, 5, 1) eq "*") {
+ $new = 1;
+ $time =~ s/\*//;
+ }
+
+ #
+ my(@tmp, @tmp2, $serie, $episode, $parent, $dirname, $dirname1);
+ if($name =~ /~/) {
+ @tmp2 = split(" ", $name, 2);
+ if(scalar(@tmp2) > 1) {
+ if(ord(substr($tmp2[0], length($tmp2[0])-1, 1)) == 180) {
+ @tmp = split("~", $tmp2[1]);
+ $name = "$tmp2[0] $tmp[scalar(@tmp) - 1]";
+ } else {
+ @tmp = split("~", $name);
+ $name = $tmp[scalar(@tmp) - 1];
+ }
+ } else {
+ @tmp = split("~", $name);
+ $name = $tmp[scalar(@tmp) - 1];
+ }
+ $dirname = $tmp[scalar(@tmp) - 2];
+ $parent = crypt($dirname, salt($dirname));
+ }
+ $parent = 0 if(!$parent);
+
+ # create subfolders
+ for(my $i = 0; $i < scalar(@tmp) - 1; $i++) {
+ my $recording_id = crypt($tmp[$i], salt($tmp[$i]));
+ my $parent;
+ if($i != 0) {
+ $parent = crypt($tmp[$i - 1], salt($tmp[$i - 1]));
+ } else {
+ $parent = 0;
+ }
+
+ my $found = 0;
+ for my $recording (@all_recordings) {
+ next if(!$recording->{isfolder});
+ if($recording->{recording_id} eq $recording_id && $recording->{parent} eq $parent) {
+ $found = 1;
+ }
+ }
+ if(!$found) {
+ push(@all_recordings, {
+ name => $tmp[$i],
+ recording_id => $recording_id,
+ parent => $parent,
+ isfolder => 1,
+ date => 0,
+ time => 0,
+ sortbydate => ($sortby eq "date") ? 1 : 0,
+ sortbytime => ($sortby eq "time") ? 1 : 0,
+ sortbyname => ($sortby eq "name") ? 1 : 0,
+ infurl => sprintf("%s?aktion=rec_list&parent=%s", $MyURL, $recording_id)
+ });
+ }
+ }
+
+ #
+ push(@all_recordings, {
+ sse => timelocal(undef, substr($time, 3, 2),
+ substr($time, 0, 2), substr($date, 0, 2),
+ (substr($date, 3, 2) - 1),
+ my_strftime("%Y")),
+ date => $date,
+ time => $time,
+ name => $name,
+ serie => $serie,
+ episode => $episode,
+ parent => $parent,
+ new => $new,
+ id => $id,
+ sortbydate => ($sortby eq "date") ? 1 : 0,
+ sortbytime => ($sortby eq "time") ? 1 : 0,
+ sortbyname => ($sortby eq "name") ? 1 : 0,
+ delurl => $MyURL . "?aktion=rec_delete&id=$id",
+ editurl => $MyURL . "?aktion=rec_edit&id=$id",
+ infurl => $MyURL . "?aktion=rec_detail&id=$id",
+ streamurl => $MyURL . "?aktion=rec_stream&id=$id"
+ });
+ }
+
+ # XXX doesn't count subsub-folders
+ for(@all_recordings) {
+ if($_->{parent} eq $parent && $_->{isfolder}) {
+ for my $recording (@all_recordings) {
+ if($recording->{parent} eq $_->{recording_id}) {
+ $_->{date}++;
+ $_->{time}++ if($recording->{new});
+ }
+ }
+ }
+ }
+
+ # create path array
+ my @path; my $fuse = 0;
+ my $rparent = $parent;
+ while($rparent) {
+ for my $recording (@all_recordings) {
+ if($recording->{recording_id} eq $rparent) {
+ push(@path, {
+ name => $recording->{name},
+ url => ($recording->{recording_id} ne $parent) ?
+ sprintf("%s?aktion=rec_list&parent=%s",
+ $MyURL, $recording->{recording_id}) : "" });
+ $rparent = $recording->{parent};
+ last;
+ }
+ }
+ $fuse++;
+ last if($fuse > 100);
+ }
+ push(@path, {
+ name => $COMMONMESSAGE{OVERVIEW},
+ url => ($parent ne 0) ?
+ sprintf("%s?aktion=rec_list&parent=%s", $MyURL, 0) : "" });
+ @path = reverse(@path);
+
+ # filter
+ if(defined($parent)) {
+ for my $recording (@all_recordings) {
+ if($recording->{parent} eq $parent) {
+ push(@recordings, $recording);
+ }
+ }
+ } else {
+ @recordings = @all_recordings;
+ }
+
+
+ #
+ if($sortby eq "time") {
+ if(!$desc) {
+ @recordings = sort({ $b->{time} <=> $a->{time} } @recordings);
+ } else {
+ @recordings = sort({ $a->{time} <=> $b->{time} } @recordings);
+ }
+ } elsif($sortby eq "name") {
+ if(!$desc) {
+ @recordings = sort({ lc($b->{name}) cmp lc($a->{name}) } @recordings);
+ } else {
+ @recordings = sort({ lc($a->{name}) cmp lc($b->{name}) } @recordings);
+ }
+ } elsif($sortby eq "date") {
+ if(!$desc) {
+ @recordings = sort({ $a->{sse} <=> $b->{sse} } @recordings);
+ } else {
+ @recordings = sort({ $b->{sse} <=> $a->{sse} } @recordings);
+ }
+ }
+ $desc ? ($desc = 0) : ($desc = 1);
+
+ #
+ my($total, $minutes_total, $free, $minutes_free, $percent) = VideoDiskFree();
+
+
+ my $template = TemplateNew("rec_list.html");
+ $template->param(
+ recloop => \@recordings,
+ sortbydateurl => "$MyURL?aktion=rec_list&parent=$parent&sortby=date&desc=$desc&parent=$parent",
+ sortbytimeurl => "$MyURL?aktion=rec_list&parent=$parent&sortby=time&desc=$desc&parent=$parent",
+ sortbynameurl => "$MyURL?aktion=rec_list&parent=$parent&sortby=name&desc=$desc&parent=$parent",
+ sortbydate => ($sortby eq "date") ? 1 : 0,
+ sortbytime => ($sortby eq "time") ? 1 : 0,
+ sortbyname => ($sortby eq "name") ? 1 : 0,
+ desc => $desc,
+ disk_total => $total,
+ disk_free => $free,
+ disk_percent => $percent,
+ minutes_free => $minutes_free,
+ minutes_total => $minutes_total,
+ path => \@path,
+ url => $MyURL,
+ help_url => HelpURL("rec_list"),
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub rec_detail {
+ my($id) = $q->param('id');
+
+ #
+ my($i, $title);
+ for(SendCMD("lstr")) {
+ ($i, undef, undef, $title) = split(/ +/, $_, 4);
+ last if($id == $i);
+ }
+ chomp($title);
+
+ #
+ my($text); my($first) = 1;
+ my($result) = SendCMD("lstr $id");
+ if($result !~ /No summary availab/i) {
+ for(split(/\|/, $result)) {
+ if($_ ne (split(/\~/, $title))[1]) {
+ if($first && $title !~ /\~/ && length($title) < 20) {
+ $title .= "~" . $_;
+ $first = 0;
+ } else {
+ $text .= "$_ ";
+ }
+ }
+ }
+ }
+
+ #
+ $title =~ s/\~/ - /;
+
+ my $template = TemplateNew("prog_detail.html");
+ $template->param(
+ text => $text ? $text : "",
+ title => $title
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub rec_delete {
+ my($id) = $q->param('id');
+ if($id) {
+ SendCMD("delr $id");
+ } else {
+ for($q->param) {
+ if(/xxxx_(.*)/) {
+ SendCMD("delr $1");
+ }
+ }
+ }
+ CloseSocket();
+ return(headerForward(RedirectToReferer("$MyURL?aktion=rec_list")));
+}
+
+sub rec_edit {
+
+ my $id = $q->param("id");
+ my($i, $title);
+
+ for(SendCMD("lstr")) {
+ ($i, undef, undef, $title) = split(/ +/, $_, 4);
+ last if($id == $i);
+ }
+ chomp($title);
+
+ my $template = TemplateNew("rec_edit.html");
+ $template->param(
+ url => $MyURL,
+ title => $title,
+ id => $id,
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub rec_rename {
+ my($id) = $q->param('id');
+ my($nn) = $q->param('nn');
+ if($id) {
+ SendCMD("RENR $id $nn");
+# } else {
+# for($q->param) {
+# if(/xxxx_(.*)/) {
+# SendCMD("renr $1 $_[0]");
+# }
+# }
+ }
+ CloseSocket();
+ headerForward("$MyURL?aktion=rec_list");
+}
+
+#############################################################################
+# configuration
+#############################################################################
+sub conf_list {
+ return if(UptoDate());
+
+ sub ApplyConfig {
+ for($q->param) {
+ if(/[A-Z]+/) {
+ $CONFIG{$_} = $q->param($_);
+ }
+ }
+ #
+ LoadTranslation();
+ }
+
+ sub WriteConfig {
+ open(CONF, ">$CONFFILE") || print "Can't open $CONFFILE! ($!)\n";
+ for my $key (sort(keys(%CONFIG))) {
+ print CONF "$key = $CONFIG{$key}\n";
+ }
+ close(CONF);
+ }
+
+ if($q->param("submit") eq ">>>>>") {
+ for my $vdr_id ($q->param("all_channels")) {
+ $CONFIG{CHANNELS_WANTED} = csvAdd($CONFIG{CHANNELS_WANTED}, $vdr_id);
+ }
+ ApplyConfig(); WriteConfig();
+ } elsif($q->param("submit") eq "<<<<<") {
+ for my $vdr_id ($q->param("selected_channels")) {
+ $CONFIG{CHANNELS_WANTED} = csvRemove($CONFIG{CHANNELS_WANTED}, $vdr_id);
+ }
+ ApplyConfig(); WriteConfig();
+ } elsif($q->param("save")) {
+ ApplyConfig(); WriteConfig();
+ } elsif($q->param("apply")) {
+ ApplyConfig();
+ }
+
+ #
+ my(@loginpages);
+ my $i = 0;
+ for my $loginpage (@LOGINPAGES) {
+ push(@loginpages, {
+ id => $i,
+ name => $LOGINPAGES_DESCRIPTION[$i],
+ current => ($CONFIG{LOGINPAGE} == $i) ? 1 : 0
+ });
+ $i++;
+ }
+
+ #
+ my @lang;
+ for my $dir (<$TEMPLATEDIR/*>) {
+ next if(!-d $dir);
+ $dir =~ s/.*\///g;
+ my $found = 0;
+ for(@lang) { ($found = 1) if($1 && ($_->{name} eq $1)); }
+ if(!$found) {
+ push(@lang, {
+ name => $dir,
+ aklang => ($CONFIG{LANGUAGE} eq $dir) ? 1 : 0,
+ });
+ }
+ }
+
+ #
+ my (@all_channels, @selected_channels);
+ for my $channel (@CHAN) {
+ #
+ push(@all_channels, {
+ name => $channel->{name},
+ vdr_id => $channel->{vdr_id}
+ });
+
+ #
+ my $found = 0;
+ for(split(",", $CONFIG{CHANNELS_WANTED})) {
+ if($_ eq $channel->{vdr_id}) {
+ $found = 1;
+ }
+ }
+ next if !$found;
+ push(@selected_channels, {
+ name => $channel->{name},
+ vdr_id => $channel->{vdr_id}
+ });
+ }
+
+ my @skinlist;
+ foreach my $file (glob(sprintf("%s/%s/*",$TEMPLATEDIR, $CONFIG{LANGUAGE}))) {
+ my $name = (split('\/', $file))[-1];
+ push(@skinlist,{
+ name => $name,
+ sel => ($CONFIG{SKIN} eq $name ? 1 : 0)
+ }) if(-d $file);
+ }
+
+ my $template = TemplateNew("config.html");
+ $template->param(
+ %CONFIG,
+ LANGLIST => \@lang,
+ ALL_CHANNELS => \@all_channels,
+ SELECTED_CHANNELS => \@selected_channels,
+ LOGINPAGES => \@loginpages,
+ SKINLIST => \@skinlist,
+ url => $MyURL,
+ help_url => HelpURL("help_url"),
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+#############################################################################
+# remote control
+#############################################################################
+sub rc_show {
+ my $template = TemplateNew("rc.html");
+ $template->param(
+ surl_0 => $MyURL . "?aktion=rc_hitk&key=0",
+ surl_1 => $MyURL . "?aktion=rc_hitk&key=1",
+ surl_2 => $MyURL . "?aktion=rc_hitk&key=2",
+ surl_3 => $MyURL . "?aktion=rc_hitk&key=3",
+ surl_4 => $MyURL . "?aktion=rc_hitk&key=4",
+ surl_5 => $MyURL . "?aktion=rc_hitk&key=5",
+ surl_6 => $MyURL . "?aktion=rc_hitk&key=6",
+ surl_7 => $MyURL . "?aktion=rc_hitk&key=7",
+ surl_8 => $MyURL . "?aktion=rc_hitk&key=8",
+ surl_9 => $MyURL . "?aktion=rc_hitk&key=9",
+
+ surl_power => $MyURL . "?aktion=rc_hitk&key=Power",
+
+ surl_ok => $MyURL . "?aktion=rc_hitk&key=Ok",
+
+ surl_menu => $MyURL . "?aktion=rc_hitk&key=Menu",
+ surl_back => $MyURL . "?aktion=rc_hitk&key=Back",
+
+ surl_up => $MyURL . "?aktion=rc_hitk&key=Up",
+ surl_down => $MyURL . "?aktion=rc_hitk&key=Down",
+ surl_left => $MyURL . "?aktion=rc_hitk&key=Left",
+ surl_right => $MyURL . "?aktion=rc_hitk&key=Right",
+
+ surl_red => $MyURL . "?aktion=rc_hitk&key=Red",
+ surl_green => $MyURL . "?aktion=rc_hitk&key=Green",
+ surl_blue => $MyURL . "?aktion=rc_hitk&key=Blue",
+ surl_yellow => $MyURL . "?aktion=rc_hitk&key=Yellow",
+
+ surl_volplus => $MyURL . "?aktion=rc_hitk&key=VolumePlus",
+ surl_volminus => $MyURL . "?aktion=rc_hitk&key=VolumeMinus",
+ url => sprintf("%s?aktion=grab_picture", $MyURL),
+ host => $CONFIG{VDR_HOST}
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub rc_hitk {
+ my $key = $q->param("key");
+ if($key eq "VolumePlus") {
+ $key = "Volume+";
+ }
+ if($key eq "VolumeMinus") {
+ $key = "Volume-";
+ }
+ SendCMD("hitk $key");
+ #XXX
+ SendFile("bilder/spacer.gif");
+}
+
+sub tv_show {
+ my $template = TemplateNew("tv.html");
+ $template->param(
+ url => sprintf("%s?aktion=grab_picture", $MyURL),
+ host => $CONFIG{VDR_HOST}
+ );
+ return(header("200", "text/html", $template->output));
+}
+
+sub show_help {
+ my $area = $q->param("area");
+ my $text;
+ if(length($HELP{$area}) == 0) {
+ $text = $HELP{ENOHELPMSG};
+ } else {
+ $text = $HELP{$area};
+ }
+ my $template = TemplateNew("prog_detail.html"); # XXX eigenes Template?
+ $template->param(text => $text);
+ return(header("200", "text/html", $template->output));
+}
+
+#############################################################################
+# experimental
+#############################################################################
+sub grab_picture {
+ my $size = $q->param("size");
+ my $file = "/tmp/vdr.jpg";
+ my $maxwidth = 768;
+ my $maxheight = 576;
+ my($width, $height);
+ if($size eq "full") {
+ ($width, $height) = ($maxwidth, $maxheight);
+ } elsif($size eq "half") {
+ ($width, $height) = ($maxwidth / 2, $maxheight / 2);
+ } elsif($size eq "quarter") {
+ ($width, $height) = ($maxwidth / 4, $maxheight / 4);
+ } else {
+ ($width, $height) = ($maxwidth / 4, $maxheight / 4);
+ }
+
+ SendCMD("grab $file jpeg 70 $width $height");
+ #SendCMD("grab $file jpeg");
+ if(-e $file && -r $file) {
+ return(header("200", "image/jpeg", ReadFile($file)));
+ } else {
+ print "Expected $file does not exist.\n";
+ print "Obviously VDR Admin could not find the screenshot file. Ensure that:\n";
+ print " - VDR has the rights to write $file\n";
+ print " - VDR and VDR Admin run on the same machine\n";
+ print " - VDR Admin may read $file\n";
+ print " - VDR has access to /dev/video* files\n";
+ print " - you have a full featured card\n";
+ }
+}
+
+sub force_update {
+ UptoDate(1);
+ RedirectToReferer("$MyURL?aktion=prog_summary");
+}
+
+#############################################################################
+# communikation with vdr
+#############################################################################
+package SVDRP;
+
+sub true () { main::true(); }
+sub false () { main::false(); };
+sub LOG_VDRCOM () { main::LOG_VDRCOM(); };
+sub CRLF () { main::CRLF(); };
+
+my($SOCKET, $EPGSOCKET, $query, $connected, $epg);
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = { };
+ bless($self, $class);
+ $connected = false;
+ $query = false;
+ $epg = false;
+ return $self;
+}
+
+sub myconnect {
+ my $this = shift;
+ if ( $epg && $CONFIG{EPG_DIRECT}) {
+ main::Log(LOG_VDRCOM, "LOG_VDRCOM: open EPG $CONFIG{EPG_FILENAME}");
+ open($EPGSOCKET,$CONFIG{EPG_FILENAME}) || main::HTMLError(sprintf("Failed to open %s", $CONFIG{EPG_FILENAME}));
+ return;
+ }
+ main::Log(LOG_VDRCOM, "LOG_VDRCOM: connect to $CONFIG{VDR_HOST}:$CONFIG{VDR_PORT}");
+
+ $SOCKET = IO::Socket::INET->new(
+ PeerAddr => $CONFIG{VDR_HOST},
+ PeerPort => $CONFIG{VDR_PORT},
+ Proto => 'tcp'
+ ) || main::HTMLError(sprintf($ERRORMESSAGE{CONNECT_FAILED}, $CONFIG{VDR_HOST}));
+
+ my $line;
+ $line = <$SOCKET>;
+ $connected = true;
+}
+
+sub close {
+ my $this = shift;
+ if( $epg && $CONFIG{EPG_DIRECT} ) {
+ main::Log(LOG_VDRCOM, "LOG_VDRCOM: closing EPG");
+ close $EPGSOCKET;
+ $epg=false;
+ return;
+ }
+ if($connected) {
+ main::Log(LOG_VDRCOM, "LOG_VDRCOM: closing connection");
+ command($this, "quit");
+ readoneline($this);
+ close $SOCKET if $SOCKET;
+ $connected = false;
+ }
+}
+
+sub command {
+ my $this = shift;
+ my $cmd = join("", @_);
+
+ if ( $cmd =~ /lste/ && $CONFIG{EPG_DIRECT} ) {
+ $epg=true;
+ main::Log(LOG_VDRCOM, sprintf("LOG_VDRCOM: special epg "));
+ } else {
+ $epg=false;
+ }
+ if(!$connected || $epg) {
+ myconnect($this);
+ }
+ if ( $epg ) {
+ $query = true;
+ return;
+ }
+
+ main::Log(LOG_VDRCOM, sprintf("LOG_VDRCOM: send \"%s\"", $cmd));
+ $cmd = $cmd . CRLF;
+ if($SOCKET) {
+ my $result = send($SOCKET, $cmd, 0);
+ if($result != length($cmd)) {
+ main::HTMLError($ERRORMESSAGE{SEND_COMMAND}, $CONFIG{VDR_HOST});
+ } else {
+ $query = true;
+ }
+ }
+}
+
+sub readoneline {
+ my $this = shift;
+ my $line;
+
+ if ( $epg && $CONFIG{EPG_DIRECT} ) {
+ $line = <$EPGSOCKET>;
+ $line =~ s/\n$//;
+ main::Log(LOG_VDRCOM, sprintf("LOG_VDRCOM: EPGread \"%s\"", $line));
+ $query=true;
+ return($line);
+ }
+
+ if($connected && $query) {
+ $line = <$SOCKET>;
+ $line =~ s/\r\n$//;
+ if(substr($line, 3, 1) ne "-") {
+ $query = 0;
+ }
+ $line = substr($line, 4, length($line));
+ main::Log(LOG_VDRCOM, sprintf("LOG_VDRCOM: read \"%s\"", $line));
+ return($line);
+ } else {
+ return undef;
+ }
+}
+#
+#############################################################################
+
+# EOF