summaryrefslogtreecommitdiff
path: root/lib/XXV
diff options
context:
space:
mode:
authorAndreas Brachold <vdr07@deltab.de>2007-08-13 18:41:27 +0000
committerAndreas Brachold <vdr07@deltab.de>2007-08-13 18:41:27 +0000
commitbcbf441e09fb502cf64924ff2530fa144bdf52c5 (patch)
treef377707a2dac078db8cd0c7d7abfe69ac1006d71 /lib/XXV
downloadxxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.gz
xxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.bz2
* Move files to trunk
Diffstat (limited to 'lib/XXV')
-rw-r--r--lib/XXV/MODULES/AUTOTIMER.pm1359
-rw-r--r--lib/XXV/MODULES/CHANNELS.pm1018
-rw-r--r--lib/XXV/MODULES/CHRONICLE.pm249
-rw-r--r--lib/XXV/MODULES/CONFIG.pm283
-rw-r--r--lib/XXV/MODULES/EPG.pm1243
-rw-r--r--lib/XXV/MODULES/EVENTS.pm190
-rw-r--r--lib/XXV/MODULES/GRAB.pm290
-rw-r--r--lib/XXV/MODULES/HTTPD.pm588
-rw-r--r--lib/XXV/MODULES/INTERFACE.pm179
-rw-r--r--lib/XXV/MODULES/LOGREAD.pm221
-rw-r--r--lib/XXV/MODULES/MEDIALIB.pm1328
-rw-r--r--lib/XXV/MODULES/MUSIC.pm1352
-rw-r--r--lib/XXV/MODULES/RECORDS.pm2136
-rw-r--r--lib/XXV/MODULES/REMOTE.pm279
-rw-r--r--lib/XXV/MODULES/REPORT.pm288
-rw-r--r--lib/XXV/MODULES/ROBOT.pm180
-rw-r--r--lib/XXV/MODULES/SHARE.pm280
-rw-r--r--lib/XXV/MODULES/STATUS.pm771
-rw-r--r--lib/XXV/MODULES/STREAM.pm179
-rw-r--r--lib/XXV/MODULES/SVDRP.pm228
-rw-r--r--lib/XXV/MODULES/TELNET.pm326
-rw-r--r--lib/XXV/MODULES/TIMERS.pm1721
-rw-r--r--lib/XXV/MODULES/USER.pm919
-rw-r--r--lib/XXV/MODULES/VTX.pm1396
-rw-r--r--lib/XXV/MODULES/WAPD.pm354
-rw-r--r--lib/XXV/OUTPUT/Ajax.pm231
-rw-r--r--lib/XXV/OUTPUT/Console.pm741
-rw-r--r--lib/XXV/OUTPUT/Dump.pm62
-rw-r--r--lib/XXV/OUTPUT/HTML/PUSH.pm95
-rw-r--r--lib/XXV/OUTPUT/HTML/WAIT.pm169
-rw-r--r--lib/XXV/OUTPUT/Html.pm851
-rw-r--r--lib/XXV/OUTPUT/NEWS/JABBER.pm296
-rw-r--r--lib/XXV/OUTPUT/NEWS/MAIL.pm313
-rw-r--r--lib/XXV/OUTPUT/NEWS/RSS.pm233
-rw-r--r--lib/XXV/OUTPUT/NEWS/VDR.pm165
-rw-r--r--lib/XXV/OUTPUT/Wml.pm431
36 files changed, 20944 insertions, 0 deletions
diff --git a/lib/XXV/MODULES/AUTOTIMER.pm b/lib/XXV/MODULES/AUTOTIMER.pm
new file mode 100644
index 0000000..812da07
--- /dev/null
+++ b/lib/XXV/MODULES/AUTOTIMER.pm
@@ -0,0 +1,1359 @@
+package XXV::MODULES::AUTOTIMER;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+
+
+# ------------------
+# Name: module
+# Descr: The standard routine to describe the Plugin
+# Usage: my $modhash = $obj->module();
+# ------------------
+sub module {
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'AUTOTIMER',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module search for epg entries with an user defined text and create new timer.'),
+ Version => '0.91',
+ Date => '2007-01-11',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ exclude => {
+ description => gettext('Exclude channel list for autotimer.'),
+ type => 'string',
+ default => 'POS > 50',
+ check => sub{
+ my $value = shift;
+ if(index($value, ',') != -1) {
+ return 'POS > 50'; # Nur um sicher zu sein, das die alten Werte nicht übernommen werden.
+ } else {
+ return $value;
+ }
+ },
+ },
+ },
+ Commands => {
+ astatus => {
+ description => gettext('Status from autotimers'),
+ short => 'as',
+ callback => sub{ $obj->status(@_) },
+ DenyClass => 'alist',
+ },
+ anew => {
+ description => gettext("Create a new autotimer"),
+ short => 'an',
+ callback => sub{ $obj->autotimerCreate(@_) },
+ Level => 'user',
+ DenyClass => 'aedit',
+ },
+ adelete => {
+ description => gettext("Delete a autotimer 'aid'"),
+ short => 'ad',
+ callback => sub{ $obj->autotimerDelete(@_) },
+ Level => 'user',
+ DenyClass => 'aedit',
+ },
+ aedit => {
+ description => gettext("Edit a autotimer 'aid'"),
+ short => 'ae',
+ callback => sub{ $obj->autotimerEdit(@_) },
+ Level => 'user',
+ DenyClass => 'aedit',
+ },
+ asearch => {
+ description => gettext("Search for autotimer with text 'aid'"),
+ short => 'ase',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'alist',
+ },
+ alist => {
+ description => gettext("List the autotimer 'aid'"),
+ short => 'al',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'alist',
+ },
+ aupdate => {
+ description => gettext("Start the autotimer process"),
+ short => 'au',
+ callback => sub{ $obj->autotimer(@_) },
+ Level => 'user',
+ DenyClass => 'aedit',
+ },
+ atoggle => {
+ description => gettext("Toggle autotimer on or off 'aid'"),
+ short => 'at',
+ callback => sub{ $obj->autotimerToggle(@_) },
+ Level => 'user',
+ DenyClass => 'aedit',
+ },
+ asuggest => {
+ hidden => 'yes',
+ callback => sub{ $obj->suggest(@_) },
+ DenyClass => 'alist',
+ },
+ },
+ RegEvent => {
+ 'newTimerfromAutotimer' => {
+ Descr => gettext('Create event entries, if a new timer from autotimer created.'),
+
+ # You have this choices (harmless is default):
+ # 'harmless', 'interesting', 'veryinteresting', 'important', 'veryimportant'
+ Level => 'veryinteresting',
+
+ # Search for a spezial Event.
+ # I.e.: Search for an LogEvent with match
+ # "Sub=>text" = subroutine =~ /text/
+ # "Msg=>text" = logmessage =~ /text/
+ # "Mod=>text" = modname =~ /text/
+ SearchForEvent => {
+ Sub => 'AUTOTIMER',
+ Msg => 'Save timer',
+ },
+ # Search for a Match and extract the information
+ # of the TimerId
+ # ...
+ Match => {
+ TimerId => qr/TimerId\:\s+\"(\d+)\"/s,
+ },
+ Actions => [
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ my $desc = getDataById($timer->{eventid}, 'EPG', 'eventid') if($timer->{eventid});
+ my $autotimer = getDataById($timer->{AutotimerId}, 'AUTOTIMER', 'Id');
+ my $title = sprintf(gettext("Autotimer('%s') found: %s"),
+ $autotimer->{Search}, $timer->{File});
+ my $description = sprintf(gettext("At: %s to %s\nDescription: %s"),
+ $timer->{NextStartTime},
+ fmttime($timer->{Stop}),
+ $desc && $desc->{description} ? $desc->{description} : ''
+ );
+
+ main::getModule('REPORT')->news($title, $description, "display", $timer->{eventid}, "interesting");
+ }
+ |,
+ ],
+
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+# Name: status
+# Descr: Standardsubroutine to report statistical data for Report Plugin.
+# Usage: my $report = $obj->status([$watcher, $console]);
+# ------------------
+sub status {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $lastReportTime = shift || 0;
+
+ my $sql = qq|
+SELECT
+ t.Id as __Id,
+ t.File,
+ t.Status as __Status,
+ c.Name as Channel,
+ c.Pos as __Pos,
+ DATE_FORMAT(t.Day, '%e.%c.%Y') as Day,
+ t.Start,
+ t.Stop,
+ t.Priority,
+ UNIX_TIMESTAMP(t.NextStartTime) as __Day,
+ t.Collision as __Collision,
+ t.eventid as __NextEpgId,
+ t.AutotimerId as __AutotimerId
+FROM
+ TIMERS as t,
+ CHANNELS as c
+WHERE
+ t.ChannelID = c.Id
+ and UNIX_TIMESTAMP(t.addtime) > ?
+ and t.AutotimerId > 0
+ORDER BY
+ t.NextStartTime|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($lastReportTime)
+ or return error "Can't execute query: $sth->errstr.";
+ my $erg = $sth->fetchall_arrayref();
+ for(@$erg) {
+ $_->[6] = fmttime($_->[6]);
+ $_->[7] = fmttime($_->[7]);
+ }
+
+ unshift(@$erg, $fields);
+ return {
+ message => sprintf(gettext('Autotimer has %d new timer programmed, since last report at %s'),
+ (scalar @$erg - 1), scalar localtime($lastReportTime)),
+ table => $erg,
+ };
+}
+
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # file
+ $self->{file} = $self->{config}->{file};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0, panic("Session to database is'nt connected")
+ unless($obj->{dbh});
+
+ # don't remove old table, if updated rows => warn only
+ tableUpdated($obj->{dbh},'AUTOTIMER',19,0);
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS AUTOTIMER (
+ Id int(11) unsigned auto_increment NOT NULL,
+ Activ enum('y', 'n') default 'y',
+ Done set('timer', 'recording', 'chronicle' ) NOT NULL default 'timer',
+ Search text NOT NULL default '',
+ InFields set('title', 'subtitle', 'description' ) NOT NULL,
+ Channels text default '',
+ Start char(4) default '0000',
+ Stop char(4) default '0000',
+ MinLength tinyint default NULL,
+ Priority tinyint(2) default NULL,
+ Lifetime tinyint(2) default NULL,
+ Dir text,
+ VPS enum('y', 'n') default 'n',
+ prevminutes tinyint default NULL,
+ afterminutes tinyint default NULL,
+ Weekdays set('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'),
+ startdate datetime default NULL,
+ stopdate datetime default NULL,
+ count int(11) default NULL,
+ PRIMARY KEY (Id)
+ ) COMMENT = '$version'
+ |);
+
+ main::after(sub{
+ my $m = main::getModule('EPG');
+ $m->updated(sub{
+ return 0 if($obj->{active} ne 'y');
+
+ lg 'Start autotimer callback to find new events!';
+ return $obj->autotimer();
+
+ });
+ return 1;
+ }, "AUTOTIMER: Install callback at update epg data ...", 30);
+
+ return 1;
+}
+
+# ------------------
+# Name: autotimer
+# Descr: Routine to parse the EPG Data for users Autotimer.
+# If Autotimerid given, then will this search only
+# for this Autotimer else for all.
+# Usage: $obj->autotimer([$autotimerid]);
+# ------------------
+sub autotimer {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $autotimerid = shift;
+
+ # Get Autotimer
+ my $sth;
+ if($autotimerid) {
+ $sth = $obj->{dbh}->prepare('select * from AUTOTIMER where Activ = "y" AND Id = ? order by Id');
+ $sth->execute($autotimerid)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ } else {
+ $sth = $obj->{dbh}->prepare('select * from AUTOTIMER where Activ = "y" order by Id');
+ $sth->execute()
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+ my $att = $sth->fetchall_hashref('Id');
+
+ my $waiter;
+ if(ref $console && !$autotimerid && $console->typ eq 'HTML') {
+ my $zaehler = scalar keys %$att;
+ $waiter = $console->wait(gettext("Look for autotimer ..."), 0, ++$zaehler, 'no');
+ }
+
+ my $l = 0; # Lines for Waiter
+ my $C = 0; # Total of created and modifed timers
+ my $M = 0;
+ my $log;
+
+ my $vdrVersion = main::getVdrVersion();
+
+ # Get Timersmodule
+ my $timermod = main::getModule('TIMERS');
+ foreach my $id (sort keys %$att) {
+ my $a = $att->{$id};
+
+ $waiter->next(++$l, undef, sprintf(gettext("Look for autotimer with ID(%d) with search '%s'"), $id, $a->{Search}))
+ if(ref $waiter);
+
+ if(ref $console && $autotimerid) {
+ $console->message(' ') if($console->{TYP} eq 'HTML');
+ $console->message(sprintf(gettext("Look for autotimer with ID(%d) with search '%s'"), $id, $a->{Search}));
+ }
+
+ # Build SQL Command and run it ....
+ my $events = $obj->_eventsearch($a, $timermod) || next;
+
+ # Only search for one at?
+ if(ref $console && $autotimerid) {
+ $console->message(sprintf(gettext("Found %d entries for '%s' in EPG database."), scalar keys %$events, $a->{Search}));
+ foreach my $Id (sort keys %$events) {
+ my $output = {
+ gettext("Channel") => $events->{$Id}->{Channel},
+ gettext("Title") => $events->{$Id}->{Title},
+ gettext("Subtitle") => $events->{$Id}->{Subtitle},
+ gettext("Day") => $events->{$Id}->{Day},
+ gettext("Start") => fmttime($events->{$Id}->{Start}),
+ gettext("Stop") => fmttime($events->{$Id}->{Stop}),
+ gettext("Description") => $events->{$Id}->{Summary},
+ };
+ $console->table($output);
+ };
+ }
+
+ # Every found and save this as timer
+ my $c = 0;
+ my $m = 0;
+ foreach my $Id (sort keys %$events) {
+ $events->{$Id}->{Activ} = 'y';
+ $events->{$Id}->{VPS} = ($events->{$Id}->{VpsStart} && $a->{VPS} eq 'y') ? 'y' : '';
+ $events->{$Id}->{Priority} = $a->{Priority};
+ $events->{$Id}->{Lifetime} = $a->{Lifetime};
+
+ $events->{$Id}->{File} = $obj->_placeholder($events->{$Id}, $a);
+
+ if($events->{$Id}->{VPS} eq 'y') {
+ $events->{$Id}->{Start} = $events->{$Id}->{VpsStart};
+ $events->{$Id}->{Stop} = $events->{$Id}->{VpsStop};
+ }
+
+ my $nexttime = $timermod->getNextTime( $events->{$Id}->{Day} , $events->{$Id}->{Start},$events->{$Id}->{Stop} )
+ or error(sprintf("Can't get the nexttime for this autotimer: %d", $events->{$Id}->{eventid}));
+
+ # Add anchor for reidentify timer
+ my $aidcomment = sprintf('#~AT[%d]', $id);
+
+ if($vdrVersion >= 10344){
+ $events->{$Id}->{Summary} = $aidcomment;
+ } else {
+ $events->{$Id}->{Summary} .= $aidcomment;
+ }
+
+ my @parameters = ($events->{$Id}, $nexttime, $aidcomment);
+
+ # Wished timer already exist with same data from autotimer ?
+ next if($obj->_timerexists(@parameters));
+
+ # Adjust timers set by the autotimer
+ my $timerID = $obj->_timerexistsfuzzy(@parameters);
+
+ if(!$timerID && $a->{Done}) {
+
+ my @done = split(',', $a->{Done});
+
+ # Ignore timer if it already with same title recorded
+ if(grep(/^chronicle$/, @done) && $obj->_chronicleexists(@parameters)) {
+ lg sprintf("Don't create timer from AT(%d) '%s', because found same data on chronicle", $id, $events->{$Id}->{File});
+ next;
+ }
+
+ # Ignore timer if it already with same title recorded
+ if(grep(/^recording$/, @done) && $obj->_recordexists(@parameters)){
+ lg sprintf("Don't create timer from AT(%d) '%s', because found same data on recordings", $id, $events->{$Id}->{File});
+ next;
+ }
+ # Ignore timer if it already a timer with same title programmed, on other place
+ if(grep(/^timer$/, @done) && $obj->_timerexiststitle(@parameters)){
+ lg sprintf("Don't create timer from AT(%d) '%s', because found same data on other timers", $id, $events->{$Id}->{File});
+ next;
+ }
+ }
+
+ my $error = 0;
+ my $erg = $timermod->saveTimer($events->{$Id}, $timerID ? $timerID : undef);
+ foreach my $zeile (@$erg) {
+ if($zeile =~ /^(\d{3})\s+(.+)/) {
+ $error = $2 if(int($1) >= 500);
+ }
+ }
+ if($error) {
+ $console->err(sprintf(gettext("Can't save timer for '%s' : %s"), $events->{$Id}->{File}, $error))
+ if(ref $console && $autotimerid);
+ } else {
+ if($timerID) {
+ ++$m;
+ $console->message(sprintf(gettext("Modify timer for '%s'."), $events->{$Id}->{File}))
+ if(ref $console && $autotimerid);
+ } else {
+ ++$c;
+ $console->message(sprintf(gettext("Create timer for '%s'."), $events->{$Id}->{File}))
+ if(ref $console && $autotimerid);
+ }
+ }
+ }
+ $C += $c;
+ $M += $m;
+ if($c) {
+ my $msg = sprintf(gettext("Create %d timer for '%s'."), $c, $a->{Search});
+ if(ref $console && $autotimerid) {
+ $console->message($msg);
+ }
+ else {
+ push(@{$log},$msg);
+ }
+ }
+ if($m) {
+ my $msg = sprintf(gettext("Modify %d timer for '%s'."), $m, $a->{Search});
+ if(ref $console && $autotimerid) {
+ $console->message($msg);
+ }
+ else {
+ push(@{$log},$msg);
+ }
+ }
+ }
+
+ $waiter->next(undef,undef,gettext('Read new timer in data base.'))
+ if(ref $waiter);
+
+ sleep 1;
+
+ $timermod->readData();
+
+ # last call of waiter
+ $waiter->end() if(ref $waiter);
+
+ if(ref $console) {
+ $console->start() if(ref $waiter);
+ unshift(@{$log},sprintf(gettext("Autotimer process has %d timers created and %d timers modified."), $C, $M));
+ lg join("\n", @$log);
+ $console->message($log);
+ $console->link({
+ text => gettext("Back to autotimer list"),
+ url => "?cmd=alist",
+ }) if($console->typ eq 'HTML');
+ }
+
+ return 1;
+}
+
+# ------------------
+# Name: autotimerCreate
+# Descr: Routine to display the create form for Autotimer.
+# Usage: $obj->autotimerCreate($watcher, $console, [$userdata]);
+# ------------------
+sub autotimerCreate {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $timerid = shift || 0;
+ my $data = shift || 0;
+
+ $obj->autotimerEdit($watcher, $console, $timerid, $data);
+}
+
+# ------------------
+# Name: autotimerEdit
+# Descr: Routine to display the edit form for Autotimer.
+# Usage: $obj->autotimerEdit($watcher, $console, [$atid], [$userdata]);
+# ------------------
+sub autotimerEdit {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $timerid = shift || 0;
+ my $data = shift || 0;
+
+ my $mod = main::getModule('CHANNELS');
+ my $modT = main::getModule('TIMERS');
+
+ my $epg;
+ if($timerid and not ref $data) {
+ my $sth = $obj->{dbh}->prepare("select * from AUTOTIMER where Id = ?");
+ $sth->execute($timerid)
+ or return $console->err(sprintf(gettext("Autotimer with ID '%s' does not exist in the database!"),$timerid));
+ $epg = $sth->fetchrow_hashref();
+
+ # Channels Ids in Namen umwandeln
+ my @channels = map { $_ = $mod->ChannelToPos($_) } split(/[\s|,]+/, $epg->{Channels});
+ $epg->{Channels} = \@channels;
+
+ # question erwartet ein Array
+ my @done = split(/\s*,\s*/, $epg->{Done});
+ $epg->{Done} = \@done;
+ my @infields = split(/\s*,\s*/, $epg->{InFields});
+ $epg->{InFields} = \@infields;
+ my @weekdays = split(/\s*,\s*/, $epg->{Weekdays});
+ $epg->{Weekdays} = \@weekdays;
+
+ } elsif (ref $data eq 'HASH') {
+ $epg = $data;
+ }
+
+ my %wd = (
+ 'Mon' => gettext('Mon'),
+ 'Tue' => gettext('Tue'),
+ 'Wed' => gettext('Wed'),
+ 'Thu' => gettext('Thu'),
+ 'Fri' => gettext('Fri'),
+ 'Sat' => gettext('Sat'),
+ 'Sun' => gettext('Sun')
+ );
+
+ my %in = (
+ 'title' => gettext('Title'),
+ 'subtitle' => gettext('Subtitle'),
+ 'description' => gettext('Description')
+ );
+
+ my %do = (
+ 'timer' => gettext('Timer'),
+ 'recording' => gettext('Exist recording'),
+ 'chronicle' => gettext('Recording chronicle')
+ );
+ my $DoneChoices = [$do{'timer'}, $do{'recording'}];
+
+ # Enable option "chronicle" only if activated.
+ my $cm = main::getModule('CHRONICLE');
+ push(@$DoneChoices, $do{'chronicle'})
+ if($cm and $cm->{active} eq 'y');
+
+ my $questions = [
+ 'Id' => {
+ typ => 'hidden',
+ def => $epg->{Id} || 0,
+ },
+ 'Activ' => {
+ typ => 'confirm',
+ def => $epg->{Activ} || 'y',
+ msg => gettext('Switch this autotimer on?'),
+ },
+ 'Search' => {
+ req => gettext('This is required!'),
+ msg => gettext(
+"Search terms to look for EPG entries.\nYou can also improve your searches :\n* by adding 'operators' to your search terms like 'AND', 'OR', 'AND NOT' e.g. 'today AND NOT tomorrow'\n* by comma seperated multiply search terms e.g. 'today,tomorrow'\n* by minus sign to exclude search terms e.g. 'today,-tomorrow'"
+),
+ def => $epg->{Search} || '',
+ },
+ 'InFields' => {
+ msg => gettext('Search in this EPG fields'),
+ typ => 'checkbox',
+ choices => [$in{'title'}, $in{'subtitle'}, $in{'description'}],
+ req => gettext('This is required!'),
+ def => sub {
+ my $value = $epg->{InFields} || ['title','subtitle'];
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+ my @ret;
+ foreach my $v (@vals) {
+ push(@ret,$in{$v});
+ }
+ return @ret;
+ },
+ check => sub{
+ my $value = shift || return;
+ my $data = shift || return error('No Data in CB');
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+ my @ret;
+ foreach my $v (@vals) {
+ unless(grep($_ eq $v, @{$data->{choices}})) {
+ my $ch = join(' ', @{$data->{choices}});
+ return undef, sprintf(gettext("You can choose: %s!"),$ch);
+ }
+ foreach my $k (keys %in) {
+ push(@ret,$k)
+ if($v eq $in{$k});
+ }
+ }
+ return join(',', @ret);
+ },
+ },
+ 'Channels' => {
+ typ => 'list',
+ def => $epg->{Channels},
+ choices => $mod->ChannelArray('Name', sprintf(' NOT (%s)', $obj->{exclude})),
+ options => 'multi',
+ msg => gettext('Restrict search to this channels'),
+ check => sub{
+ my $value = shift || return;
+ my @vals;
+ foreach my $chname ((ref $value eq 'ARRAY' ? @$value : split(/\s*,\s*/, $value))) {
+ if( my $chid = $mod->PosToChannel($chname) || $mod->NameToChannel($chname)) {
+ push(@vals, $chid);
+ } else {
+ return undef, sprintf(gettext("The channel '%s' does not exist!"),$chname);
+ }
+ }
+ return join(',', @vals);
+ },
+ },
+ 'Done' => {
+ msg => gettext('Ignore retries with same title?'),
+ typ => 'checkbox',
+ choices => $DoneChoices,
+ def => sub {
+ my $value = $epg->{Done};
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+ my @ret;
+ foreach my $v (@vals) {
+ push(@ret,$do{$v});
+ }
+ return @ret;
+ },
+ check => sub{
+ my $value = shift || '';
+ my $data = shift || return error('No Data in CB');
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+ my @ret;
+ foreach my $v (@vals) {
+ unless(grep($_ eq $v, @{$data->{choices}})) {
+ my $ch = join(' ', @{$data->{choices}});
+ return undef, sprintf(gettext("You can choose: %s!"),$ch);
+ }
+ foreach my $k (keys %do) {
+ push(@ret,$k)
+ if($v eq $do{$k});
+ }
+ }
+ return join(',', @ret);
+ },
+ },
+ 'Start' => {
+ typ => 'string',
+ def => sub{
+ my $value = $epg->{Start} || return "";
+ return fmttime($value);
+ },
+ msg => gettext("Starttime in format 'HH:MM'"),
+ check => sub{
+ my $value = shift || 0;
+ return undef, gettext('You set a start-time without a stop-time!')
+ if(not $data->{Stop} and $value);
+ return "" if(not $value);
+ $value = fmttime($value) if($value =~ /^\d+$/sig);
+ return undef, gettext('No right time!') if($value !~ /^\d+:\d+$/sig);
+ my @v = split(':', $value);
+ $value = sprintf('%02d%02d',$v[0],$v[1]);
+ if(int($value) < 2400 and int($value) >= 0) {
+ return sprintf('%04d',$value);
+ } else {
+ return undef, gettext('No right time!');
+ }
+ },
+ },
+ 'Stop' => {
+ typ => 'string',
+ def => sub{
+ my $value = $epg->{Stop} || return "";
+ return fmttime($value);
+ },
+ msg => gettext("Endtime in format 'HH:MM'"),
+ check => sub{
+ my $value = shift || 0;
+ return undef, gettext('You set a stop-time without a start-time!')
+ if(not $data->{Start} and $value);
+ return "" if(not $value);
+ $value = fmttime($value) if($value =~ /^\d+$/sig);
+ return undef, gettext('No right time!') if($value !~ /^\d+:\d+$/sig);
+ my @v = split(':', $value);
+ $value = sprintf('%02d%02d',$v[0],$v[1]);
+ if(int($value) < 2400 and int($value) >= 0) {
+ return sprintf('%04d',$value);
+ } else {
+ return undef, gettext('No right time!');
+ }
+ },
+ },
+ 'Weekdays' => {
+ msg => gettext('Search only on this weekdays'),
+ typ => 'checkbox',
+ choices => [$wd{'Mon'}, $wd{'Tue'}, $wd{'Wed'}, $wd{'Thu'}, $wd{'Fri'}, $wd{'Sat'}, $wd{'Sun'}],
+ def => sub {
+ my $value = $epg->{Weekdays} || ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+ my @ret;
+ foreach my $v (@vals) {
+ push(@ret,$wd{$v});
+ }
+ return @ret;
+ },
+ check => sub{
+ my $value = shift || [$wd{'Mon'}, $wd{'Tue'}, $wd{'Wed'}, $wd{'Thu'}, $wd{'Fri'}, $wd{'Sat'}, $wd{'Sun'}];
+ my $data = shift || return error('No Data in CB');
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+ my @ret;
+ foreach my $v (@vals) {
+ unless(grep($_ eq $v, @{$data->{choices}})) {
+ my $ch = join(' ', @{$data->{choices}});
+ return undef, sprintf(gettext("You can choose: %s!"),$ch);
+ }
+ foreach my $k (keys %wd) {
+ push(@ret,$k)
+ if($v eq $wd{$k});
+ }
+ }
+ return join(',', @ret);
+ },
+ },
+ 'VPS' => {
+ typ => 'confirm',
+ def => $epg->{VPS} || 'n',
+ msg => gettext('Activate VPS for new timer?'),
+ },
+ 'prevminutes' => {
+ typ => 'integer',
+ msg => gettext('Buffer time in minutes before the scheduled end of the recorded program.'),
+ def => $epg->{prevminutes},
+ check => sub{
+ my $value = shift;
+ return if($value eq "");
+ if($value =~ /^\d+$/sig and $value >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'afterminutes' => {
+ typ => 'integer',
+ msg => gettext('Buffer time in minutes after the scheduled end of the recorded program.'),
+ def => $epg->{afterminutes},
+ check => sub{
+ my $value = shift;
+ return if($value eq "");
+ if($value =~ /^\d+$/sig and $value >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'MinLength' => {
+ typ => 'integer',
+ msg => gettext('Minimum length in minutes'),
+ def => $epg->{MinLength} || 0,
+ check => sub{
+ my $value = shift || return;
+ if($value =~ /^\d+$/sig and $value > 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'Priority' => {
+ typ => 'integer',
+ msg => sprintf(gettext('Priority (0 .. %d)'),$console->{USER}->{MaxPriority} ? $console->{USER}->{MaxPriority} : 99 ),
+ def => (defined $epg->{Priority} ? $epg->{Priority} : $modT->{Priority}),
+ check => sub{
+ my $value = shift || 0;
+ if($value =~ /^\d+$/sig and $value >= 0 and $value < 100) {
+ if($console->{USER}->{MaxPriority} and $value > $console->{USER}->{MaxPriority}) {
+ return undef, sprintf(gettext('Sorry, but maximum priority is limited on %d!'), $console->{USER}->{MaxPriority});
+ }
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'Lifetime' => {
+ typ => 'integer',
+ msg => sprintf(gettext('Lifetime (0 .. %d)'),$console->{USER}->{MaxLifeTime} ? $console->{USER}->{MaxLifeTime} : 99 ),
+ def => (defined $epg->{Lifetime} ? $epg->{Lifetime} : $modT->{Lifetime}),
+ check => sub{
+ my $value = shift || 0;
+ if($value =~ /^\d+$/sig and $value >= 0 and $value < 100) {
+ if($console->{USER}->{MaxLifeTime} and $value > $console->{USER}->{MaxLifeTime}) {
+ return undef, sprintf(gettext('Sorry, but maximum lifetime is limited on %d!'), $console->{USER}->{MaxLifeTime});
+ }
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'Dir' => {
+ typ => 'string',
+ msg => gettext('Group all recordings on a directory'),
+ def => $epg->{Dir},
+ # choices => main::getModule('TIMERS')->getRootDirs,
+ },
+ ];
+
+ # Ask Questions
+ $data = $console->question(($timerid ? gettext('Edit autotimer')
+ : gettext('Create a new autotimer')), $questions, $data);
+
+ if(ref $data eq 'HASH') {
+ delete $data->{Channel};
+
+ # Last chance ;)
+ return $console->err(gettext('Nothing to search defined!'))
+ unless($data->{Search});
+
+ $obj->_insert($data);
+
+ $data->{Id} = $obj->{dbh}->selectrow_arrayref('SELECT max(ID) FROM AUTOTIMER')->[0]
+ if(not $data->{Id});
+
+ $console->message(gettext('Autotimer saved!'));
+ debug sprintf('%s autotimer with search "%s" is saved%s',
+ ($timerid ? 'New' : 'Changed'),
+ $data->{Search},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+ $obj->autotimer($watcher, $console, $data->{Id});
+
+ $console->link({
+ text => gettext("Back to referred side"),
+ url => $console->{browser}->{Referer},
+ }) if($console->typ eq 'HTML');
+
+ }
+ return 1;
+}
+
+# ------------------
+# Name: autotimerDelete
+# Descr: Routine to display the delete form for Autotimer.
+# Usage: $obj->autotimerDelete($watcher, $console, $atid);
+# ------------------
+sub autotimerDelete {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $timerid = shift || return $console->err(gettext("No ID for autotimer to delete! Please use adelete 'aid'")); # If timerid the edittimer
+
+ my @timers = reverse sort{ $a <=> $b } split(/[^0-9]/, $timerid);
+
+ my $sql = sprintf('DELETE FROM AUTOTIMER where Id in (%s)', join(',' => ('?') x @timers));
+ my $sth = $obj->{dbh}->prepare($sql);
+ if(!$sth->execute(@timers)) {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ $console->err(sprintf gettext("Autotimer with ID '%s' does not exist in the database!"), join(',', @timers));
+ return 0;
+ }
+
+ $console->message(sprintf gettext("Autotimer %s is deleted."), join(',', @timers));
+ debug sprintf('autotimer with id "%s" is deleted%s',
+ join(',', @timers),
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if($console->typ eq 'HTML');
+}
+
+# ------------------
+# Name: autotimerToogle
+# Descr: Switch the Autotimer on or off.
+# Usage: $obj->autotimerToogle($watcher, $console, $atid);
+# ------------------
+sub autotimerToggle {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $timerid = shift || return $console->err(gettext("No ID for autotimer to toggle! Please use atoggle 'aid'"));
+
+ my @timers = reverse sort{ $a <=> $b } split(/[^0-9]/, $timerid);
+
+ my $sql = sprintf('SELECT Id,Activ FROM AUTOTIMER where Id in (%s)', join(',' => ('?') x @timers));
+ my $sth = $obj->{dbh}->prepare($sql);
+ if(!$sth->execute(@timers)) {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ $console->err(sprintf(gettext("Autotimer with ID '%s' does not exist in the database!"),$timerid));
+ return 0;
+ }
+ my $data = $sth->fetchall_hashref('Id');
+
+ my $erg;
+ for my $timer (@timers) {
+
+ unless(exists $data->{$timer}) {
+ $console->err(sprintf(gettext("Autotimer with ID '%s' does not exist in the database!"), $timer));
+ next;
+ }
+
+ my $status = (($data->{$timer}->{Activ} eq 'n' ) ? 'y' : 'n');
+
+ my $sql = "UPDATE AUTOTIMER set Activ = ? where Id = ?";
+ my $sth = $obj->{dbh}->prepare($sql);
+ if(!$sth->execute($status,$timer)) {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ $console->err(sprintf(gettext("Can't toggle autotimer with ID '%s'!"),$timer));
+ next;
+ }
+
+ debug sprintf('autotimer with id "%s" is %s%s',
+ $timer,
+ ($status eq 'n' ? 'disabled' : 'activated'),
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ if($console->typ ne 'AJAX') {
+ my $text = ($status eq 'n') ? gettext('disabled')
+ : gettext('activated');
+ $console->message(sprintf gettext("Autotimer %s is %s."), $timer, $text);
+ }
+
+ # AJAX
+ push(@$erg,[$timer,($status eq 'n' ? 0 : 1),0,0]);
+ }
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+
+ if($console->typ eq 'AJAX') {
+ # { "data" : [ [ ID, ON, RUN, CONFLICT ], .... ] }
+ # { "data" : [ [ 5, 1, 0, 0 ], .... ] }
+ $console->table($erg);
+ }
+
+}
+
+# ------------------
+# Name: list
+# Descr: List Autotimers in a table display.
+# Usage: $obj->list($watcher, $console, [$atid], [$params]);
+# ------------------
+sub list {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $text = shift || '';
+ my $params = shift;
+
+ my $where = '';
+ if($text =~ /^.*?/) {
+ if($text =~ /^[0-9]+?/) {
+ $where = "WHERE Id = '$text'";
+ } elsif($text) {
+ $where = 'WHERE '.buildsearch("Search,Dir",$text);
+ }
+ }
+
+ my %f = (
+ 'Id' => umlaute(gettext('Service')),
+ 'Act' => umlaute(gettext('Act')),
+ 'Search' => umlaute(gettext('Search')),
+ 'Channels' => umlaute(gettext('Channels')),
+ 'Start' => umlaute(gettext('Start')),
+ 'Stop' => umlaute(gettext('Stop')),
+ 'Dir' => umlaute(gettext('Dir')),
+ 'Min' => umlaute(gettext('Min')),
+ );
+
+ my $sql = qq|
+ select
+ Id as $f{'Id'},
+ Activ as $f{'Act'},
+ Search as $f{'Search'},
+ Channels as $f{'Channels'},
+ Dir as $f{'Dir'},
+ Start as $f{'Start'},
+ Stop as $f{'Stop'},
+ MinLength as $f{'Min'}
+ FROM
+ AUTOTIMER
+ $where
+ |;
+
+ my $fields = fields($obj->{dbh}, $sql);
+
+ my $sortby = gettext("Search");
+ $sortby = $params->{sortby}
+ if(exists $params->{sortby} && grep(/^$params->{sortby}$/i,@{$fields}));
+ $sql .= " order by $sortby";
+ if(exists $params->{desc} && $params->{desc} == 1) {
+ $sql .= " desc"; }
+ else {
+ $sql .= " asc"; }
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+
+ my $channels = main::getModule('CHANNELS')->ChannelHash('Id');
+ my $timers = main::getModule('TIMERS')->getTimersByAutotimer();
+
+ $console->table($erg,
+ {
+ sortable => 1,
+ channels => $channels,
+ timers => $timers,
+ }
+ );
+}
+
+
+# ------------------
+sub _eventsearch {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $a = shift || return error ('No Data from Autotimer!' );
+ my $timermod = shift || main::getModule('TIMERS') || return error ("Can't access modul TIMERS!");
+
+ # Searchstrings to Paragraphs Changed
+ $a->{Search} =~ s/\:/\:\.\*/
+ if($a->{InFields} =~ /description/);
+
+ my $search = buildsearch($a->{InFields}, $a->{Search});
+
+ # Start and Stop
+ if($a->{Start} and $a->{Stop}) {
+ if($a->{Start} > $a->{Stop}) {
+ $search .= "\n AND ((DATE_FORMAT(e.starttime, '%H%i') > $a->{Start} AND DATE_FORMAT(e.starttime, '%H%i') < 2359) OR (DATE_FORMAT(e.starttime, '%H%i') >= 0 and DATE_FORMAT(e.starttime, '%H%i') < $a->{Stop}))";
+ } else {
+ $search .= "\n AND (DATE_FORMAT(e.starttime, '%H%i') > $a->{Start} AND DATE_FORMAT(e.starttime, '%H%i') < $a->{Stop})";
+ }
+ }
+
+ # Min Length
+ if(exists $a->{MinLength} and $a->{MinLength}) {
+ $search .= sprintf(" AND e.duration >= %d ", $a->{MinLength} * 60);
+ }
+
+ # Channels
+ if($a->{Channels} and my @channelids = split(',', $a->{Channels})) {
+ @channelids = map {$_ = "'$_'"} @channelids;
+ $search = sprintf(' %s AND channel_id in (%s)', $search, join(',', @channelids));
+ }
+
+ # Weekdays
+ if($a->{Weekdays} and my @weekdays = split(',', $a->{Weekdays})) {
+ if(scalar @weekdays != 7 and scalar @weekdays != 0) {
+ @weekdays = map {$_ = "'$_'"} @weekdays;
+ $search = sprintf(' %s AND DATE_FORMAT(e.starttime, \'%%a\') in (%s)', $search, join(',', @weekdays));
+ }
+ }
+
+ # Exclude channels, ifn't already lookup for channels
+ if($obj->{exclude} && not $a->{Channels}) {
+ $search = sprintf(' %s AND NOT (c.%s)', $search, $obj->{exclude});
+ }
+
+ # Custom time range
+ my $after = 0;
+ my $prev = 0;
+# if($a->{VPS} ne 'y') {
+ if(defined $a->{prevminutes}) {
+ $prev = $a->{prevminutes} * 60;
+ } else {
+ $prev = $timermod->{prevminutes} * 60;
+ }
+ if(defined $a->{afterminutes}) {
+ $after = $a->{afterminutes} * 60;
+ } else {
+ $after = $timermod->{afterminutes} * 60;
+ }
+# }
+
+ # Search for events
+ my $sql = qq|
+SELECT
+ e.eventid as eventid,
+ e.channel_id as ChannelID,
+ c.Name as Channel,
+ c.POS as POS,
+ e.title as Title,
+ e.subtitle as Subtitle,
+ e.description as Summary,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) - $prev ), '%d') as Day,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) - $prev ), '%H%i') as Start,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) + e.duration + $after ), '%H%i') as Stop,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.vpstime)), '%H%i') as VpsStart,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.vpstime) + e.duration), '%H%i') as VpsStop
+FROM
+ EPG as e,
+ CHANNELS as c
+WHERE
+ ( $search )
+ AND ( e.channel_id = c.Id )|;
+
+#dumper $sql;
+ my $data = $obj->{dbh}->selectall_hashref($sql, 'eventid');
+
+ return $data;
+}
+
+# ------------------
+sub _timerexists {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eventdata = shift || return error ('No Data from Autotimer!' );
+ my ($nexttime, $aidcomment) = @_;
+
+ # Avoid Timer already defined (the timer with the same data again do not put on)
+ my $sql = "select count(*) as cc from TIMERS where
+ ChannelID = ?
+ and UNIX_TIMESTAMP(NextStartTime) = ?
+ and UNIX_TIMESTAMP(NextStopTime) = ?
+ and Priority = ?
+ and Lifetime = ?
+ and (
+ ( Status & 1 = '0' )
+ or ( File = ? and Summary = ? )
+ or ( Summary not like ? )
+ )";
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eventdata->{ChannelID},$nexttime->{start},$nexttime->{stop},
+ $eventdata->{Priority},$eventdata->{Lifetime},
+ $eventdata->{File},$eventdata->{Summary},"%".$aidcomment)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg->{cc}
+ if($erg);
+ return 0;
+
+}
+
+# ------------------
+sub _timerexistsfuzzy {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eventdata = shift || return error ('No Data from Autotimer!' );
+ my ($nexttime, $aidcomment) = @_;
+
+ # Adjust timers set by the autotimer
+ my $timerID = 0;
+ my $sql = "select ID from TIMERS where
+ ChannelID = ?
+ and UNIX_TIMESTAMP(NextStartTime) = ?
+ and UNIX_TIMESTAMP(NextStopTime) = ?
+ and Summary like ?
+ order by length(Summary) desc;";
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eventdata->{ChannelID},$nexttime->{start},$nexttime->{stop},
+ "%".$aidcomment)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg->{ID}
+ if($erg);
+ return 0;
+}
+
+# ------------------
+sub _recordexists {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eventdata = shift || return error ('No Data from Autotimer!' );
+ my ($nexttime, $aidcomment) = @_;
+
+ # Ignore timer if it already with same title recorded
+ my $sql = "SELECT count(*) as cc
+ FROM RECORDS as r, OLDEPG as e
+ WHERE e.eventid = r.EventId
+ AND CONCAT_WS('~',e.title,IF(e.subtitle<>'',e.subtitle,NULL)) = ?";
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eventdata->{File})
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg->{cc}
+ if($erg);
+ return 0;
+}
+
+# ------------------
+sub _chronicleexists {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eventdata = shift || return error ('No Data from Autotimer!' );
+ my ($nexttime, $aidcomment) = @_;
+
+ my $chroniclemod = main::getModule('CHRONICLE') || return error ("Can't access modul CHRONICLE!");
+ return 0
+ if(not $chroniclemod or $chroniclemod->{active} ne 'y');
+
+ my $sql = "select count(*) as cc from CHRONICLE where title = ?";
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eventdata->{File})
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg->{cc}
+ if($erg);
+ return 0;
+}
+
+# ------------------
+sub _timerexiststitle {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eventdata = shift || return error ('No Data from Autotimer!' );
+ my ($nexttime, $aidcomment) = @_;
+
+ my $sql = "select count(*) as cc from TIMERS where File = ?";
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eventdata->{File})
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg->{cc}
+ if($erg);
+ return 0;
+}
+
+
+# ------------------
+sub _insert {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return;
+
+ if(ref $data eq 'HASH') {
+ my ($names, $vals, $kenn);
+ map {
+ push(@$names, $_);
+ push(@$vals, $data->{$_}),
+ push(@$kenn, '?'),
+ } sort keys %$data;
+
+ my $sql = sprintf("REPLACE INTO AUTOTIMER (%s) VALUES (%s)",
+ join(', ', @$names),
+ join(', ', @$kenn),
+ );
+ my $sth = $obj->{dbh}->prepare( $sql );
+ $sth->execute( @$vals );
+ } else {
+ my $sth = $obj->{dbh}->prepare('REPLACE INTO AUTOTIMER VALUES (?,?,?,?,?,?,?,?,?)');
+ $sth->execute( @$data );
+ }
+}
+
+# ------------------
+# Name: _placeholder
+# Descr: Replace the placeholder with extendet EPG
+# Usage: my $text = $obj->_placeholder($epgdata, $autotimerdata);
+# ------------------
+sub _placeholder {
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $at = shift || return error ('No AtData!' );
+
+ my $file;
+
+ if ($at->{Dir}) {
+ my $title = $at->{Dir};
+ if($title =~ /.*%.*%.*/sig) {
+ my %at_details;
+ $at_details{'title'} = $data->{Title};
+ $at_details{'subtitle'} = $data->{Subtitle} ? $data->{Subtitle} : $data->{Start};
+ $at_details{'date'} = $data->{Day};
+ $at_details{'regie'} = $1 if $data->{Summary} =~ m/\|Director: (.*?)\|/;
+ $at_details{'category'} = $1 if $data->{Summary} =~ m/\|Category: (.*?)\|/;
+ $at_details{'genre'} = $1 if $data->{Summary} =~ m/\|Genre: (.*?)\|/;
+ $at_details{'year'} = $1 if $data->{Summary} =~ m/\|Year: (.*?)\|/;
+ $at_details{'country'} = $1 if $data->{Summary} =~ m/\|Country: (.*?)\|/;
+ $at_details{'originaltitle'} = $1 if $data->{Summary} =~ m/\|Originaltitle: (.*?)\|/;
+ $at_details{'fsk'} = $1 if $data->{Summary} =~ m/\|FSK: (.*?)\|/;
+ $at_details{'episode'} = $1 if $data->{Summary} =~ m/\|Episode: (.*?)\|/;
+ $at_details{'rating'} = $1 if $data->{Summary} =~ m/\|Rating: (.*?)\|/;
+ $title =~ s/%([\w_-]+)%/$at_details{lc($1)}/sieg;
+ $file = $title;
+ } else { # Classic mode DIR~TITLE~SUBTILE
+ $file = sprintf('%s~%s~%s', $at->{Dir}, $data->{Title},$data->{Subtitle});
+ }
+ } elsif($data->{Subtitle}) {
+ $file = sprintf('%s~%s', $data->{Title},$data->{Subtitle});
+ } else {
+ $file = $data->{Title};
+ }
+
+ # sind irgendweche Tags verwendet worden, die leer waren und die doppelte Verzeichnisse erzeugten?
+ $file =~s#~+#~#g;
+ $file =~s#^~##g;
+ $file =~s#~$##g;
+
+ return $file;
+}
+
+# ------------------
+sub suggest { # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+ my $params = shift;
+
+ if($search) {
+ my $sql = qq|
+ SELECT
+ Search
+ FROM
+ AUTOTIMER
+ WHERE
+ ( Search LIKE ? )
+ GROUP BY
+ Search
+ ORDER BY
+ Search
+ LIMIT 25
+ |;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute('%'.$search.'%')
+ or return error "Can't execute query: $sth->errstr.";
+ my $result = $sth->fetchall_arrayref();
+ $console->table($result)
+ if(ref $console && $result);
+ }
+}
+
+1;
diff --git a/lib/XXV/MODULES/CHANNELS.pm b/lib/XXV/MODULES/CHANNELS.pm
new file mode 100644
index 0000000..28d7620
--- /dev/null
+++ b/lib/XXV/MODULES/CHANNELS.pm
@@ -0,0 +1,1018 @@
+package XXV::MODULES::CHANNELS;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+use File::stat;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'CHANNELS',
+ Prereq => {
+ },
+ Description => gettext('This module reads new channels and stores them in the database.'),
+ Version => '0.91',
+ Date => '2007-01-11',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ file => {
+ description => gettext('Location of channels.conf on your system.'),
+ default => '/var/lib/vdr/channels.conf',
+ type => 'file',
+ required => gettext('This is required!'),
+ },
+ interval => {
+ description => gettext('How often channels are to be updated (in seconds)'),
+ default => 3 * 60 * 60,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ empty => {
+ description => gettext('Include channels with empty PID'),
+ default => 'n',
+ type => 'confirm',
+ },
+ filterCA => {
+ description => gettext('Filter channels, set all wanted CA(Common Access)'),
+ # 0 for FTA, 1-4 for DVB Device, 32001 for AnalogPlugin
+ type => 'list',
+ options => 'multi',
+ default => '',
+ choices => sub{
+ my @knownCA;
+ foreach my $CA (@{$obj->{knownCA}}) {
+ my $desc;
+ if($CA eq '0') { $desc = gettext("Free to air"); }
+ elsif($CA eq '1'
+ or $CA eq '2'
+ or $CA eq '3'
+ or $CA eq '4') { $desc = sprintf(gettext("DVB card %s"),$CA);}
+ else { $desc = sprintf("CA '%s'",$CA); }
+ push(@knownCA,[$desc,$CA]);
+ }
+ return @knownCA;
+ },
+ check => sub{
+ my $value = shift;
+ if(ref $value eq 'ARRAY') {
+ return join(',', @$value);
+ } else {
+ return $value;
+ }
+ },
+ },
+ stripCH => {
+ description => gettext("Clean channel names, only the 'long' part is visible."),
+ # Format in vdr 1.2.6 (Format "" or "long"). it show also all parts
+ # Format in vdr 1.3.10 (Format "short,long")
+ # Format in vdr 1.3.12 (Format "short,long;provider")
+ # Format in vdr 1.3.?? (Format "provider;short,long")
+ # Format in vdr 1.3.18 (Format "short,long;provider")
+ default => 'short,long;provider',
+ type => 'string',
+ },
+ },
+ Commands => {
+ cupdate => {
+ description => gettext('Read channels and write them into database'),
+ short => 'cu',
+ callback => sub{ $obj->readData(@_) },
+ DenyClass => 'cedit',
+ Level => 'user',
+ },
+ clist => {
+ description => gettext("List Channels from database 'cname'"),
+ short => 'cl',
+ callback => sub{ $obj->list(@_) },
+ Level => 'user',
+ },
+ cnew => {
+ description => gettext("Create a new channel"),
+ short => 'cne',
+ callback => sub{ $obj->newChannel(@_) },
+ Level => 'user',
+ DenyClass => 'cedit',
+ },
+ cedit => {
+ description => gettext("Edit a channel 'cid'"),
+ short => 'ced',
+ callback => sub{ $obj->editChannel(@_) },
+ Level => 'user',
+ DenyClass => 'cedit',
+ },
+ cdelete => {
+ description => gettext("Delete one or more channels 'pos'"),
+ short => 'cdl',
+ callback => sub{ $obj->deleteChannel(@_) },
+ Level => 'user',
+ DenyClass => 'cedit',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $lastReportTime = shift || 0;
+
+ my $sql = "select count(*) from CHANNELS";
+ my $gesamt = $obj->{dbh}->selectrow_arrayref($sql)->[0];
+
+ $sql = "select count(*) from CHANNELGROUPS";
+ my $groups = $obj->{dbh}->selectrow_arrayref($sql)->[0];
+
+ return {
+ message => sprintf(gettext('The system has %d saved Channels in %d Groups'), $gesamt, $groups),
+ };
+
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ $self->{knownCA} = [0,1,2,3,4];
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ # Interval to read channels and put to DB
+ Event->timer(
+ interval => $self->{interval},
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ lg 'Start the interval reading channels to DB!';
+ $self->readData();
+ },
+ );
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0, panic("Session to database is'nt connected")
+ unless($obj->{dbh});
+
+ # remove old table, if updated rows
+ tableUpdated($obj->{dbh},'CHANNELS',16,1);
+ tableUpdated($obj->{dbh},'CHANNELGROUPS',3,1);
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS CHANNELS (
+ Id varchar(100) NOT NULL,
+ Name varchar(100) NOT NULL default '',
+ Frequency int(11) NOT NULL default '0',
+ Parameters varchar(100) default '',
+ Source varchar(100),
+ Srate int(11) default 0,
+ VPID varchar(100) default '',
+ APID varchar(100) default '',
+ TPID varchar(100) default '',
+ CA varchar(100) default '',
+ SID int(11) default 0,
+ NID int(11) default 0,
+ TID int(11) default 0,
+ RID int(11) default 0,
+ GRP int(11) default 0,
+ POS int(11) NOT NULL,
+ PRIMARY KEY (Id)
+ ) COMMENT = '$version'
+ |);
+
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS CHANNELGROUPS (
+ Id int(11) auto_increment not NULL,
+ Name varchar(100) default 'unknown',
+ Counter int(11) default '0',
+ PRIMARY KEY (Id)
+ ) COMMENT = '$version'
+ |);
+
+ main::after(sub{
+ $obj->{svdrp} = main::getModule('SVDRP');
+ unless($obj->{svdrp}) {
+ panic ("Can't get modul SVDRP");
+ return 0;
+ }
+
+ my $erg = $obj->readData();
+ return 1;
+ }, "CHANNELS: Read and register Channels ...", 5);
+ return 1;
+}
+
+# ------------------
+sub insert {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return;
+ my $pos = shift || return;
+ my $grp = shift || 0;
+
+ foreach my $CA (split(',', $data->[8])) {
+ push(@{$obj->{knownCA}},$CA);
+ }
+
+ return if($obj->{empty} eq 'n' and not (($data->[6] ne "0" || $data->[7] ne "0"))); # Ignore Channels with APID = 0/TPID = 0 from PID Scan
+ if($obj->{filterCA} ne "") {
+ my $filter = $obj->{filterCA};
+ $filter =~ s/\,/|/g; # Transform 0,2,400 => 0|2|400
+ return 0 if(not ($data->[8] =~ /(^|\,)($filter)(\,|$)/s)); # check (^|,)(0|2|400)(,|$)
+ }
+
+ # Strip short and providername from channelname e.g ch
+ if($obj->{stripCH}) {
+ my $ch = $data->[0];
+ my $filter = $obj->{stripCH};
+ my @p = split(';',$filter);
+ if(scalar @p == 2) {
+ if(($p[0] =~ /provider/i)) { # format "provider;name"
+ $ch = (split(';', $ch))[-1] if($ch =~ /;/);
+ $filter = $p[1];
+ }
+ elsif(($p[1] =~ /provider/i)) { # format "name;provider"
+ $ch = (split(';', $ch))[0] if($ch =~ /;/);
+ $filter = $p[0];
+ }
+ }
+ my @c = split(',',$filter);
+ if(scalar @c == 2) {
+ if(($c[0] =~ /long/i)) { # format "long,short"
+ $ch = (split(',', $ch))[0] if($ch =~ /,/);
+ }
+ elsif(($c[1] =~ /long/i)) { # format "short,long"
+ $ch = (split(',', $ch))[-1] if($ch =~ /,/);
+ }
+ }
+ $data->[0] = $ch if($ch);
+ }
+
+ # ID
+ if ( $data->[3] eq 'C' or $data->[3] eq 'T') {
+ while(length($data->[1]) > 3) {
+ $data->[1] = substr($data->[1], 0, length($data->[1])-3);
+ }
+ }
+
+ my $id;
+ $data->[12] = (split(':', $data->[12]))[0];
+# if($data->[12] && $data->[12] > 0 && $data->[12] < 100) {
+ # By DVB-C gabs Probleme weil die Zahl grösser 100 war
+ # Siehe auch http://www.vdr-portal.de/board/thread.php?sid=&postid=364373
+ if($data->[12] && $data->[12] > 0) {
+ $id = sprintf('%s-%u-%u-%u-%u', $data->[3], $data->[10], ($data->[10] || $data->[11]) ? $data->[11] : $data->[1], $data->[9],$data->[12]);
+ } else {
+ $id = sprintf('%s-%u-%u-%u', $data->[3], $data->[10], ($data->[10] || $data->[11]) ? $data->[11] : $data->[1], $data->[9]);
+ }
+ unshift(@$data, $id);
+
+ # ChannelGroup
+ push(@$data, $grp);
+
+ # POS
+ push(@$data, $pos);
+
+ my $sth = $obj->{dbh}->prepare('REPLACE INTO CHANNELS VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
+ $sth->execute( @$data );
+ lg sprintf('Add new Channel "%s" with Id "%s" in ChannelsDB!', $data->[1], $id);
+ return 1;
+}
+
+# ------------------
+sub insertGrp {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $pos = shift || return;
+ my $name = shift || 0;
+
+ lg sprintf('Add new ChannelGroup "%s" in ChannelsGroup!', $name);
+ my $sth = $obj->{dbh}->prepare('INSERT INTO CHANNELGROUPS SET Name=?, Counter=?');
+ $sth->execute($name, $pos)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ return $sth->{mysql_insertid};
+}
+
+# ------------------
+sub readData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $file = $obj->{file} || return 1, error ('No Channels File');
+
+ return 1, panic ('No Channels File found') if( ! -e $file);
+
+ # only if file modification from last read time
+ my $mtime = (stat($file)->mtime);
+ return
+ if(! ref $console and defined $obj->{LastRefreshTime} and ($mtime > $obj->{LastRefreshTime}));
+
+ $obj->{dbh}->do('DELETE FROM CHANNELS');
+ $obj->{dbh}->do('DELETE FROM CHANNELGROUPS');
+
+ my $fh = IO::File->new("< $file") or return error("Can't open File $file $! ");
+ my $c = 0;
+ my $nPos = 1;
+ my $grp = 0;
+ while ( defined (my $line = <$fh>) ) {
+ $line =~ s/[\r|\n]//sig;
+ next if($line eq "");
+ my @data = split(':', $line, 13);
+ $data[-1] = (split(':', $data[-1]))[0];
+
+ if( $line =~ /^\:\@(\d*)\s*(.*)/ and $nPos <= $1) {
+ $nPos = $1;
+ my $grpText = $2;
+ $grp = $obj->insertGrp($nPos, $grpText);
+ } elsif( $line =~ /^\:(.+)/) {
+ my $grpText = $1;
+ $grp = $obj->insertGrp($nPos, $grpText);
+ } else {
+ $grp = $obj->insertGrp(1, gettext("Channels"))
+ if(!$grp);
+ $c++
+ if(scalar @data > 4 && $obj->insert(\@data, $nPos++, $grp));
+ }
+ }
+ $fh->close;
+
+ # Cool we have new Channels!
+ my $LastChannel = $obj->_LastChannel;
+ if($obj->{LastChannel}->{POS} and $LastChannel->{POS} > $obj->{LastChannel}->{POS}) {
+ $obj->_brandNewChannels($obj->{LastChannel}->{POS});
+ }
+
+ # Remember the maximum Channelposition
+ $obj->{LastChannel} = $obj->_LastChannel;
+
+ $console->message(sprintf(gettext("Write %d channels into database."), $c))
+ if(ref $console);
+
+ # sort list with CA numerical
+ my %CA;
+ @CA{@{$obj->{knownCA}}} = ();
+ @{$obj->{knownCA}} = sort { if(is_numeric($a) && is_numeric($b)) {
+ $a <=> $b
+ } else {
+ $a cmp $b } } keys %CA;
+
+ $obj->{LastRefreshTime} = $mtime;
+ return 1;
+}
+# ------------------
+sub getnum {
+# ------------------
+ use POSIX qw(strtod);
+ my $str = shift;
+ $str =~ s/^\s+//;
+ $str =~ s/\s+$//;
+ $! = 0;
+ my($num, $unparsed) = strtod($str);
+ if (($str eq '') || ($unparsed != 0) || $!) {
+ return undef;
+ } else {
+ return $num;
+ }
+}
+# ------------------
+sub is_numeric { defined getnum($_[0]) }
+# ------------------
+
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || '';
+ my $params = shift;
+
+ my $sql = qq|
+select
+ c.*, cg.Name as __GrpName
+from
+ CHANNELS as c,
+ CHANNELGROUPS as cg
+where
+ c.Name like ?
+ and
+ c.GRP = cg.Id
+|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+
+ my $sortby = "POS";
+ $sortby = $params->{sortby}
+ if(exists $params->{sortby} && grep(/^$params->{sortby}$/i,@{$fields}));
+ $sql .= "order by $sortby";
+ $sql .= " desc"
+ if(exists $params->{desc} && $params->{desc} == 1);
+
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute('%'.$id.'%')
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+
+ my $erg = $sth->fetchall_arrayref();
+ unshift(@$erg, $fields);
+ $console->table($erg,{sortable => 1 });
+}
+
+
+# ------------------
+sub NameToChannel {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('select Id from CHANNELS where UPPER(Name) = UPPER( ? )');
+ $sth->execute($name)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg ? $erg->{Id} : undef;
+}
+
+# ------------------
+sub PosToName {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $pos = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('select Name from CHANNELS where POS = ?');
+ $sth->execute($pos)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg ? $erg->{Name} : undef;
+}
+
+# ------------------
+sub PosToChannel {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $pos = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('select Id from CHANNELS where POS = ?');
+ $sth->execute($pos)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg ? $erg->{Id} : undef;
+}
+
+# ------------------
+sub ChannelGroupsArray {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $field = shift || return undef;
+ my $where = shift || '';
+ $where = sprintf('WHERE %s', $where) if($where);
+
+ my $sql = sprintf('select %s, Id from CHANNELGROUPS %s order by Id', $field, $where);
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ return $erg;
+}
+
+# ------------------
+sub ChannelArray {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $field = shift || return undef;
+ my $where = shift || '';
+ $where = sprintf('WHERE %s', $where) if($where);
+
+ my $sql = sprintf('select %s, POS from CHANNELS %s order by POS', $field, $where);
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ return $erg;
+}
+
+# ------------------
+sub ChannelIDArray {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $field = shift || return undef;
+ my $where = shift || '';
+ $where = sprintf('WHERE %s', $where) if($where);
+
+ my $sql = sprintf('select %s, Id from CHANNELS %s order by POS', $field, $where);
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ return $erg;
+}
+
+# ------------------
+sub ChannelHash {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $field = shift || return undef;
+ my $where = shift || '';
+ $where = sprintf('WHERE %s', $where) if($where);
+
+ my $sql = sprintf('select * from CHANNELS %s', $where);
+ my $erg = $obj->{dbh}->selectall_hashref($sql, $field);
+ return $erg;
+}
+
+# ------------------
+sub ChannelToName {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('select Name from CHANNELS where Id = ?');
+ $sth->execute($id)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg ? $erg->{Name} : undef;
+}
+
+# ------------------
+sub ChannelToPos {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('select POS from CHANNELS where Id = ?');
+ $sth->execute($id)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+ return $erg ? $erg->{POS} : undef;
+}
+
+
+# ------------------
+sub getChannelType {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return undef;
+ my $pos = $obj->ChannelToPos($id);
+ if($pos and $pos >= 1)
+ {
+ my $data = $obj->ChannelHash('POS', sprintf('POS = %d', $pos));
+ if(exists $data->{$pos}) {
+ my $ch = $data->{$pos};
+ if($ch->{VPID}) {
+ return 'TV';
+ } elsif($ch->{APID}) {
+ return 'RADIO';
+ }
+ }
+ }
+ error("Unknown channel! Can't identify type of channel with id: %s", $id);
+ return 'UNKNOWN';
+}
+
+# ------------------
+sub _LastChannel {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $sql = sprintf('select * from CHANNELS order by POS desc limit 1');
+ my $erg = $obj->{dbh}->selectrow_hashref($sql);
+ return $erg;
+}
+
+# ------------------
+sub newChannel {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $defaultData = shift || 0;
+
+ $self->editChannel($watcher, $console, 0, $defaultData);
+}
+
+# ------------------
+sub editChannel {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $cid = shift || 0; # If channelid then edit channel
+ my $data = shift || 0; # Data for defaults
+
+ my $defaultData;
+ if($cid and not ref $data) {
+
+ $cid = $self->PosToChannel($cid)
+ unless(index($cid, '-') > -1);
+
+ my $sth = $self->{dbh}->prepare('select POS, Name, Frequency, Parameters, Source, Srate, VPID, APID, TPID, CA, SID, NID, TID, RID from CHANNELS where Id = ?');
+ $sth->execute($cid)
+ or return $console->err(sprintf(gettext("Channel '%s' does not exist in the database!"),$cid));
+ $defaultData = $sth->fetchrow_hashref();
+ } elsif (ref $data eq 'HASH') {
+ $defaultData = $data;
+ }
+
+ my $questions = [
+ 'POS' => {
+ typ => 'hidden',
+ def => $defaultData->{POS} || 0,
+ } ];
+
+ my $newpos = [
+ 'NEWPOS' => {
+ typ => 'integer',
+ msg => gettext('Position'),
+ def => int($defaultData->{POS}),
+ check => sub{
+ my $value = shift;
+ if(int($value) > 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ } ];
+ #Change Position only on editing
+ push(@{$questions},@{$newpos})
+ if($cid && main::getVdrVersion >= 10332);
+
+ my $more = [
+ 'Name' => {
+ typ => 'string',
+ def => $defaultData->{Name} || gettext('New channel'),
+ msg => gettext("Name"),
+ check => sub{
+ my $value = shift || return;
+ if($value ne '') {
+ return $value;
+ } else {
+ return undef, gettext('This is required!');
+ }
+ },
+ },
+ 'Frequency' => {
+ typ => 'integer',
+ msg => gettext('Transponder frequency'),
+ def => int($defaultData->{Frequency}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) > 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'Source' => {
+ typ => 'string',
+ def => $defaultData->{Source} || "",
+ msg => gettext("Signal source"),
+ check => sub{
+ my $value = shift || return;
+ if($value ne '') {
+ return $value;
+ } else {
+ return undef, gettext('This is required!');
+ }
+ },
+ },
+ 'Parameters' => {
+ typ => 'string',
+ def => $defaultData->{Parameters} || "",
+ msg => gettext("Various parameters, depends on signal source"),
+ check => sub{
+ my $value = shift || return;
+ if($value ne '') {
+ return $value;
+ } else {
+ return undef, gettext('This is required!');
+ }
+ },
+ },
+ 'Srate' => {
+ typ => 'integer',
+ msg => gettext('Symbol rate'),
+ def => int($defaultData->{Srate}) || 27500,
+ check => sub{
+ my $value = shift;
+ if(int($value) > 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'VPID' => {
+ typ => 'integer',
+ msg => gettext('Video PID (VPID)'),
+ def => int($defaultData->{VPID}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'APID' => {
+ typ => 'string',
+ def => $defaultData->{APID} || 0,
+ msg => gettext("Audio PID (APID)"),
+ check => sub{
+ my $value = shift || return;
+ if($value ne '') {
+ return $value;
+ } else {
+ return undef, gettext('This is required!');
+ }
+ },
+ },
+ 'TPID' => {
+ typ => 'integer',
+ msg => gettext('Teletext PID (TPID)'),
+ def => int($defaultData->{TPID}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'CA' => {
+ typ => 'string',
+ def => $defaultData->{CA} || 0,
+ msg => gettext("Conditional access (CA)"),
+ check => sub{
+ my $value = shift || return;
+ if($value ne '') {
+ return $value;
+ } else {
+ return undef, gettext('This is required!');
+ }
+ },
+ },
+ 'SID' => {
+ typ => 'integer',
+ msg => gettext('Service ID (SID)'),
+ def => int($defaultData->{SID}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'NID' => {
+ typ => 'integer',
+ msg => gettext('Network ID (NID)'),
+ def => int($defaultData->{NID}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'TID' => {
+ typ => 'integer',
+ msg => gettext('Transport stream ID (TID)'),
+ def => int($defaultData->{TID}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'RID' => {
+ typ => 'integer',
+ msg => gettext('Radio ID (RID)'),
+ def => int($defaultData->{RID}) || 0,
+ check => sub{
+ my $value = shift;
+ if(int($value) >= 0) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ ];
+ push(@{$questions},@{$more});
+
+ # Ask Questions
+ my $datasave = $console->question(($cid ? gettext('Edit channel')
+ : gettext('New channel')), $questions, $data);
+
+ if(ref $datasave eq 'HASH') {
+ my $erg = $self->saveChannel($datasave, $datasave->{POS});
+
+ my $error;
+ foreach my $zeile (@$erg) {
+ if($zeile =~ /^(\d{3})\s+(.+)/) {
+ $error = $2 if(int($1) >= 500);
+ }
+ }
+ unless($error) {
+ debug sprintf('%s channel with name "%s" is saved%s',
+ ($cid ? 'Changed' : 'New'),
+ $data->{Name},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+ $console->message($erg);
+ } else {
+ error sprintf('%s channel with name "%s" does\'nt saved : %s',
+ ($cid ? 'Changed' : 'New'),
+ $data->{Name},
+ $error
+ );
+ $console->err($erg);
+ }
+ sleep(1);
+ $self->readData($watcher,$console);
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+ }
+}
+
+# ------------------
+sub saveChannel {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+ my $data = shift || return error('No Data to Save!');
+ my $pos = shift || 0;
+
+ my $erg;
+
+ if($pos
+ && defined $data->{NEWPOS}
+ && $pos != $data->{NEWPOS} ) {
+ $erg = $self->{svdrp}->command(
+ sprintf("movc %s %s",
+ $pos,
+ $data->{NEWPOS}
+ ));
+ $pos = $data->{NEWPOS};
+ push(@{$erg},"\r\n");
+ }
+
+ $erg = $self->{svdrp}->command(
+ sprintf("%s %s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s",
+ $pos ? "modc $pos" : "newc",
+ $data->{Name},
+ int($data->{Frequency}),
+ $data->{Parameters},
+ $data->{Source},
+ int($data->{Srate}),
+ int($data->{VPID}),
+ $data->{APID},
+ int($data->{TPID}),
+ $data->{CA} ? $data->{CA} : '0',
+ int($data->{SID}),
+ int($data->{NID}),
+ int($data->{TID}),
+ int($data->{RID})
+ )
+ );
+ return $erg;
+}
+
+# ------------------
+sub deleteChannel {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $channelid = shift || return $console->err(gettext("No channel to delete! Please use cdelete 'pos'"));
+ my $answer = shift || 0;
+
+ my @channels = reverse sort{ $a <=> $b } split(/[^0-9]/, $channelid);
+
+ my $sql = sprintf('select Id,POS,Name from CHANNELS where POS in (%s)', join(',' => ('?') x @channels));
+ my $sth = $self->{dbh}->prepare($sql);
+ $sth->execute(@channels)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $data = $sth->fetchall_hashref('POS');
+
+ foreach my $pos (@channels) {
+ unless(exists $data->{$pos}) {
+ $console->err(sprintf(gettext("Channel with number '%s' does not exist in database!"), $pos));
+ next;
+ }
+
+ if(ref $console and $console->{TYP} ne 'HTML') {
+ $console->table($data->{$pos});
+ my $confirm = $console->confirm({
+ typ => 'confirm',
+ def => 'y',
+ msg => gettext('Are you sure to delete this channel?'),
+ }, $answer);
+ next if(! $answer eq 'y');
+ }
+
+ debug sprintf('Channel with name "%s" is deleted%s',
+ $data->{$pos}->{Name},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $self->{svdrp}->queue_cmds("delc $pos"); # Sammeln der Kommandos
+ }
+
+ if($self->{svdrp}->queue_cmds('COUNT')) {
+ my $erg = $self->{svdrp}->queue_cmds("CALL"); # Aufrufen der Kommandos
+ $console->msg($erg, $self->{svdrp}->err)
+ if(ref $console);
+
+ sleep(1);
+
+ $self->readData($watcher,$console);
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+ } else {
+ $console->err(gettext("No channel to delete!"));
+ }
+
+ return 1;
+}
+
+# ------------------
+sub _brandNewChannels {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $oldmaximumpos = shift || return;
+
+ my $sql = 'select * from CHANNELS where POS > ?';
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($oldmaximumpos)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_hashref('POS');
+
+ my $text;
+ foreach my $chpos (sort {$erg->{$a} <=> $erg->{$b}} keys %$erg) {
+ my $c = $erg->{$chpos};
+ $text .= sprintf(gettext('New %s channel: %s on position: %d %s'),
+ ($c->{VPID} > 5 or index('+', $c->{VPID})
+ ? gettext('TV')
+ : gettext('Radio')),
+ $c->{Name},
+ $c->{POS},
+ (index('+', $c->{VPID}) || $c->{VPID} == 1 ? gettext('(encrypted)') : ''),
+ );
+ }
+
+ my $rm = main::getModule('REPORT');
+ $rm->news(
+ sprintf(gettext('Discover %d new channels!'), scalar keys %$erg),
+ $text,
+ 'clist',
+ undef,
+ 'veryinteresting',
+ );
+ return 1;
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/CHRONICLE.pm b/lib/XXV/MODULES/CHRONICLE.pm
new file mode 100644
index 0000000..f2fdd2c
--- /dev/null
+++ b/lib/XXV/MODULES/CHRONICLE.pm
@@ -0,0 +1,249 @@
+package XXV::MODULES::CHRONICLE;
+
+use strict;
+use Tools;
+use Locale::gettext;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+
+ my $args = {
+ Name => 'CHRONICLE',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module store recordings at chronicle.'),
+ Version => '0.91',
+ Date => '09.01.2007',
+ Author => 'a.brachold',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ chrlist => {
+ description => gettext('List recording chronicle'),
+ short => 'chrl',
+ callback => sub{ $self->list(@_) },
+ DenyClass => 'rlist',
+ },
+ chrsearch => {
+ description => gettext("Search at chronicle for 'text'"),
+ short => 'chrs',
+ callback => sub{ $self->search(@_) },
+ DenyClass => 'rlist',
+ },
+ chrdelete => {
+ description => gettext("Delete at chronicle with 'id'"),
+ short => 'chrd',
+ callback => sub{ $self->delete(@_) },
+ DenyClass => 'redit',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+
+ if($self->{active} eq 'y') {
+ return 0, panic("Session to database is'nt connected")
+ unless($self->{dbh});
+
+ # don't remove old table, if updated rows => warn only
+ tableUpdated($self->{dbh},'CHRONICLE',6,0);
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+ $self->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS CHRONICLE (
+ id int unsigned auto_increment not NULL,
+ hash varchar(16) NOT NULL default '',
+ title text NOT NULL default '',
+ channel_id varchar(100) NOT NULL default '',
+ starttime datetime NOT NULL default '0000-00-00 00:00:00',
+ duration int NOT NULL default '0',
+ PRIMARY KEY (id),
+ UNIQUE KEY (hash)
+ ) COMMENT = '$version'
+ |);
+
+ main::after(sub{
+ my $m = main::getModule('RECORDS');
+ $m->updated(sub{
+ return 0 if($self->{active} ne 'y');
+
+ lg 'Start chronicle callback to store recordings!';
+ return $self->_insertData();
+
+ });
+ return 1;
+ }, "CHRONICLE: Install callback at update recordings ...", 15);
+ }
+ 1;
+}
+
+# ------------------
+sub _insertData {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+
+ my $sql = qq|
+INSERT IGNORE INTO CHRONICLE
+ SELECT
+ 0, PASSWORD(CONCAT(e.channel_id,e.starttime,title)),
+ REPLACE(IF(Length(e.subtitle)<=0, IF(left(e.title,1) = '%',right(e.title,length(e.title)-1),e.title), CONCAT_WS('~',e.title,e.subtitle)),'~%','~') as title,
+ IF(e.channel_id <> "<undef>",e.channel_id , NULL),
+ e.starttime,
+ e.duration
+ FROM OLDEPG as e,RECORDS as r
+ WHERE r.eventid = e.eventid
+|;
+ $self->{dbh}->do($sql);
+
+ return 1;
+}
+
+# ------------------
+sub list {
+# ------------------
+ my $self = shift;
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my %f = (
+ 'id' => umlaute(gettext('Service')),
+ 'title' => umlaute(gettext('Title')),
+ 'subtitle' => umlaute(gettext('Subtitle')),
+ 'channel' => umlaute(gettext('Channel')),
+ 'day' => umlaute(gettext('Day')),
+ 'start' => umlaute(gettext('Start')),
+ 'stop' => umlaute(gettext('Stop'))
+ );
+
+ my $sql = qq|
+SELECT
+ CHRONICLE.id as $f{'id'},
+ CHRONICLE.title as $f{'title'},
+ CHRONICLE.channel_id as $f{'channel'},
+ DATE_FORMAT(CHRONICLE.starttime, '%d.%m.%Y') as $f{'day'},
+ DATE_FORMAT(CHRONICLE.starttime, '%H:%i') as $f{'start'},
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(CHRONICLE.starttime) + CHRONICLE.duration), '%H:%i') as $f{'stop'}
+FROM CHRONICLE
+ORDER BY CHRONICLE.starttime
+|;
+ my $fields = fields($self->{dbh}, $sql);
+ my $erg = $self->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+ $console->table($erg);
+
+ return 1;
+}
+
+# ------------------
+sub search {
+# ------------------
+ my $self = shift;
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $quest = shift || return $console->err(gettext("No 'text' to search! Please use chrsearch 'text'"));
+
+ $quest =~ s/\'/\./sg;
+ $quest =~ s/\+/\\\\\+/sg;
+
+ my %f = (
+ 'id' => umlaute(gettext('Service')),
+ 'title' => umlaute(gettext('Title')),
+ 'subtitle' => umlaute(gettext('Subtitle')),
+ 'channel' => umlaute(gettext('Channel')),
+ 'day' => umlaute(gettext('Day')),
+ 'start' => umlaute(gettext('Start')),
+ 'stop' => umlaute(gettext('Stop'))
+ );
+
+ my $sql = qq|
+SELECT
+ CHRONICLE.id as $f{'id'},
+ CHRONICLE.title as $f{'title'},
+ CHRONICLE.channel_id as $f{'channel'},
+ DATE_FORMAT(CHRONICLE.starttime, '%d.%m.%Y') as $f{'day'},
+ DATE_FORMAT(CHRONICLE.starttime, '%H:%i') as $f{'start'},
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(CHRONICLE.starttime) + CHRONICLE.duration), '%H:%i') as $f{'stop'}
+FROM CHRONICLE
+WHERE CHRONICLE.title RLIKE ?
+ORDER BY CHRONICLE.starttime
+|;
+ my $fields = fields($self->{dbh}, $sql);
+ my $sth = $self->{dbh}->prepare($sql);
+ $sth->execute($quest)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+ unshift(@$erg, $fields);
+ $console->table($erg);
+
+ return 1;
+}
+
+# ------------------
+sub delete {
+# ------------------
+ my $self = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $items = shift || return $console->err(gettext("No ID to delete! Please use chrdelete 'id'"));
+
+ my @ids = reverse sort{ $a <=> $b } split(/[^0-9]/, $items);
+
+ my $sql = sprintf('DELETE FROM CHRONICLE WHERE id in (%s)', join(',' => ('?') x @ids));
+ my $sth = $self->{dbh}->prepare($sql);
+ $sth->execute(@ids)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+
+ return 1;
+}
+
+1;
diff --git a/lib/XXV/MODULES/CONFIG.pm b/lib/XXV/MODULES/CONFIG.pm
new file mode 100644
index 0000000..771b172
--- /dev/null
+++ b/lib/XXV/MODULES/CONFIG.pm
@@ -0,0 +1,283 @@
+package XXV::MODULES::CONFIG;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'CONFIG',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module edit, write and reconfigure the configuration.'),
+ Version => '0.01',
+ Date => '27.10.2004',
+ Author => 'xpix',
+ Level => 'admin',
+ Commands => {
+ configedit => {
+ description => gettext("Edit configuration 'sector'"),
+ short => 'ce',
+ callback => sub{ $obj->edit(@_) },
+ },
+ configwrite => {
+ description => gettext('Write configuration'),
+ short => 'cw',
+ callback => sub{ $obj->write(@_) },
+ },
+ configget => {
+ description => gettext("Get configuration from 'modname'"),
+ short => 'cg',
+ callback => sub{ $obj->get(@_) },
+ },
+ reconfigure => {
+ description => gettext('Reconfigure all Processes'),
+ short => 'cr',
+ callback => sub{ $obj->reconfigure(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the Configdata
+ $self->{config} = $attr{'-config'};
+
+ return $self;
+}
+
+# ------------------
+sub menu {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $sector = shift || 0;
+
+ my $ret = {};
+ $ret->{title} = gettext("Preferences for XXV");
+ $ret->{highlight} = $sector;
+
+ my $mods = main::getModules;
+ foreach my $module (sort keys %{$mods}) {
+ my $name = $mods->{$module}->{MOD}->{Name};
+ next unless(exists $obj->{config}->{$name});
+
+ $ret->{links}->{$name} = {
+ text => $name,
+ link => "?cmd=configedit&data=$name",
+ };
+ }
+ $ret->{links}->{'reconfigure'} = {
+ text => gettext("Reconfigure"),
+ link => "?cmd=reconfigure",
+ };
+ $ret->{links}->{'write'} = {
+ text => gettext("Write configuration"),
+ link => "?cmd=configwrite",
+ };
+
+ return $console->littlemenu($ret);
+}
+
+# ------------------
+sub edit {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $sector = shift || 0;
+ my $data = shift || 0;
+
+ $obj->menu( $watcher, $console, $sector )
+ if($console->{TYP} eq 'HTML' or ($console->{TYP} ne 'HTML' and not $sector));
+ return unless $sector;
+
+ $sector = uc($sector) unless($sector eq 'General');
+
+ my $cfg = $obj->{config}->{$sector}
+ or return $console->err(sprintf(gettext("Sorry, but the section %s does not exist in configuration!"),$sector));
+
+ my $mod = main::getModule($sector);
+
+ my $prefs = $mod->{MOD}->{Preferences}
+ or return $console->err(sprintf(gettext("Sorry, but the 'Preferences' in Module: %s do not exist"),$sector));
+
+ my $questions = [];
+ foreach my $name (sort { lc($a) cmp lc($b) } keys(%{$prefs})) {
+ my $def = $prefs->{$name}->{default};
+ $def = $cfg->{$name}
+ if(defined $cfg->{$name} && $cfg->{$name} ne "");
+ push(@$questions, $name,
+ {
+ typ => $prefs->{$name}->{type} || 'string',
+ options => $prefs->{$name}->{options},
+ msg => sprintf("%s:\n%s", ucfirst($name), ($prefs->{$name}->{description} || gettext('No Description'))),
+ def => $def,
+ req => $prefs->{$name}->{required},
+ choices => $prefs->{$name}->{choices},
+ check => $prefs->{$name}->{check},
+ readonly => $prefs->{$name}->{readonly} || 0,
+ }
+ );
+ }
+
+ $console->link({text => sprintf(gettext('%s manual'), $sector), url => "?cmd=doc&data=$sector"})
+ if($console->typ eq 'HTML');
+
+ $cfg = $console->question(sprintf(gettext('Change %s configuration'), $sector), $questions, $data);
+
+ if(ref $cfg eq 'HASH') {
+ $obj->{config}->{$sector} = $cfg;
+ $obj->reconfigure();
+ $obj->write();
+
+ debug sprintf('Config Section "%s" is changed and saved%s',
+ $sector,
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $console->message(sprintf(gettext("Sector: '%s' save .. please wait."), $sector));
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+ }
+}
+
+# ------------------
+sub write {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ $obj->reconfigure($watcher, $console);
+ my $configfile = main::getUsrConfigFile;
+
+ $obj->{config}->write( $configfile )
+ or return error( sprintf ("Can't written '%s': %s", $configfile , $! ));
+ $console->message(sprintf gettext("Configuration written in '%s'."), $configfile)
+ if(ref $console);
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+}
+
+# ------------------
+sub get {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $modname = shift || 0;
+
+ return $console->err(gettext('I need a name of the module, in order to indicate the configuration!'))
+ unless($modname and ref $console);
+
+ $modname = uc($modname) unless($modname eq 'General');
+
+ my $cfg = $obj->{config}->{$modname};
+
+ $console->err(sprintf(gettext("Sorry, but the section %s does not exist in configuration!"),$modname))
+ if(! $cfg and ref $console);
+
+ if(ref $console) {
+ return $console->table($cfg);
+ } else {
+ return $cfg;
+ }
+}
+
+# ------------------
+sub reconfigure {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ my $cfg = $obj->{config};
+ foreach my $moduleName (keys %$cfg) {
+ if($moduleName eq 'General') {
+ main::reconfigure();
+ } else {
+ my $mod = main::getModule($moduleName)
+ or (error("$moduleName does not exist!") && next);
+ foreach my $parameter (keys %{$mod->{MOD}->{Preferences}}) {
+ if(defined $mod->{$parameter}) {
+ $cfg->{$moduleName}->{$parameter} = $mod->{MOD}->{Preferences}->{$parameter}->{default}
+ if(not defined $cfg->{$moduleName}->{$parameter});
+ $mod->{$parameter} = $cfg->{$moduleName}->{$parameter};
+
+ # Check this input
+ if(my $check = $mod->{MOD}->{Preferences}->{$parameter}->{check}) {
+ if(ref $check eq 'CODE') {
+ my ($ok, $err) = &$check($mod->{$parameter});
+ unless($ok || not $err) {
+ my $message = sprintf("Config -> %s -> %s: %s %s", $moduleName, $parameter, $mod->{$parameter}, $err);
+ if(ref $console) {
+ $console->err($message);
+ } else {
+ error $message;
+ }
+ }
+ }
+ }
+
+ } else {
+ $console->err(sprintf(gettext("Strange, i can not find %s in %s"), $parameter, $moduleName))
+ if(ref $console);
+ }
+ }
+ }
+ }
+
+ $obj->menu( $watcher, $console )
+ if(ref $console and $console->{TYP} eq 'HTML');
+ $console->message(gettext('Reconfigure successfully'))
+ if(ref $console);
+}
+
+# ------------------
+sub realModNames {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $mods = main::getModules();
+ my @realModName;
+
+ # Search for command and display the Description
+ foreach my $modName (sort keys %{$mods}) {
+ my $modCfg = $mods->{$modName}->{MOD};
+ push(@realModName, $mods->{$modName}->{MOD}->{Name})
+ if(exists $mods->{$modName}->{MOD}->{Name});
+ }
+
+ return sort @realModName;
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/EPG.pm b/lib/XXV/MODULES/EPG.pm
new file mode 100644
index 0000000..6ad8107
--- /dev/null
+++ b/lib/XXV/MODULES/EPG.pm
@@ -0,0 +1,1243 @@
+package XXV::MODULES::EPG;
+use strict;
+
+use Tools;
+use File::Basename;
+use Locale::gettext;
+
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'EPG',
+ Prereq => {
+ 'Date::Manip' => 'date manipulation routines',
+ 'Time::Local' => 'efficiently compute time from local and GMT time ',
+ },
+ Description => gettext('This module reads new EPG Data and stores them in the database.'),
+ Version => '0.92',
+ Date => '2007-06-03',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ epgimages => {
+ description => gettext('Location of additional EPG images.'),
+ default => '/var/cache/vdr/epgimages',
+ type => 'dir',
+ },
+ interval => {
+ description => gettext('How often EPG data are to be analyzed (in seconds)'),
+ default => 60 * 60,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ periods => {
+ description => gettext("Pre-defined list for the 'Running Now' view (comma separated list)"),
+ default => '12:00,18:00,20:15,22:00',
+ type => 'string',
+ required => gettext('This is required!'),
+ },
+ timeframe => {
+ description => gettext("How much hours to display in schema"),
+ default => 2,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ search => {
+ description => gettext('Search in EPG Data'),
+ short => 's',
+ callback => sub{ $obj->search(@_) },
+ },
+ program => {
+ description => gettext("List program for channel 'channel name'"),
+ short => 'p',
+ callback => sub{ $obj->program(@_) },
+ },
+ display => {
+ description => gettext("Show program event 'eventid'"),
+ short => 'd',
+ callback => sub{ $obj->display(@_) },
+ },
+ now => {
+ description => gettext('Display events is running now'),
+ short => 'n',
+ callback => sub{ $obj->runningNow(@_) },
+ },
+ next => {
+ description => gettext('Display events is running next'),
+ short => 'nx',
+ callback => sub{ $obj->runningNext(@_) },
+ },
+ schema => {
+ description => gettext('Display events in a schematic way'),
+ short => 'sch',
+ callback => sub{ $obj->schema(@_) },
+ },
+ erestart => {
+ description => gettext('Reload EPG data'),
+ short => 'er',
+ callback => sub{
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ debug sprintf('Start reload EPG data%s',
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $obj->startReadEpgData($watcher,$console);
+ },
+ Level => 'admin',
+ },
+ erun => {
+ description => gettext('Display the epg event running in vdr'),
+ short => 'en',
+ callback => sub{ $obj->NowOnChannel(@_) },
+ Level => 'user',
+ DenyClass => 'remote',
+ },
+ conflict => {
+ hidden => 'yes',
+ callback => sub{ $obj->checkOnTimer(@_) },
+ },
+ edescription => {
+ hidden => 'yes',
+ short => 'ed',
+ callback => sub { $obj->getDescription(@_) },
+ },
+ esuggest => {
+ hidden => 'yes',
+ callback => sub{ $obj->suggest(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $lastReportTime = shift || 0;
+
+ my $total = 0;
+ my $newEntrys = 0;
+
+ {
+ my $sth = $obj->{dbh}->prepare("select count(*) as count from EPG");
+ if(!$sth->execute())
+ {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ } else {
+ my $erg = $sth->fetchrow_hashref();
+ $total = $erg->{count} if($erg && $erg->{count});
+ }
+ }
+
+ {
+ my $sth = $obj->{dbh}->prepare("select count(*) as count from EPG where UNIX_TIMESTAMP(addtime) > ?");
+ if(!$sth->execute($lastReportTime))
+ {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ } else {
+ my $erg = $sth->fetchrow_hashref();
+ $newEntrys = $erg->{count} if($erg && $erg->{count});
+ }
+ }
+
+ return {
+ message => sprintf(gettext('EPG table contains %d entries and since the last login at %s %d new entries'),
+ $total, scalar localtime($lastReportTime), $newEntrys),
+ };
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0, panic("Session to database is'nt connected")
+ unless($obj->{dbh});
+
+ # Look for table or create this table
+ foreach my $table (qw/EPG OLDEPG/) {
+
+ # remove old table, if updated rows
+ tableUpdated($obj->{dbh},$table,14,1);
+
+ my $version = main::getVersion;
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS $table (
+ eventid bigint unsigned NOT NULL default '0',
+ title text NOT NULL default '',
+ subtitle text default '',
+ description text,
+ channel_id varchar(100) NOT NULL default '',
+ starttime datetime NOT NULL default '0000-00-00 00:00:00',
+ duration int(11) NOT NULL default '0',
+ tableid tinyint(4) default 0,
+ image text default '',
+ version tinyint(3) default 0,
+ video varchar(100) default '',
+ audio varchar(255) default '',
+ addtime datetime NOT NULL default '0000-00-00 00:00:00',
+ vpstime datetime default '0000-00-00 00:00:00',
+ PRIMARY KEY (eventid),
+ INDEX (starttime),
+ INDEX (channel_id)
+ ) COMMENT = '$version'
+ |);
+ }
+
+ $obj->{after_updated} = [];
+
+ # Repair later Data ...
+ main::after(sub{
+ $obj->{svdrp} = main::getModule('SVDRP');
+ unless($obj->{svdrp}) {
+ panic ("Can't get modul SVDRP");
+ return 0;
+ }
+
+ $obj->startReadEpgData();
+
+ # Restart watcher every x hours
+ Event->timer(
+ interval => $obj->{interval},
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ lg sprintf('The read on epg data is restarted!');
+ $obj->startReadEpgData();
+ },
+ );
+ return 1;
+ }, "EPG: Start read epg data and repair ...", 40);
+
+ return 1;
+}
+
+# ------------------
+sub startReadEpgData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ my $waiter;
+ if(ref $console && $console->typ eq 'HTML') {
+ $waiter = $console->wait(gettext("Read EPG data ..."),0,1000,'no');
+ }
+
+ # Read data over SVDRP
+ my $vdata = $obj->{svdrp}->command('LSTE');
+ map {
+ $_ =~ s/^\d{3}.//;
+ $_ =~ s/[\r|\n]$//;
+ } @$vdata;
+ debug sprintf('The read on epg data start now!');
+
+
+ # Adjust waiter max value now.
+ $waiter->max(scalar @$vdata)
+ if(ref $console && ref $waiter);
+
+ $obj->moveOldEPGEntrys();
+
+ # Read file row by row
+ my $updated = $obj->compareEpgData($vdata,$watcher,$console,$waiter);
+
+ $obj->deleteDoubleEPGEntrys();
+
+ $obj->updated() if($updated);
+
+ # last call of waiter
+ $waiter->end() if(ref $waiter);
+
+ if(ref $console) {
+ $console->start() if(ref $waiter);
+ $console->message(sprintf(gettext("%d events in database updated."), $updated));
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+ }
+}
+
+# Routine um Callbacks zu registrieren und
+# diese nach dem Aktualisieren der EPG Daten zu starten
+# ------------------
+sub updated {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cb = shift || 0;
+ my $log = shift || 0;
+
+ if($cb) {
+ push(@{$obj->{after_updated}}, [$cb, $log]);
+ } else {
+ foreach my $CB (@{$obj->{after_updated}}) {
+ next unless(ref $CB eq 'ARRAY');
+ lg $CB->[1]
+ if($CB->[1]);
+ &{$CB->[0]}()
+ if(ref $CB->[0] eq 'CODE');
+ }
+ }
+}
+# This Routine will compare data from epg.data
+# and EPG Database row by row
+# ------------------
+sub compareEpgData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vdata = shift || return error('No data');
+ my $watcher = shift;
+ my $console = shift;
+ my $waiter = shift;
+
+ my $changedData = 0;
+ my $updatedData = 0;
+ my $deleteData = 0;
+
+ # Second - read data
+ my $count = 0;
+
+ my $vdrData;
+ my $channel;
+ my $channelname;
+ while($count < scalar $vdata) {
+ ($vdrData,$channel,$channelname,$count) = $obj->readEpgData($vdata,$count);
+ last if(not $channel);
+
+ $waiter->next($count,undef, sprintf(gettext("Analyze channel '%s'"), $channelname))
+ if(ref $waiter);
+
+ # First - read database
+ my $sql = qq|select eventid, title, subtitle, length(description) as ldescription, duration, UNIX_TIMESTAMP(starttime) as starttime, UNIX_TIMESTAMP(vpstime) as vpstime, video, audio from EPG where channel_id = ? |;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($channel)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $db_data = $sth->fetchall_hashref('eventid');
+
+ lg sprintf( 'Compare EPG Database with data from vdr : %d / %d for channel %s', scalar keys %$db_data,scalar keys %$vdrData, $channel);
+ # Compare this Hashes
+ foreach my $eid (keys %{$vdrData}) {
+ my $row = $vdrData->{$eid};
+
+ # Exists in DB .. update
+ if(exists $db_data->{$eid}) {
+ # Compare fields
+ foreach my $field (qw/title subtitle ldescription duration starttime vpstime video audio/) {
+ next if(not exists $row->{$field} or not $row->{$field});
+ if((not exists $db_data->{$eid}->{$field})
+ or (not $db_data->{$eid}->{$field})
+ or ($db_data->{$eid}->{$field} ne $row->{$field})) {
+ $obj->replace($eid, $row);
+ $updatedData++;
+ last;
+ }
+ }
+
+ # delete updated rows from hash
+ delete $db_data->{$eid};
+
+ } else {
+ # Not exists in DB .. insert
+ $obj->replace($eid, $row);
+ $changedData++;
+ }
+ }
+
+ # Delete unused EpgEntrys in DB
+ $deleteData += scalar keys %$db_data;
+ if(scalar keys %$db_data > 0) {
+ my $sth = $obj->{dbh}->prepare('DELETE FROM EPG WHERE eventid IN (?)');
+ foreach my $eventid (keys %$db_data) {
+ if(!$sth->execute($eventid)) {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+ }
+ }
+ }
+ debug 'Finish .. %d events created, %d events replaced, %d events deleted', $changedData, $updatedData, $deleteData;
+
+ return ($changedData + $updatedData + $deleteData);
+}
+
+# ------------------
+sub moveOldEPGEntrys {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ # Copy and delete old EPG Entrys
+ $obj->{dbh}->do('REPLACE INTO OLDEPG SELECT * FROM EPG WHERE (UNIX_TIMESTAMP(EPG.starttime) + EPG.duration) < UNIX_TIMESTAMP()');
+ $obj->{dbh}->do('DELETE FROM EPG WHERE (UNIX_TIMESTAMP(EPG.starttime) + EPG.duration) < UNIX_TIMESTAMP()');
+}
+
+# ------------------
+sub deleteDoubleEPGEntrys {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ # Delete double EPG Entrys
+ my $erg = $obj->{dbh}->selectall_arrayref('SELECT eventid FROM EPG GROUP BY starttime, channel_id having count(*) > 1');
+ if(scalar @$erg > 0) {
+ lg sprintf('Repair data found %d wrong events!', scalar @$erg);
+ my $sth = $obj->{dbh}->prepare('DELETE FROM EPG WHERE eventid = ?');
+ foreach my $row (@$erg) {
+ $sth->execute($row->[0]);
+ }
+ }
+}
+
+# ------------------
+sub replace {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $eventid = shift || return error ('No eventid to insert!');;
+ my $attr = shift || return error ('No data to insert!');
+
+ my $sth = $obj->{dbh}->prepare('REPLACE INTO EPG(eventid, title, subtitle, description, channel_id, duration, tableid, image, version, video, audio, starttime, addtime, vpstime) VALUES (?,?,?,?,?,?,?,?,?,?,?,FROM_UNIXTIME(?),FROM_UNIXTIME(?),FROM_UNIXTIME(?))');
+ $sth->execute(
+ $eventid,
+ $attr->{title},
+ $attr->{subtitle},
+ $attr->{description},
+ $attr->{channel},
+ $attr->{duration},
+ $attr->{tableid},
+ $attr->{image} || '',
+ hex($attr->{version}),
+ $attr->{video} || '1 01 deu 4:3',
+ $attr->{audio} || "2 03 deu stereo",
+ $attr->{starttime},
+ time,
+ $attr->{vpstime}
+ ) if($attr->{channel});
+}
+
+# ------------------
+sub encodeEpgId {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $epgid = shift || return error('No EPG Id!');
+ my $channel = shift || return error('No Channel!');
+
+ # look for NID-TID-SID for unique eventids (SID 0-30000 / TID 0 - 1000 / NID 0 - 10000
+ my @id = split('-', $channel);
+
+ # Make a fix format 0xCCCCEEEE : C-Channelid (high-word), E-Eventid(low-word) => real-eventid = uniqueid & FFFF
+ my $eventid = ((($id[-3] + $id[-2] + $id[-1]) & 0x3FFF) << 16) | ($epgid & 0xFFFF);
+
+ return $eventid;
+}
+
+# ------------------
+sub readEpgData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vdata = shift || return error('Problem to read Data!');
+ my $count = shift || 0;
+ my $dataHash = {};
+
+ my $cmod = main::getModule ('CHANNELS');
+ my $channels = $cmod->ChannelArray ('Id,Name');
+ my $channel;
+ my $channelname;
+ my $event;
+
+ #debug 'Read %d lines in EPG Database from %d', scalar @$vdata,$offset;
+ for(;$count < scalar (@$vdata);$count++) {
+ my $line = @{$vdata}[$count];
+
+ # Ok, Datarow complete...
+ if($line eq 'e' and $event->{eventid} and $event->{channel}) {
+ if(-e sprintf('%s/%d.png', $obj->{epgimages}, $event->{eventid})) {
+ my $firstimage = sprintf('%d.png',$event->{eventid});
+ $event->{image} = $firstimage."\n";
+ my $imgpath = sprintf('%s/%d_?.png',$obj->{epgimages},$event->{eventid});
+ foreach my $img (glob($imgpath)) {
+ $event->{image} .= sprintf("%s.png\n", basename($img, '.png'));
+ }
+ }
+
+ $channel = $event->{channel};
+ my $eventid = $obj->encodeEpgId($event->{eventid}, $channel);
+
+ $event->{title} = gettext("No Title")
+ unless($event->{title});
+ $event->{description} = ""
+ unless($event->{description});
+
+ %{$dataHash->{$eventid}} = %{$event};
+
+ $event = undef;
+ $event->{channel} = $channel;
+ next;
+ }
+ elsif($line eq 'c') {
+ # Finish this channel
+ return ($dataHash,$channel,$channelname,$count+1)
+ if(scalar keys %$dataHash);
+
+ undef $event->{channel};
+ undef $channel;
+ undef $channelname;
+ }
+
+ my ($mark, $data) = $line =~ /^(\S)\s+(.+)/g;
+ next unless($mark and $data);
+
+ # Next channel
+ if($mark eq 'C') {
+ if($channel) {
+ debug 'Missing channel endtag c at line %d',$count;
+ return ($dataHash,$channel,$channelname,$count) if(scalar keys %$dataHash);
+ }
+ undef $event->{channel};
+ my $channel = (split(/\s+/, $data))[0];
+ # import only known channels
+ foreach my $ch (@{$channels}) {
+ if($ch->[0] eq $channel) {
+ $event->{channel} = $channel;
+ $channelname = $ch->[1];
+ last;
+ }
+ }
+ } elsif($mark eq 'E') {
+ ($event->{eventid}, $event->{starttime}, $event->{duration}, $event->{tableid}, $event->{version}) = split(/\s+/, $data);
+ } elsif($mark eq 'T') {
+ $event->{title} = $data;
+ } elsif($mark eq 'S') {
+ $event->{subtitle} = $data;
+ } elsif($mark eq 'D') {
+ $event->{description} = $data;
+ $event->{description} =~ s/\|/\r\n/g; # pipe used from vdr as linebreak
+ $event->{description} =~ s/^\s+//; # no leading white space
+ $event->{description} =~ s/\s+$//; # no trailing white space
+ $event->{ldescription} = length($event->{description});
+ } elsif($mark eq 'X') {
+ my @d = split(/\s+/, $data);
+ if($d[0] eq '1') {
+ $event->{video} .= $data;
+ } else {
+ $event->{audio} .= $data."\n";
+ }
+ } elsif($mark eq 'V') {
+ $event->{vpstime} = $data;
+ }
+ }
+# debug 'Finish Read %d lines in EPG Database at %d', scalar @$vdata,$count;
+ return ($dataHash,$channel,$channelname,$count);
+}
+
+# ------------------
+sub search {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift;
+ my $params = shift;
+
+ my $tim = main::getModule('TIMERS');
+
+ # Textsearch
+ my $search;
+ if($data) {
+ if($params->{Where} && $params->{Where} eq 'title') {
+ $search = buildsearch("e.title",$data);
+ } elsif($params->{Where} && $params->{Where} eq 'titlesubtitle') {
+ $search = buildsearch("e.title,e.subtitle",$data);
+ } else {
+ $search = buildsearch("e.title,e.subtitle,e.description",$data);
+ }
+ }
+
+ # Channelsearch
+ if($params->{channel}) {
+ $search .= ' AND '
+ if($search);
+ $search .= sprintf('c.POS = %lu ', $params->{channel});
+ }
+
+ # Videoformat search
+ if($params->{Videoformat} && $params->{Videoformat} eq 'widescreen') {
+ $search .= ' AND '
+ if($search);
+ $search .= 'e.video like "%%16:9%%" ';
+ }
+
+ # Audioformat search
+ # XXX: Leider kann man an den Audioeintrag nicht richtig erkennnen
+ # hab erst zu spät erkannt das diese Info aus dem tvm2vdr kommen ;(
+# if($params->{Audioformat} eq 'dts') {
+# $search .= ' AND '
+# if($search);
+# $search .= 'e.audio like "%%Digital%%" ';
+# }
+
+ # MinLength search
+ if($params->{MinLength}) {
+ $search .= ' AND '
+ if($search);
+ $search .= sprintf('e.duration >= %d ', ($params->{MinLength}*60));
+ }
+
+
+ my $erg = [];
+ if($search) {
+ my $sql = qq|
+ select
+ e.eventid as Service,
+ e.title as Title,
+ e.subtitle as __Subtitle,
+ c.Name as Channel,
+ c.POS as __Pos,
+ DATE_FORMAT(e.starttime, '%H:%i') as Start,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) + e.duration), '%H:%i') as Stop,
+ UNIX_TIMESTAMP(e.starttime) as Day,
+ e.description,
+ IF(e.vpstime!=0,DATE_FORMAT(e.vpstime, '%H:%i'),'') as __VPS
+ from
+ EPG as e,
+ CHANNELS as c
+ where
+ e.channel_id = c.Id
+ AND ( $search )
+ order by
+ starttime
+ |;
+ #dumper($sql);
+ my $fields = fields($obj->{dbh}, $sql);
+
+ $erg = $obj->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+ }
+ $console->table($erg, {
+ timers => $tim->getEpgIds,
+ runningTimer => $tim->getRunningTimer('eventid'),
+ }
+ );
+}
+
+# ------------------
+sub program {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $channel = shift || $obj->{dbh}->selectrow_arrayref("select POS from CHANNELS limit 1")->[0];
+
+ my $mod = main::getModule('CHANNELS');
+ my $tim = main::getModule('TIMERS');
+
+ my $cid;
+ if($channel =~ /^\d+$/sig) {
+ $cid = $mod->PosToChannel($channel)
+ or return $console->err(sprintf(gettext("This channel '%s' does not exist in the database!"),$channel));
+ } else {
+ $cid = $mod->NameToChannel($channel)
+ or return $console->err(sprintf(gettext("This channel '%s' does not exist in the database!"),$channel));
+ }
+
+ my $sql = qq|
+select
+ e.eventid as Service,
+ e.title as Title,
+ e.subtitle as __Subtitle,
+ DATE_FORMAT(e.starttime, '%H:%i') as Start,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) + e.duration), '%H:%i') as Stop,
+ UNIX_TIMESTAMP(e.starttime) as Day,
+ e.description as __Description,
+ e.video as __Video,
+ e.audio as __Audio,
+ IF(e.vpstime!=0,DATE_FORMAT(e.vpstime, '%H:%i'),'') as __VPS
+from
+ EPG as e, CHANNELS as c
+where
+ e.channel_id = c.Id and
+ e.channel_id = ?
+order by
+ starttime
+|;
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($cid)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+ unshift(@$erg, $fields);
+
+ $console->table($erg, {
+ channels => $mod->ChannelArray('Name'),
+ current => $mod->ChannelToPos($cid),
+ timers => $tim->getEpgIds,
+ runningTimer => $tim->getRunningTimer('eventid'),
+ }
+ );
+}
+
+# ------------------
+sub display {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $eventid = shift || return $console->err(gettext("No EventID to display the Event Programm! Please use display 'eventid'"));
+
+ my %f = (
+ 'Id' => umlaute(gettext('Service')),
+ 'Title' => umlaute(gettext('Title')),
+ 'Subtitle' => umlaute(gettext('Subtitle')),
+ 'Channel' => umlaute(gettext('Channel')),
+ 'Start' => umlaute(gettext('Start')),
+ 'Stop' => umlaute(gettext('Stop')),
+ 'Description' => umlaute(gettext('Description')),
+ 'Percent' => umlaute(gettext('Percent')),
+ );
+
+ my $fields;
+ my $erg;
+
+ my $start = "e.starttime";
+ my $stopp = "FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) + e.duration)";
+ my $vps = "e.vpstime";
+
+ $start = "UNIX_TIMESTAMP(e.starttime)" if($console->typ eq "HTML");
+ $stopp = "UNIX_TIMESTAMP(e.starttime) + e.duration" if($console->typ eq "HTML");
+ $vps = "UNIX_TIMESTAMP(e.vpstime)" if($console->typ eq "HTML");
+
+ foreach my $table (qw/EPG OLDEPG/) {
+ my $sql = qq|
+select
+ e.eventid as $f{'Id'},
+ e.title as $f{'Title'},
+ e.subtitle as $f{'Subtitle'},
+ $start as $f{'Start'},
+ $stopp as $f{'Stop'},
+ c.Name as $f{'Channel'},
+ e.description as $f{'Description'},
+ e.image as __Image,
+ (unix_timestamp(e.starttime) + e.duration - unix_timestamp())/duration*100 as $f{'Percent'},
+ e.video as __Video,
+ e.audio as __Audio,
+ IF(e.vpstime!=0,$vps,'') as __VPS
+from
+ $table as e,CHANNELS as c
+where
+ e.channel_id = c.Id
+ and eventid = ?
+|;
+ $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eventid)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ $erg = $sth->fetchall_arrayref();
+
+ last
+ if(scalar @{$erg} != 0 );
+ }
+
+ return $console->err(sprintf(gettext("No data for event '%d' present to display!"),$eventid))
+ if(scalar @{$erg} == 0 );
+
+ unshift(@$erg, $fields);
+
+ my $tim = main::getModule('TIMERS');
+ $console->table($erg,{timers => $tim->getEpgIds});
+}
+
+# ------------------
+sub runningNext {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift;
+ my $param = shift || {};
+ my $cgroups = main::getModule('CHANNELS')->ChannelGroupsArray('Name');
+ my $cgrp = $param->{cgrp} || $cgroups->[0][1]; # Erster GroupEintrag
+
+ # Create temporary table
+ $obj->{dbh}->do(qq|
+CREATE TEMPORARY TABLE IF NOT EXISTS NEXTEPG (
+ channel_id varchar(100) NOT NULL default '',
+ nexttime datetime NOT NULL default '0000-00-00 00:00:00'
+ )
+|);
+ # Remove old data
+ $obj->{dbh}->do('delete from NEXTEPG');
+
+ # Get channelid and starttime of next broadcasting
+ my $sqltemp = qq|
+INSERT INTO NEXTEPG select
+ c.Id as channel_id,
+ MIN(e.starttime) as nexttime
+ FROM EPG as e, CHANNELS as c
+ WHERE e.channel_id = c.Id
+AND UNIX_TIMESTAMP(e.starttime) > UNIX_TIMESTAMP(NOW())
+AND c.GRP = ?
+
+GROUP BY c.Id
+|;
+ my $sthtemp = $obj->{dbh}->prepare($sqltemp);
+ $sthtemp->execute($cgrp)
+ or return error sprintf("Can't execute query: %s.",$sthtemp->errstr);
+
+ my %f = (
+ 'Service' => umlaute(gettext('Service')),
+ 'Title' => umlaute(gettext('Title')),
+ 'Channel' => umlaute(gettext('Channel')),
+ 'Start' => umlaute(gettext('Start')),
+ 'Stop' => umlaute(gettext('Stop'))
+ );
+ my $sql =
+qq|
+select
+ e.eventid as $f{'Service'},
+ e.title as $f{'Title'},
+ e.subtitle as __Subtitle,
+ c.Name as $f{'Channel'},
+ c.POS as __POS,
+ g.Name as __Channelgroup,
+ DATE_FORMAT(e.starttime, "%H:%i") as $f{'Start'},
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) + e.duration), "%H:%i") as $f{'Stop'},
+ e.description as __Description,
+ 999 as __Percent,
+ IF(e.vpstime!=0,DATE_FORMAT(e.vpstime, '%H:%i'),'') as __VPS
+FROM
+ EPG as e, CHANNELS as c, NEXTEPG as n, CHANNELGROUPS as g
+WHERE
+ e.channel_id = c.Id
+ AND n.channel_id = c.Id
+ AND c.GRP = g.Id
+ AND e.starttime = n.nexttime
+ AND c.GRP = ?
+ORDER BY
+ c.POS|;
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($cgrp)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+ unshift(@$erg, $fields);
+
+ my $tim = main::getModule('TIMERS');
+
+ $console->table($erg,
+ {
+ timers => $tim->getEpgIds,
+ runningTimer => $tim->getRunningTimer('eventid'),
+ periods => $obj->{periods},
+ cgroups => $cgroups,
+ channelgroup => $cgrp,
+ }
+ );
+}
+
+# ------------------
+sub runningNow {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $zeit = shift || time;
+ my $param = shift || {};
+ my $cgroups = main::getModule('CHANNELS')->ChannelGroupsArray('Name');
+ my $cgrp = $param->{cgrp} || $cgroups->[0][1]; # Erster GroupEintrag
+
+ # i.e.: 635 --> 06:35
+ $zeit = fmttime($zeit)
+ if(length($zeit) <= 4);
+
+ # i.e.: 06:35 --> timeinsecs
+ if($zeit =~ /^\d+:\d+$/sig) {
+ $zeit = UnixDate(ParseDate($zeit),"%s") || time;
+ }
+
+ $zeit += 86400 if($zeit < time);
+ $zeit++;
+
+ my %f = (
+ 'Service' => umlaute(gettext('Service')),
+ 'Title' => umlaute(gettext('Title')),
+ 'Channel' => umlaute(gettext('Channel')),
+ 'Start' => umlaute(gettext('Start')),
+ 'Stop' => umlaute(gettext('Stop')),
+ 'Percent' => umlaute(gettext('Percent')),
+ );
+ my $sql =
+qq|
+select
+ e.eventid as $f{'Service'},
+ e.title as $f{'Title'},
+ e.subtitle as __Subtitle,
+ c.Name as $f{'Channel'},
+ c.POS as __POS,
+ g.Name as __Channelgroup,
+ DATE_FORMAT(e.starttime, "%H:%i") as $f{'Start'},
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) + e.duration), "%H:%i") as $f{'Stop'},
+ e.description as __Description,
+ (unix_timestamp(e.starttime) + e.duration - unix_timestamp())/e.duration*100 as $f{'Percent'},
+ IF(e.vpstime!=0,DATE_FORMAT(e.vpstime, '%H:%i'),'') as __VPS
+FROM
+ EPG as e, CHANNELS as c, CHANNELGROUPS as g
+WHERE
+ e.channel_id = c.Id
+ AND c.GRP = g.Id
+ AND ? BETWEEN UNIX_TIMESTAMP(e.starttime)
+ AND (UNIX_TIMESTAMP(e.starttime) + e.duration)
+ AND c.GRP = ?
+ORDER BY
+ c.POS|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($zeit, $cgrp)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+ unshift(@$erg, $fields);
+
+ my $tim = main::getModule('TIMERS');
+ $console->table($erg,
+ {
+ timers => $tim->getEpgIds,
+ runningTimer => $tim->getRunningTimer('eventid'),
+ zeit => $zeit,
+ periods => $obj->{periods},
+ cgroups => $cgroups,
+ channelgroup => $cgrp,
+ }
+ );
+}
+
+# ------------------
+sub NowOnChannel {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $channel = shift || $obj->_actualChannel || return error('No Channel!');
+ my $zeit = time;
+
+ my $sql =
+qq|
+select
+ e.eventid as Service,
+ e.title as Title,
+ e.subtitle as Subtitle,
+ c.Name as Channel,
+ c.POS as POS,
+ e.video as __video,
+ e.audio as __audio,
+ DATE_FORMAT(e.starttime, "%a %d.%m") as StartDay,
+ DATE_FORMAT(e.starttime, "%H:%i") as StartTime,
+ (unix_timestamp(e.starttime) + e.duration - unix_timestamp())/e.duration*100 as __Percent,
+ e.description as Description,
+ IF(e.vpstime!=0,DATE_FORMAT(e.vpstime, '%H:%i'),'') as __VPS
+FROM
+ EPG as e, CHANNELS as c
+WHERE
+ e.channel_id = c.Id
+ AND ? BETWEEN UNIX_TIMESTAMP(e.starttime)
+ AND (UNIX_TIMESTAMP(e.starttime) + e.duration)
+ AND c.POS = ?
+ORDER BY
+ starttime
+LIMIT 1
+|;
+#dumper($sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($zeit, $channel)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+
+ if(ref $console) {
+ return $console->table($erg);
+ } else {
+ return $erg;
+ }
+}
+
+# ------------------
+sub _actualChannel {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $erg = $obj->{svdrp}->command('chan');
+ my ($chanpos, $channame) = $erg->[1] =~ /^250\s+(\d+)\s+(\S+)/sig;
+ return $chanpos;
+}
+
+# ------------------
+sub schema {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $zeit = shift || time;
+ my $param = shift || {};
+
+
+ # i.e.: 635 --> 06:35
+ $zeit = fmttime($zeit)
+ if(length($zeit) <= 4);
+
+ # i.e.: 06:35 --> timeinsecs
+ if($zeit =~ /^\d+:\d+$/sig) {
+ $zeit = UnixDate(ParseDate($zeit),"%s") || time;
+ }
+
+ $zeit += 86400 if($zeit < time - ($obj->{timeframe} * 3600));
+ $zeit++;
+ my $zeitvon = $obj->toFullHour($zeit);
+
+ my $zeitbis = $zeitvon + ($obj->{timeframe}*3600);
+ my $cgroups = main::getModule('CHANNELS')->ChannelGroupsArray('Name');
+ my $cgrp = $param->{cgrp} || $cgroups->[0][1]; # Erster GroupEintrag
+
+ my $sql =
+qq|
+select
+ e.eventid as Service,
+ e.title as Title,
+ e.subtitle as __Subtitle,
+ c.Name as Channel,
+ c.POS as __POS,
+ DATE_FORMAT(e.starttime, "%H:%i") as Start,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) + e.duration), "%H:%i") as Stop,
+ (unix_timestamp(e.starttime) + e.duration - unix_timestamp())/e.duration*100 as Percent,
+ e.description as __Description,
+ UNIX_TIMESTAMP(starttime) as second_start,
+ UNIX_TIMESTAMP(starttime) + e.duration as second_stop,
+ e.video as __video,
+ e.audio as __audio,
+ e.image as __image
+FROM
+ EPG as e, CHANNELS as c
+WHERE
+ e.channel_id = c.Id
+ AND
+ (
+ ( UNIX_TIMESTAMP(e.starttime) >= ? AND UNIX_TIMESTAMP(e.starttime) <= ? )
+ OR
+ ( UNIX_TIMESTAMP(e.starttime) + e.duration >= ? AND UNIX_TIMESTAMP(e.starttime) + e.duration <= ? )
+ OR
+ ( UNIX_TIMESTAMP(e.starttime) <= ? AND UNIX_TIMESTAMP(e.starttime) + e.duration >= ? )
+ )
+ AND
+ c.GRP = ?
+ORDER BY
+ c.POS,e.starttime
+|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($zeitvon,$zeitbis,$zeitvon,$zeitbis,$zeitvon,$zeitbis,$cgrp)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+
+ my $data = {};
+ foreach my $c (@$erg) {
+ push(@{$data->{$c->[4]}}, $c);
+ }
+
+ my $tim = main::getModule('TIMERS');
+ $console->table($data,
+ {
+ timers => $tim->getEpgIds,
+ runningTimer => $tim->getRunningTimer('eventid'),
+ zeitvon => $zeitvon,
+ zeitbis => $zeitbis,
+ periods => $obj->{periods},
+ cgroups => $cgroups,
+ channelgroup => $cgrp,
+ HouresProSite => $obj->{timeframe}
+ }
+ );
+}
+
+# ------------------
+sub checkOnTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $eid = shift || return error('No Id');
+ my $tim = main::getModule('TIMERS');
+
+ my $sql = qq|
+SELECT
+ e.starttime as NextStartTime,
+ ADDDATE(e.starttime, INTERVAL e.duration SECOND) as NextStopTime,
+ LEFT(c.Source,1) as source,
+ c.TID as transponderid
+FROM
+ EPG as e, CHANNELS as c
+WHERE
+ e.eventid = ?
+ and
+ e.channel_id = c.Id
+|;
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($eid)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $data = $sth->fetchrow_hashref();
+ my $erg = $tim->checkOverlapping($data) || ['ok'];
+ my $tmod = main::getModule('TIMERS');
+ # Zeige den Title des Timers
+ foreach (@$erg) {
+ $_ = $tmod->getTimerById((split(':', $_))[0])->{File}
+ unless($_ eq 'ok');
+ }
+
+ $console->message(join(',',@$erg))
+ if(ref $console);
+
+}
+
+# ------------------
+sub getDescription { # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $eid = shift || 0;
+
+ my $event = $obj->getId($eid,"description");
+
+ $console->message($event && $event->{description} ? $event->{description} : "")
+ if(ref $console);
+}
+
+# ------------------
+sub toFullHour {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $zeit = shift || return error ('No Time to convert!' );
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
+ localtime($zeit);
+ my $retzeit = timelocal(0, 0, $hour, $mday, $mon, $year);
+ return $retzeit;
+}
+
+
+# ------------------
+sub getId {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return error ('No Id!' );
+ my $fields = shift || '*';
+
+ foreach my $table (qw/EPG OLDEPG/) {
+ # EPG
+ my $sql = sprintf('select %s from %s WHERE eventid = ?',$fields, $table);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($id)
+ or return error "Can't execute query: $sth->errstr.";
+
+ my $erg = $sth->fetchrow_hashref();
+ return $erg
+ if($erg);
+ }
+ debug("Event %d not exist!", $id);
+ return {};
+}
+
+# ------------------
+sub suggest { # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+ my $params = shift;
+
+ if($search) {
+ my $ch = '';
+ if($params->{channel}) {
+ $ch = " AND c.POS = ? ";
+ }
+
+ my $sql = qq|
+ SELECT
+ e.title as title
+ FROM
+ EPG as e,
+ CHANNELS as c
+ WHERE
+ channel_id = c.Id
+ AND ( e.title LIKE ? )
+ $ch
+ GROUP BY
+ title
+UNION
+ SELECT
+ e.subtitle as title
+ FROM
+ EPG as e,
+ CHANNELS as c
+ WHERE
+ channel_id = c.Id
+ AND ( e.subtitle LIKE ? )
+ $ch
+ GROUP BY
+ title
+ORDER BY
+ title
+LIMIT 25
+ |;
+ my $sth = $obj->{dbh}->prepare($sql);
+ if($params->{channel}) {
+ $sth->execute('%'.$search.'%',$params->{channel},'%'.$search.'%',$params->{channel})
+ or return error "Can't execute query: $sth->errstr.";
+ } else {
+ $sth->execute('%'.$search.'%','%'.$search.'%')
+ or return error "Can't execute query: $sth->errstr.";
+ }
+ my $result = $sth->fetchall_arrayref();
+ $console->table($result)
+ if(ref $console && $result);
+ }
+}
+
+1;
diff --git a/lib/XXV/MODULES/EVENTS.pm b/lib/XXV/MODULES/EVENTS.pm
new file mode 100644
index 0000000..23b94b6
--- /dev/null
+++ b/lib/XXV/MODULES/EVENTS.pm
@@ -0,0 +1,190 @@
+package XXV::MODULES::EVENTS;
+use strict;
+
+use Tools;
+use Locale::gettext;
+use File::Basename;
+use File::Find;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'EVENTS',
+ Prereq => {
+ 'Data::Dumper' => 'stringified perl data structures, suitable for both printing and eval',
+ },
+ Description => gettext(
+"This module manage the events for control and watch the xxv system.
+An additional Loghandler is installed and parse every Message. If
+a defined Event exists and match the keywords defined in
+Module->RegEvent->SearchForEvent then call the Loghandler 'callEvent'.
+This sub look in Module->RegEvent->Actions, and call this Routines.
+"),
+ Version => '0.92',
+ Date => '2007-01-28',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+# list => {
+# description => gettext('Display the event list'),
+# short => 'el',
+# callback => sub{ $obj->list(@_) },
+# Level => 'user',
+# },
+# eedit => {
+# description => gettext('Edit a event'),
+# short => 'ee',
+# callback => sub{ $obj->edit(@_) },
+# Level => 'user',
+# },
+# etoogle => {
+# description => gettext('Change a event on or off'),
+# short => 'eto',
+# callback => sub{ $obj->toogle(@_) },
+# Level => 'user',
+# },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ $self->{Trenner} = "\n#-- NextSub --#\n";
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ main::after(sub{
+ $obj->{EVENTS} = $obj->searchForEvents();
+ # This will add a callback for log events (ignore verbose)
+ $Tools::LOGCALLB = sub{
+ $obj->callEvent(@_);
+ };
+ return 1;
+ }, "EVENTS: Look for event entrys in modules ...", 3);
+
+ return 1;
+}
+
+# ------------------
+sub searchForEvents {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $mods = main::getModules();
+ my $events = {};
+ foreach my $modname (keys %$mods) {
+ if(exists $mods->{$modname}->{MOD}->{RegEvent}
+ and my $re = $mods->{$modname}->{MOD}->{RegEvent}
+ ) {
+ foreach my $rname (keys %$re) {
+ my $options = $re->{$rname};
+ $options->{Grp} = (split(/::/, $modname))[-1];
+ $events->{$rname} = $options;
+ }
+ }
+ }
+ return $events;
+}
+
+# ------------------
+sub callEvent {
+# ------------------
+ my $obj = shift || return error ('No Object in callEvent!' );
+ my $args = {
+ 'Mod' => shift,
+ 'Sub' => shift,
+ 'Msg' => shift,
+ };
+ return unless(exists $obj->{EVENTS});
+ return if($obj->{active} ne 'y');
+
+ foreach my $id (keys %{$obj->{EVENTS}}) {
+ my $entry = $obj->{EVENTS}->{$id};
+ my $bool = 0;
+
+ # Search for right fields
+ next unless(exists $entry->{SearchForEvent});
+ for my $sType (keys %{$entry->{SearchForEvent}}) {
+ my $sValue = $entry->{SearchForEvent}->{$sType};
+ $bool++ if(index($args->{$sType}, $sValue) > -1);
+ }
+ next unless($bool >= scalar keys %{$entry->{SearchForEvent}});
+
+ # Search for Matchtext
+ my $MatchVar = {};
+ if(exists $entry->{Match}) {
+ $bool = 0;
+ for my $mName (keys %{$entry->{Match}}) {
+ my $mRegex = $entry->{Match}->{$mName};
+ $MatchVar->{$mName} = $1
+ if($args->{Msg} =~ $mRegex);
+ $bool = 1 if($MatchVar->{$mName});
+ }
+ next unless($bool);
+ }
+
+ # Call the Actions
+ if(exists $entry->{Actions}) {
+ for my $action (@{$entry->{Actions}}) {
+ my $callback;
+ my $code = sprintf('$callback = %s;', $action);
+ eval($code);
+ if($@) {
+ error($@);
+ next;
+ }
+ my $erg = &$callback($MatchVar, $entry)
+ if(ref $callback eq 'CODE');
+ }
+ }
+ }
+
+ return 1;
+}
+1;
diff --git a/lib/XXV/MODULES/GRAB.pm b/lib/XXV/MODULES/GRAB.pm
new file mode 100644
index 0000000..d421203
--- /dev/null
+++ b/lib/XXV/MODULES/GRAB.pm
@@ -0,0 +1,290 @@
+package XXV::MODULES::GRAB;
+use strict;
+
+use Tools;
+use Locale::gettext;
+use File::Basename;
+use File::Find;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'GRAB',
+ Prereq => {
+ 'GD' => 'image manipulation routines',
+ 'Template' => 'Front-end module to the Template Toolkit ',
+ },
+ Description => gettext('This module grab a picture from livestream.'),
+ Version => '0.01',
+ Date => '06.09.2004',
+ Author => 'xpix',
+ Preferences => {
+ xsize => {
+ description => gettext('Image width'),
+ default => 320,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ ysize => {
+ description => gettext('Image height'),
+ default => 240,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ file => {
+ description => gettext('Location where the grabbed image file will be stored'),
+ default => '/tmp/live.jpg',
+ type => 'file',
+ },
+ imgtext => {
+ description => gettext('Text to display in the grabbed picture.'),
+ default => "[?- i = channel.split(' ') -?][[? i.shift ?]] [? i.join(' ') ?]",
+ type => 'string',
+ },
+ vpos => {
+ description => gettext('Vertical position of displayed text, in pixels.'),
+ default => 10,
+ type => 'integer',
+ },
+ font => {
+ description => gettext('True type font to draw image text.'),
+ default => 'VeraIt.ttf',
+ type => 'list',
+ choices => $obj->findttf,
+ },
+ imgfontsize => {
+ description => gettext('Font size to draw image text (only for ttf font!).'),
+ default => 10,
+ type => 'integer',
+ },
+ imgquality => {
+ description => gettext('Quality from image in percent.'),
+ default => 80,
+ type => 'integer',
+ },
+ },
+ Commands => {
+ grab => {
+ description => gettext('Grab a picture'),
+ short => 'gr',
+ callback => sub{ $obj->grab(@_) },
+ Level => 'user',
+ DenyClass => 'remote',
+ },
+ gdisplay => {
+ description => gettext('Display the picture'),
+ short => 'gd',
+ callback => sub{ $obj->display(@_) },
+ Level => 'user',
+ DenyClass => 'remote',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # create Template object
+ $self->{tt} = Template->new(
+ START_TAG => '\[\?', # Tagstyle
+ END_TAG => '\?\]', # Tagstyle
+ INTERPOLATE => 1, # expand "$var" in plain text
+ PRE_CHOMP => 1, # cleanup whitespace
+ EVAL_PERL => 1, # evaluate Perl code blocks
+ );
+
+ $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ main::after(sub{
+ $obj->{svdrp} = main::getModule('SVDRP');
+ unless($obj->{svdrp}) {
+ panic ("Can't get modul SVDRP");
+ return 0;
+ }
+ return 1;
+ }, "GRAB: Init module ...");
+ return 1;
+}
+
+# ------------------
+sub grab {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $file = $obj->{file};
+ my $erg;
+
+ if(main::getVdrVersion() >= 10338) {
+
+ # command for get inline data (JPEG BASE64 coded)
+ my $cmd = sprintf('grab - %d %d %d',
+ $obj->{imgquality},
+ $obj->{xsize},
+ $obj->{ysize},
+ );
+
+ my $data = $obj->{svdrp}->command($cmd);
+ my $uu = [ grep(/^216-/, @$data) ];
+ foreach (@{$uu}) { s/^216-//g; }
+
+ if(scalar @{$uu} <= 0) {
+ # None data with 216-, maybe svdrp message contain reason
+ $erg = $data;
+ } elsif(!open(F, ">$file")) {
+ # Open failed
+ $erg = sprintf("Can't write to file %s : %s",$file,$!);
+ } else {
+ # uudecode data to file
+ binmode(F);
+ foreach (@{$uu}) { print F MIME::Base64::decode_base64($_); }
+ close F;
+ }
+ } else {
+
+ if(-e $file) {
+ unlink($file) || error("Can't remove '%s' : %s",$file,$!);
+ }
+ # the command
+ my $cmd = sprintf('grab %s jpeg %d %d %d',
+ $obj->{file},
+ $obj->{imgquality},
+ $obj->{xsize},
+ $obj->{ysize},
+ );
+
+ $erg = $obj->{svdrp}->command($cmd);
+ }
+ # Make imgtext
+ $file = $obj->makeImgText($file, $obj->{imgtext})
+ if($obj->{imgtext} && -s $file);
+
+ $console->msg($erg, $obj->{svdrp}->err)
+ if(ref $console);
+ return $file;
+}
+
+# ------------------
+sub display {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my $file = $obj->grab();
+ if(-s $file) { # Datei existiert und hat eine Grösse von mehr als 0 Bytes
+ $console->{nocache} = 1;
+ return $console->image($file);
+ } else {
+ error("Can't locate file : $file, maybe grabbing was failed");
+ return 0;
+ }
+}
+
+# ------------------
+sub makeImgText {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $file = shift || $obj->{file} || return error ('No File to display');
+ my $text = shift || $obj->{imgtext} || return error ('No Text to display');
+
+ my $im;
+ if(int(${GD::VERSION}) >= 2.0) {
+ $im = GD::Image->newFromJpeg($file, 1) || return error("Can't read $file $!");
+ } else {
+ $im = GD::Image->newFromJpeg($file) || return error("Can't read $file $!");
+ }
+ my $color = $im->colorClosest(255,255,255);
+ my $shadow = $im->colorClosest(0,0,0);
+
+
+ # XXX: Hier sollten noch mehr Informationen dazu kommen
+ my $channeltext = main::getModule('REMOTE')->switch();
+ my $channelpos = (split(' ', $channeltext))[0];
+ my $vars = {
+ channel => $channeltext,
+ event => main::getModule('EPG')->NowOnChannel($watcher, $console, $channelpos),
+ };
+
+ my $output = '';
+ $obj->{tt}->process(\$text, $vars, \$output)
+ or return error($obj->{tt}->error());
+
+ my $font = sprintf("%s/%s",$obj->{paths}->{FONTPATH},$obj->{font});
+ if($obj->{paths}->{FONTPATH} and $obj->{font} and -r $font) {
+ $im->stringFT($shadow,$font,$obj->{imgfontsize},0,11,($obj->{vpos}-1),$output);
+ $im->stringFT($color,$font,$obj->{imgfontsize},0,10,($obj->{vpos}),$output);
+ } else {
+ # Schatten
+ $im->string(&gdGiantFont,11, ($obj->{vpos}-1),$output,$shadow);
+ # Text
+ $im->string(&gdGiantFont,10, ($obj->{vpos}),$output,$color);
+ }
+
+ my $img_data = $im->jpeg($obj->{imgquality});
+ my @f = split('\.', $file);
+ my $newfile = ($file =~ 'text' ? $file : sprintf('%s_text.%s', @f));
+ save_file($newfile, $img_data);
+ return $newfile;
+}
+
+# ------------------
+sub findttf
+# ------------------
+{
+ my $obj = shift || return error ('No Object!' );
+ my $found;
+ find({ wanted => sub{
+ if($File::Find::name =~ /\.ttf$/sig) {
+ my $l = basename($File::Find::name);
+ push(@{$found},[$l,$l]);
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $obj->{paths}->{FONTPATH}
+ );
+ error "Can't find useful font at : ", $obj->{paths}->{FONTPATH}
+ if(scalar $found == 0);
+ return $found;
+}
+
+1;
diff --git a/lib/XXV/MODULES/HTTPD.pm b/lib/XXV/MODULES/HTTPD.pm
new file mode 100644
index 0000000..3efbf83
--- /dev/null
+++ b/lib/XXV/MODULES/HTTPD.pm
@@ -0,0 +1,588 @@
+package XXV::MODULES::HTTPD;
+
+use Locale::gettext;
+use XXV::OUTPUT::Html;
+use XXV::OUTPUT::Ajax;
+use File::Basename;
+use File::Find;
+
+use Tools;
+
+$| = 1;
+
+use strict;
+
+my $mime = {
+ png => "image/png",
+ gif => "image/gif",
+ jpg => "image/jpeg",
+ css => "text/css",
+ ico => "image/x-icon",
+ js => "application/x-javascript",
+ m3u => "audio/x-mpegurl",
+ rss => "application/xhtml+xml",
+ avi => "video/avi",
+ mp4 => "video/mp4",
+ mpg => "video/x-mpeg",
+ mpeg => "video/x-mpeg",
+ mov => "video/quicktime",
+ wmv => "video/x-ms-wmv",
+ flv => "video/x-flv"
+};
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'HTTPD',
+ Prereq => {
+ 'IO::Socket::INET' => 'Object interface for AF_INET domain sockets ',
+ 'MIME::Base64' => 'Encoding and decoding of base64 strings',
+ 'CGI qw/:push -nph -no_xhtml -compile/'
+ => 'Simple Common Gateway Interface Class',
+ 'Compress::Zlib' => 'Interface to zlib compression library. ',
+ },
+ Description => gettext('This module is a multisession HTTPD server.'),
+ Version => '0.92',
+ Date => '2007-01-21',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ Clients => {
+ description => gettext('Maximum number from simultaneous connections to the same time'),
+ default => 5,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Port => {
+ description => gettext('Number of port to listen for http clients'),
+ default => 8080,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Interface => {
+ description => gettext('Local interface to bind service'),
+ default => '0.0.0.0',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ HtmlRoot => {
+ description => gettext('Used Skin'),
+ default => 'default',
+ type => 'list',
+ required => gettext('This is required!'),
+ choices => sub{ return $obj->findskins },
+ },
+ StartPage => {
+ description => gettext('First page, which is to be seen when logon'),
+ default => 'now',
+ type => 'list',
+ required => gettext('This is required!'),
+ choices => [
+ [ gettext('Schema'), 'schema'],
+ [ gettext('Running now'), 'now'],
+ [ gettext('Program guide'), 'program'],
+ [ gettext('Autotimer'), 'alist'],
+ [ gettext('Timers'), 'tlist'],
+ [ gettext('Recordings'), 'rlist'],
+ [ gettext('Music'), 'mlist'],
+ [ gettext('Remote'), 'remote'],
+ [ gettext('Teletext'), 'vtxpage'],
+ [ gettext('Status'), 'sa'],
+ ],
+ },
+ Debug => {
+ description => gettext('Dump additional debugging information, needed only for software development.'),
+ default => 'n',
+ type => 'confirm',
+ },
+ },
+ Commands => {
+ checkvalue => {
+ hidden => 'yes',
+ callback => sub{ $obj->checkvalue(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ $self->init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ # globals
+ my $channels;
+
+ $obj->{STATUS}->{'starttime'} = scalar localtime;
+
+ # make socket
+ my $socket = IO::Socket::INET->new(
+ Listen => $obj->{Clients},
+ LocalPort => $obj->{Port},
+ LocalAddr => $obj->{Interface},
+ Reuse => 1
+ ) or return error("Can't create Socket: $!");
+
+ # install an initial watcher
+ Event->io(
+ fd => $socket,
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ # accept client
+ my $client=$socket->accept;
+ panic "Can't connect http to new client." and return unless $client;
+ $client->autoflush;
+
+ # make "channel" number
+ my $channel=++$channels;
+
+ $obj->{STATUS}->{'connects'}++;
+
+ # install a communicator
+ Event->io(
+ fd => $client,
+ prio => -1, # -1 very hard ... 6 very low
+ poll => 'r',
+ cb => sub {
+ my $watcher = shift;
+ $obj->communicator($watcher);
+ }
+ );
+ },
+ ) if($obj->{active} eq 'y');
+
+ return 1;
+}
+
+sub communicator
+{
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!' );
+
+ # read new line and report it
+ my $handle=$watcher->w->fd;
+
+ my $data = $obj->parseRequest($handle,(defined $obj->{LOGOUT} && $obj->{LOGOUT} == 1 ));
+ unless($data) {
+ undef $obj->{LOGOUT};
+ $watcher->w->cancel;
+ $handle->close();
+ undef $watcher;
+ return 1;
+ }
+ undef $obj->{LOGOUT}
+ if(exists $obj->{LOGOUT});
+
+ my $ip = getip($handle);
+ my $htmlRootDir = sprintf('%s/%s', $obj->{paths}->{HTMLDIR}, $obj->{HtmlRoot});
+ my $htmlDefDir = sprintf('%s/%s', $obj->{paths}->{HTMLDIR}, 'default');
+
+ my $query = $data->{Query};
+ if($data->{Method} eq 'POST' && $data->{Post}) {
+ $query .= '&' if($query);
+ $query .= $data->{Post};
+ }
+ my $cgi = CGI->new( $query );
+
+ my $console;
+ if(my $outputtype = $cgi->param('ajax')) {
+ # Is a Ajax Request
+ $console = XXV::OUTPUT::Ajax->new(
+ -handle => $handle,
+ -cgi => $cgi,
+ -browser=> $data,
+ -output => $outputtype,
+ -debug => ($obj->{Debug} eq 'y' ? 1 : 0),
+
+ );
+ } else {
+ # Is a Html Request
+ $console = XXV::OUTPUT::Html->new(
+ -handle => $handle,
+ -dbh => $obj->{dbh},
+ -htmdir => $htmlRootDir,
+ -htmdef => $htmlDefDir,
+ -cgi => $cgi,
+ -mime => $mime,
+ -browser=> $data,
+ -paths => $obj->{paths},
+ -start => $obj->{StartPage},
+ -debug => ($obj->{Debug} eq 'y' ? 1 : 0),
+ );
+ }
+
+ my $userMod = main::getModule('USER');
+ if(ref $userMod and $userMod->{active} eq 'y') {
+ $console->{USER} = $userMod->check($handle, $data->{username}, $data->{password});
+ $console->login(gettext('You have no permissions to this system!'))
+ unless(exists $console->{USER}->{Level});
+ }
+
+ if(ref $userMod and
+ ($userMod->{active} ne 'y'
+ or exists $console->{USER}->{Level})) {
+
+ $console->{call} = 'nothing';
+ if(($data->{Request} eq '/' or $data->{Request} =~ /\.html$/) and not $data->{Query}) {
+ # Send the first page (index.html)
+ my $page = $data->{Request};
+ if($page eq '/') {
+ if(-r sprintf('%s/index.tmpl', $htmlRootDir)) {
+ $console->index;
+ } else {
+ $console->datei(sprintf('%s/index.html', $htmlRootDir));
+ }
+ } else {
+ $console->datei(sprintf('%s%s', $htmlRootDir, $page));
+ }
+ } elsif(my $typ = $mime->{lc((split('\.', $data->{Request}))[-1])}) {
+ # Send multimedia files (this must registered in $mime!)
+ if($data->{Request} =~ /epgimages\//) {
+ my $epgMod = main::getModule('EPG');
+ $data->{Request} =~ s/.*epgimages\//$epgMod->{epgimages}\//;
+ $console->datei($data->{Request}, $typ);
+ } elsif($data->{Request} =~ /previewimages\//) {
+ my $recMod = main::getModule('RECORDS');
+ $data->{Request} =~ s/.*previewimages\//$recMod->{previewimages}\//;
+ $console->datei($data->{Request}, $typ);
+ } elsif($data->{Request} =~ /coverimages\//) {
+ my $musicMod = main::getModule('MUSIC');
+ $data->{Request} =~ s/.*coverimages\//$musicMod->{coverimages}\//;
+ $console->datei($data->{Request}, $typ);
+ } elsif($data->{Request} =~ /vtximages\//) {
+ my $vtxMod = main::getModule('VTX');
+ $data->{Request} =~ s/.*vtximages\//$obj->{paths}->{VTXPATH}\//;
+ $console->datei($data->{Request}, $typ);
+ } elsif($data->{Request} =~ /tempimages\//) {
+ my $tmp = $userMod->userTmp;
+ $data->{Request} =~ s/.*tempimages\//$tmp\//;
+ $console->datei($data->{Request}, $typ);
+ } else {
+ $console->datei(sprintf('%s%s', $htmlRootDir, $data->{Request}), $typ);
+ }
+ } elsif( $cgi->param('binary') ) {
+ # Send multimedia files (if param binary)
+ $obj->handleInput($watcher, $console, $cgi);
+ } else {
+ $obj->handleInput($watcher, $console, $cgi);
+ $console->footer() unless($console->typ eq 'AJAX' or $console->{noFooter});
+ }
+
+ }
+ $console->printout();
+
+ # make entry more readable
+ $data->{Query} =~ s/%([a-f0-9][a-f0-9])/pack("C", hex($1))/ieg
+ if($data->{Query});
+ $data->{Referer} =~ s/%([a-f0-9][a-f0-9])/pack("C", hex($1))/ieg
+ if($data->{Referer});
+ # Log like Apache Format ip, resolved hostname, user, request, status, bytes, referer, useragent
+ lg sprintf('%s - %s "%s%s" %s %s "%s" "%s"',
+ $ip,
+ $data->{username} ? $data->{username} : "-",
+ $data->{Request} ? $data->{Request} : "",
+ $data->{Query} ? "?" . $data->{Query} : "",
+ $console->{'header'},
+ $console->{'sendbytes'},
+ $data->{Referer} ? $data->{Referer} : "-",
+ "-" #$data->{http_useragent} ? $data->{http_useragent} : ""
+ );
+
+ $obj->{STATUS}->{'sendbytes'} += $console->{'sendbytes'};
+ $watcher->w->cancel;
+ undef $watcher;
+}
+
+# ------------------
+sub _readline {
+# ------------------
+ my $fh = $_[0];
+ my $c='';
+ my $line='';
+ my $eof=0;
+
+ while ($c ne "\n" && ! $eof) {
+ if (sysread($fh, $c, 1) > 0) {
+ $line = $line . $c;
+ } else {
+ $eof=1;
+ }
+ }
+ return $line;
+}
+# ------------------
+sub parseRequest {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $socket = shift || return error ('No Handle!' );
+ my $logout = shift || 0;
+
+ binmode $socket;
+ my $data = {};
+ my $line;
+ while (defined($line = &_readline($socket))) {
+ if(!$line || $line =~ /^\r\n$/) {
+ last;
+ } elsif(!$data->{Method} && $line =~ /^(\w+) (\/[\w\.\/\-\:\%]*)([\?[\w=&\.\+\%-\:\!]*]*)[\#\d ]+HTTP\/1.\d/) {
+ ($data->{Method}, $data->{Request}, $data->{Query}) = ($1, $2, $3 ? substr($3, 1, length($3)) : undef);
+ } elsif($line =~ /Referer: (.*)/) {
+ $data->{Referer} = $1;
+ $data->{Referer} =~ s/(\r|\n)//g;
+ } elsif($line =~ /Host: (.*)/) {
+ $data->{HOST} = $1;
+ $data->{HOST} =~ s/(\r|\n)//g;
+ } elsif($line =~ /Authorization: basic (.*)/i and not $logout) {
+ ($data->{username}, $data->{password}) = split(":", MIME::Base64::decode_base64($1), 2);
+ } elsif($line =~ /User-Agent: (.*)/i) {
+ $data->{http_useragent} = $1;
+ $data->{http_useragent} =~ s/(\r|\n)//g;
+ } elsif($line =~ /Accept-Encoding:.+?gzip/i) {
+ $data->{accept_gzip} = 1;
+ } elsif($line =~ /If-None-Match: (\S+)/i) {
+ $data->{Match} = $1;
+ } elsif($line =~ /Cookie: (\S+)=(\S+)/i) {
+ $data->{$1} = $2;
+ } elsif($line =~ /Content-Type: (\S+)/i) {
+ $data->{ContentType} = $1;
+ } elsif($line =~ /Content-Length: (\S+)/i) {
+ $data->{ContentLength} = $1;
+ } else {
+ #dumper($line);
+ }
+ }
+
+ $data->{Request} =~ s/%([a-f0-9][a-f0-9])/pack("C", hex($1))/ieg
+ if($data->{Request});
+ if($data->{Method} eq 'GET') {
+ #dumper($data);
+ return $data;
+ } elsif($data->{Method} eq 'POST') {
+ if(int($data->{ContentLength})>0) {
+ my $post;
+ my $bytes = sysread($socket,$post,$data->{ContentLength});
+ $data->{Post} = $post
+ if($bytes && $data->{ContentLength} == $bytes);
+ }
+ #dumper($data);
+ return $data;
+ } else {
+ return undef;
+ }
+
+}
+
+# ------------------
+sub handleInput {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $cgi = shift || return error ('No CGI Object');
+
+ my $ucmd = $cgi->param('cmd') || '<undef>';
+ my $udata = $cgi->param('data') || '';
+
+ # Set the referer, if come a form with a error
+ # then patch the referer
+ $console->{browser}->{Referer} = $cgi->param('referer')
+ if($cgi->param('referer'));
+
+ # Test on result set (user has save) and
+ # get the DataVars in a special Hash
+ my $result;
+ foreach my $name ($cgi->param) {
+ if(my ($n) = $name =~ /^__(.+)/sig) {
+ my @vals = $cgi->param($name);
+ if(scalar @vals > 1) {
+ @{$result->{$n}} = @vals;
+ } else {
+ $result->{$n} = shift @vals;
+ }
+ }
+ }
+
+ # Test the command on exists, permissions and so on
+ my $u = main::getModule('USER');
+ my ($cmdobj, $cmdname, $shorterr, $err) = $u->checkCommand($console, $ucmd);
+ $console->{call} = $cmdname;
+ if($cmdobj and not $shorterr) {
+ $console->{CMDSTAT} = $cmdobj->{callback}($watcher, $console, $udata, $result );
+ } elsif($shorterr eq 'noperm' or $shorterr eq 'noactive') {
+ $console->status403($err);
+ $console->{CMDSTAT} = undef;
+ } else {
+ $obj->usage($watcher, $console, undef, $err);
+ $console->{CMDSTAT} = undef;
+ }
+}
+
+# ------------------
+sub usage {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return main::getModule('TELNET')->usage(@_);
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift || return;
+ my $lastReportTime = shift || 0;
+
+ return {
+ message => sprintf(gettext('Traffic on HTTPD Socket since %s: send: %d kbytes - received: %d kbytes - connects: %d'),
+ $obj->{STATUS}->{'starttime'}, convert($obj->{STATUS}->{'sendbytes'}), convert($obj->{STATUS}->{'readbytes'}), $obj->{STATUS}->{'connects'} ),
+ };
+
+}
+
+# ------------------
+sub findskins
+# ------------------
+{
+ my $obj = shift || return error ('No Object!' );
+ my $found;
+ find({ wanted => sub{
+ if(-d $File::Find::name
+ and ( -e $File::Find::name.'/index.tmpl'
+ or -e $File::Find::name.'/index.html')
+ ) {
+ my $l = basename($File::Find::name);
+ push(@{$found},[$l,$l]);
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $obj->{paths}->{HTMLDIR}
+ );
+ error "Can't find useful HTML Skin at : $obj->{paths}->{HTMLDIR}"
+ if(scalar $found == 0);
+ return sort { lc($a->[0]) cmp lc($b->[0]) } @{$found};
+}
+
+# ------ unzip ------------
+# Name: unzip
+# Desc: Uncompress Files in gz format
+# Usag: my $res = $obj->unzip(file.gz);
+# Test: my $res = $obj->unzip('t/abc.gz');
+# return 1 if(load_file($res) eq 'abc');
+# ------ unzip ------------
+sub unzip {
+ my $obj = shift || return error ('No Object');
+ my $file = shift || return error ('No File');
+
+ my $gz = gzopen($file, "rb")
+ or return $obj->msg(undef, sprintf(gettext("can't open file '%s' : %s"), $file, &gzerror ));
+
+ my $text;
+ while($gz->gzread(my $buffer) > 0) {
+ $text .= $buffer; # nothing
+ }
+
+ $gz->gzclose();
+
+ my $tmpfile = sprintf('%s/gz_%d.tmp', main::getModule('USER')->userTmp, time);
+
+ return save_file($tmpfile, $text);
+}
+
+
+# ------------------
+# Callback for ajax, to check for right values in HTML Widget
+# supported :
+# isdir:/tmp
+# isfile:/bla/foobar
+# getip:localhost
+sub checkvalue {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift || return error ('No Data!' );
+
+ my @query = split(':',$data);
+ my $check = $query[0];
+ shift @query;
+ my $value = join(':',@query);
+
+ my $erg;
+ # e.g. isdir:/tmp
+ if($check eq "isdir") {
+ if(-d $value) {
+ $erg = "SUCCESS: directory found.";
+ } else {
+ $erg = "ERROR: directory not found.";
+ }
+ # e.g. isfile:/bla/foobar
+ } elsif($check eq "isfile") {
+ if(-r $value) {
+ $erg = "SUCCESS: file found.";
+ } else {
+ $erg = "ERROR: file not found.";
+ }
+ # e.g. getip:localhost
+ } elsif($check eq "getip") {
+ my $aton = inet_aton($value);
+ if($aton) {
+ $erg = inet_ntoa($aton);
+ } else {
+ $erg = "ERROR: host does not exist.";
+ }
+ # Unknown query
+ } else {
+ $erg = "ERROR: Query : " . $check . " not supported.";
+ }
+
+ return $console->msg($erg)
+ if(ref $console);
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/INTERFACE.pm b/lib/XXV/MODULES/INTERFACE.pm
new file mode 100644
index 0000000..b468dac
--- /dev/null
+++ b/lib/XXV/MODULES/INTERFACE.pm
@@ -0,0 +1,179 @@
+package XXV::MODULES::INTERFACE;
+
+use Locale::gettext;
+use XXV::OUTPUT::Dump;
+use Tools;
+
+
+use strict;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'INTERFACE',
+ Prereq => {
+ 'IO::Socket::INET' => 'Object interface for AF_INET domain sockets ',
+ "SOAP::Lite" => 'Client and server side SOAP implementation',
+ "SOAP::Transport::HTTP" => 'Server/Client side HTTP support for SOAP::Lite',
+ "SOAP::Transport::HTTP::Event" => 'Server/Client side HTTP support for SOAP::Lite',
+ },
+ Description => gettext('This module is a multichannel soap server for second party software.'),
+ Version => '0.01',
+ Date => '06.09.2004',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ LocalPort => {
+ description => gettext('Number of port to listen for soap clients'),
+ default => 8082,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Interface => {
+ description => gettext('Local interface to bind service'),
+ default => '0.0.0.0',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_} || '';
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ $self->init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ if($obj->{active} eq 'y') {
+
+ # Install the SOAP Server
+ my $daemon = SOAP::Transport::HTTP::Event
+ -> new (
+ LocalAddr => $obj->{Interface},
+ LocalPort => $obj->{LocalPort},
+ )
+ -> dispatch_to('SOAPService');
+
+ debug("Install the SOAP server at %s", $daemon->url);
+ my ($sock, $httpd) = $daemon->getDaemon();
+
+ Event->io(
+ fd => $sock,
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ $daemon->handle($sock, $httpd);
+ }
+ );
+ }
+
+ return 1;
+
+}
+
+
+1;
+
+BEGIN {
+
+ package SOAPService;
+
+ use vars qw(@ISA);
+# @ISA = qw(Exporter SOAP::Server::Parameters);
+ use SOAP::Lite;
+ use Tools;
+
+ # ------------------
+ # Name: getCommand
+ # Descr: Call every commands.
+ # Usage: my $data = $obj->getCommand($cmd, [$data, $params]);
+ # ------------------
+ sub getCommand {
+ my $obj = shift || return error ('No Object!' );
+ my $cmd = shift || return error ('No Command!' );
+ my $data = shift;
+
+ my $ret = $obj->handleInput($cmd, $data);
+ return $ret;
+ }
+
+ # ------------------
+ sub handleInput {
+ # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $ucmd = shift || return error ('No Command');
+ my $udata = shift;
+
+ my $watcher = $obj;
+
+ my $console = XXV::OUTPUT::Dump->new();
+ $console->{USER}->{Name} = undef;
+ $console->{USER}->{Level} = 'admin';
+ $console->{USER}->{value} = 10;
+
+ # Test the command on exists, permissions and so on
+ my $u = main::getModule('USER');
+ my ($cmdobj, $cmdname, $shorterr, $err) = $u->checkCommand($console, $ucmd);
+ $console->{call} = $cmdname;
+ if($cmdobj and not $shorterr) {
+ my @ret = $cmdobj->{callback}($watcher, $console, $udata);
+ return \@ret;
+ } elsif($shorterr eq 'noperm' or $shorterr eq 'noactive') {
+ return $console->err($err);
+ } else {
+ return $obj->usage($watcher, $console, undef, $err);
+ }
+ }
+
+ # ------------------
+ sub usage {
+ # ------------------
+ my $obj = shift || return error ('No Object!' );
+ return main::getModule('TELNET')->usage(@_);
+ }
+
+} # End BEGIN
+
+1;
diff --git a/lib/XXV/MODULES/LOGREAD.pm b/lib/XXV/MODULES/LOGREAD.pm
new file mode 100644
index 0000000..88a0a7c
--- /dev/null
+++ b/lib/XXV/MODULES/LOGREAD.pm
@@ -0,0 +1,221 @@
+package XXV::MODULES::LOGREAD;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+use XXV::OUTPUT::HTML::PUSH;
+
+$|++;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'LOGREAD',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module read the xxv log file and show it on console.'),
+ Version => '0.01',
+ Date => '17.09.2005',
+ Author => 'xpix',
+ Level => 'admin',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ tail => {
+ description => sprintf(gettext("Path of command '%s'"),'tail'),
+ default => '/usr/bin/tail',
+ type => 'file',
+ required => gettext('This is required!'),
+ },
+ rows => {
+ description => gettext('How much lines to display?'),
+ default => '100',
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ syslog => {
+ description => gettext('Path of syslog file?'),
+ default => '/var/log/syslog',
+ type => 'file',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ logger => {
+ description => gettext("Display the last log entries"),
+ short => 'lg',
+ callback => sub{ $obj->logger(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ return $self;
+}
+
+# ------------------
+sub logger {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $logname = shift || 'standard';
+ my $params = shift || {};
+
+ $obj->{logfiles} = {
+ main => {
+ # Path to logfile
+ logfile => $obj->{paths}->{LOGFILE},
+ #24 (14870) [22:29:08 09/22/05] CHANNELS: Read and register Channels ...
+ # Regular expression for every loglines
+ regex => qr/^(\d+)\s+\((\d+)\)\s+\[(\d+\-\d+\-\d+ \d+\:\d+\:\d+)\]\s+(.+?)$/s,
+ # Fields List for describe the rows
+ fields => [qw/Nr Typ Time Message/],
+ # Callback for coloring rows
+ display=> sub{
+ my $typ = $_[0][1];
+ return 'black' if($typ < 200);
+ return 'green' if($typ < 300);
+ return 'blue' if($typ < 400);
+ return 'brown' if($typ < 500);
+ return 'red' if($typ >= 500);
+ },
+ # Maximum letters for truncate in template
+ maxlet=> 50,
+ },
+ syslog => {
+ logfile => $obj->{syslog},
+ #Sep 23 00:35:01 vdr /USR/SBIN/CRON[16971]: (root) CMD (/usr/bin/weatherng.sh)
+ regex => qr/^(.+?)\s+(\d+)\s+(\d+\:\d+\:\d+)\s+(.+?)\s+(.+)/s,
+ fields => [qw/Month MDay Time Prg Message/],
+ display=> sub{
+ my $txt = $_[0][-1];
+ return 'red' if($txt =~ /ERROR/si);
+ return 'blue' if($txt =~ /WARNING/si);
+ return 'green' if($txt =~ /INFO/si);
+ return 'black';
+ },
+ maxlet=> 80,
+ },
+ };
+
+ if( ! ref $obj->{logfiles}->{$logname}) {
+ return $console->err(sprintf("The log with the name %s does not exist! Please use '%s'!", $logname, join("' or '", keys %{$obj->{logfiles}})));
+ }
+
+ my $logfile = $obj->{logfiles}->{$logname}->{logfile};
+ my @out = $obj->tail($logfile);
+
+ return $console->msg(undef, sprintf(gettext("Can't read log file %s!"), $logfile))
+ unless(scalar @out);
+
+ my $output = $obj->parseLogOutput($obj->{logfiles}->{$logname}, \@out);
+
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks)
+ = stat($logfile);
+
+ $console->table($output, {
+ type => $logname,
+ logfile => $logfile,
+ Size => convert($size),
+ LastChanged => scalar localtime($mtime),
+ full => $params->{full},
+ color => $obj->{logfiles}->{$logname}->{display},
+ maxlet => $obj->{logfiles}->{$logname}->{maxlet},
+ });
+ return 1;
+}
+
+# ------------------
+sub tail {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $logfile = shift || return error ('No Logfile!' );
+ my $rows = shift || $obj->{rows};
+
+ my $cmd = sprintf('%s --lines=%d %s', $obj->{tail}, $rows, $logfile);
+ my @out = (`$cmd`);
+ return @out;
+}
+
+
+# ------------------
+sub parseLogOutput {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $log = shift || return error ('No Prefs for logfile!' );
+ my $out = shift || return;
+
+ my $regex = $log->{regex};
+ $obj->{logbuf} = undef;
+
+ my $ret = [];
+ foreach my $line (@$out) {
+ if(my @d = $line =~ $regex) {
+ $obj->parseData($ret, \@d) if($d[0]);
+ } else {
+ $obj->parseData($ret, $line);
+ }
+ }
+ my @r = reverse @$ret;
+
+ unshift(@r, $log->{fields});
+ return \@r;
+}
+
+# ------------------
+sub parseData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $ret = shift || return error('No Referenced Array');
+ my $data = shift || 0;
+
+
+ if(ref $data eq 'ARRAY') { # Set Data
+ $data->[-1] .= $obj->{logbuf}
+ if($obj->{logbuf});
+ push(@$ret, $data);
+ $obj->{logbuf} = undef;
+ } elsif($data) { # Message (last row, last item) .+ $line
+ $obj->{logbuf} .= $data;
+ }
+ return $ret;
+}
+
+1;
diff --git a/lib/XXV/MODULES/MEDIALIB.pm b/lib/XXV/MODULES/MEDIALIB.pm
new file mode 100644
index 0000000..5cf20b6
--- /dev/null
+++ b/lib/XXV/MODULES/MEDIALIB.pm
@@ -0,0 +1,1328 @@
+package XXV::MODULES::MEDIALIB;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+use Data::Dumper;
+use File::Path;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $args = {
+ Name => 'MEDIALIB',
+ Prereq => {
+ 'Digest::MD5' => 'Perl interface to the MD5 Algorithm',
+ 'LWP::Simple' => 'simple procedural interface to LWP',
+ 'LWP::UserAgent' => 'simple procedural interface to LWP',
+ },
+ Description => gettext('This module manages media like DVDs, VCD, etc.'),
+ Version => '0.04',
+ Date => '2007-03-11',
+ Author => 'poetter',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ usecache => {
+ description => gettext('Cache images'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ libraryimagedir => {
+ description => gettext('Location of coverimages.'),
+ default => '/var/cache/xxv/libraryimages',
+ type => 'dir',
+ required => gettext("This is required!"),
+ },
+ listcols => {
+ description => gettext('Number of columns in listview'),
+ default => 4,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ actorcols => {
+ description => gettext('Number of columns of actors in detailview'),
+ default => 6,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ deflanguage => {
+ description => gettext('Default media language'),
+ default => 'german',
+ type => 'string',
+ },
+ defmediatype => {
+ description => gettext('Default mediatype'),
+ default => 1,
+ type => 'list',
+ choices => sub {
+ my $erg = $obj->_get_mediatype_as_array();
+ map { my $x = $_->[1]; $_->[1] = $_->[0]; $_->[0] = $x; } @$erg;
+ return @$erg;
+ },
+ },
+ defrange => {
+ description => gettext('Default range'),
+ default => 'ABC',
+ type => 'list',
+ choices => sub {
+ my $erg = $obj->_get_ranges_as_array();
+ map { my $x = $_->[1]; $_->[1] = $_->[0]; $_->[0] = $x; } @$erg;
+ return @$erg;
+ },
+ }, },
+ Commands => {
+ mllist => {
+ description => gettext('List medias'),
+ short => 'mll',
+ callback => sub{ $obj->listMedia(@_) },
+ DenyClass => 'media',
+ },
+ mldisplay => {
+ description => gettext('Display media'),
+ short => 'mld',
+ callback => sub{ $obj->displayMedia(@_) },
+ DenyClass => 'media',
+ },
+ mlnew => {
+ description => gettext('Create new media'),
+ short => 'mln',
+ callback => sub{ $obj->createMedia(@_) },
+ DenyClass => 'media',
+ },
+ mledit => {
+ description => gettext('Edit media'),
+ short => 'mle',
+ callback => sub{ $obj->editMedia(@_) },
+ DenyClass => 'media',
+ },
+ mlcopy => {
+ description => gettext('Copy media'),
+ short => 'mlc',
+ callback => sub{ $obj->copyMedia(@_) },
+ DenyClass => 'media',
+ },
+ mldelete => {
+ description => gettext('Delete media'),
+ short => 'mlt',
+ callback => sub{ $obj->deleteMedia(@_) },
+ DenyClass => 'media',
+ },
+ mlresearch => {
+ description => gettext('Research media'),
+ short => 'mlr',
+ callback => sub{ $obj->researchMedia(@_) },
+ DenyClass => 'media',
+ },
+ mlcache => {
+ description => gettext('Get image from media cache'),
+ short => 'mlca',
+ callback => sub{ $obj->mediacache(@_) },
+ DenyClass => 'media',
+ },
+ mlsave => {
+ description => gettext('Save media'),
+ short => 'mls',
+ callback => sub{ $obj->saveMedia(@_) },
+ DenyClass => 'media',
+ },
+ mlimport => {
+ description => gettext('Import media'),
+ short => 'mli',
+ callback => sub{ $obj->importMedia(@_) },
+ DenyClass => 'media',
+ },
+ mlsearch => {
+ description => gettext('Search media'),
+ short => 'mlse',
+ callback => sub{ $obj->searchMedia(@_) },
+ DenyClass => 'media',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0, panic("Session to database is'nt connected")
+ unless($obj->{dbh});
+
+ # don't remove old table, if updated rows => warn only
+ tableUpdated($obj->{dbh},'MEDIALIB_ACTORS',4,0);
+ tableUpdated($obj->{dbh},'MEDIALIB_VIDEODATA',33,0);
+ tableUpdated($obj->{dbh},'MEDIALIB_VIDEOGENRE',2,0);
+
+ # Look for tables or create this tables
+ my $version = main::getVersion;
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS MEDIALIB_ACTORS (
+ name varchar(255) NOT NULL default '',
+ actorid varchar(15) NOT NULL default '',
+ imgurl varchar(255) NOT NULL default '',
+ checked timestamp(14) NOT NULL,
+ PRIMARY KEY (name)
+ ) COMMENT = '$version'
+ |);
+
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS MEDIALIB_VIDEODATA (
+ id int(10) unsigned NOT NULL auto_increment,
+ md5 varchar(32) default NULL,
+ title varchar(255) default NULL,
+ subtitle varchar(255) default NULL,
+ language varchar(255) default NULL,
+ diskid varchar(15) default NULL,
+ comment varchar(255) default NULL,
+ disklabel varchar(32) default NULL,
+ imdbID varchar(15) default NULL,
+ year year(4) default NULL,
+ imgurl varchar(255) default NULL,
+ director varchar(255) default NULL,
+ actors text,
+ runtime int(10) unsigned default NULL,
+ country varchar(255) default NULL,
+ plot text,
+ filename varchar(255) default NULL,
+ filesize int(16) unsigned default NULL,
+ filedate datetime default NULL,
+ audio_codec varchar(255) default NULL,
+ video_codec varchar(255) default NULL,
+ video_width int(10) unsigned default NULL,
+ video_height int(10) unsigned default NULL,
+ istv tinyint(1) unsigned NOT NULL default '0',
+ lastupdate timestamp(14) NOT NULL,
+ seen tinyint(1) unsigned NOT NULL default '0',
+ mediatype int(10) unsigned NOT NULL default '0',
+ custom1 varchar(255) default NULL,
+ custom2 varchar(255) default NULL,
+ custom3 varchar(255) default NULL,
+ custom4 varchar(255) default NULL,
+ created datetime default NULL,
+ owner_id int(11) NOT NULL default '0',
+ PRIMARY KEY (id),
+ KEY seen (seen),
+ KEY title_idx (title),
+ KEY diskid_idx (diskid),
+ KEY mediatype (mediatype,istv),
+ FULLTEXT KEY actors_idx (actors),
+ FULLTEXT KEY comment (comment)
+ ) COMMENT = '$version'
+ |);
+
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS MEDIALIB_VIDEOGENRE (
+ video_id int(10) unsigned NOT NULL default '0',
+ genre_id int(10) unsigned NOT NULL default '0',
+ PRIMARY KEY (video_id,genre_id)
+ ) COMMENT = '$version'
+ |);
+
+ unless(-d $obj->{libraryimagedir}) {
+ mkpath($obj->{libraryimagedir}) or error "Can't mkpath $obj->{libraryimagedir} : $!";
+ lg sprintf('mkdir path "%s" ',
+ $obj->{coverimages},
+ );
+ }
+
+ 1;
+}
+
+# ------------------
+# Name: status
+# Descr: Standardsubroutine to report statistical data for Report Plugin.
+# Usage: my $report = $obj->status([$watcher, $console]);
+# ------------------
+sub status {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ my $sql = qq|
+SELECT
+ COUNT(id)
+FROM
+ MEDIALIB_VIDEODATA
+|;
+
+ my $erg = $obj->{dbh}->selectrow_arrayref($sql);
+ return {
+ message => sprintf(gettext('Media Library has stored %d medias'), $erg->[0]),
+ };
+
+}
+
+# ------------------
+sub researchMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || {};
+
+
+ if(ref $params eq 'HASH') {
+
+ #print Dumper( $params );
+ my $medias;
+ if ( $params->{source} eq 'dvdpalace' ) {
+
+ eval "use MediaLibParser::DVDPalace";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+
+ my $mlp = MediaLibParser::DVDPalace->new(
+ 'lookup_result' => $params->{title},
+ 'start_result' => defined $params->{start} ? $params->{start} : 0,
+ );
+
+ ( $medias, $params->{hitcount} ) = $mlp->result;
+ }
+ return $console->table($medias, $params);
+ }
+
+}
+
+# ------------------
+sub createMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || 0;
+
+ $obj->editMedia($watcher, $console, 0, $params);
+}
+
+# ------------------
+sub copyMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || 0;
+
+ my $sql = qq|
+SELECT
+ *
+FROM
+ MEDIALIB_VIDEODATA
+WHERE
+ id = ?
+|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($id)
+ or return $console->err(sprintf(gettext("Media with ID '%s' does not exist in the database!"),$id));
+ my $erg = $sth->fetchrow_hashref();
+ delete $erg->{id};
+ $erg->{range} = $params->{range} if($params && $params->{range});
+
+ $obj->editMedia($watcher, $console, 0, $erg);
+}
+
+# ------------------
+sub importMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || {};
+
+ #print Dumper($id, $params);
+ $id = delete $params->{id} if defined $params->{id};
+ my $range = delete $params->{range} if defined $params->{range};
+
+ if ( ref $params && defined $params->{import} ) {
+
+ if ( $params->{source} eq 'dvdpalace' ) {
+ eval "use MediaLibParser::DVDPalace";
+ return panic("\nCan not load Module: MediaLibParser::DVDPalace") if($@);
+
+ my $mlp = MediaLibParser::DVDPalace->new(
+ 'url_media' => $params->{import},
+ );
+
+ $params = $mlp->media;
+ }
+
+ $params->{id} = $id;
+
+ #print Dumper('$params', $params);
+ if ( $params->{genres} ) {
+ my $gen_hash_1 = $obj->_get_videogenres_as_hash;
+ my $gen_hash_2 = {
+ 'Komödie' => 'Comedy',
+ 'Musik' => 'Music',
+ 'Musikfilm' => 'Music',
+ 'Kriegsfilm' => 'War',
+ 'Abenteuer' => 'Adventure',
+ 'Kinderfilm' => 'Childs',
+ 'Science Fiction' => 'Sci-Fi',
+
+ # noch mit Genres auffuellen so wie sie auftauchen.
+ };
+ #print Dumper('$gen_hash_1', $gen_hash_1);
+ #print Dumper('$gen_hash_2', $gen_hash_2);
+ my $ret;
+ foreach my $key ( @{$params->{genres}} ) {
+ #print Dumper('$key', $key);
+ if( defined $gen_hash_1->{$key} ) {
+ push (@$ret, $gen_hash_1->{$key}->{id});
+ } elsif ( defined $gen_hash_2->{$key} ) {
+ foreach my $name ( keys %$gen_hash_1 ) {
+ push (@$ret, $gen_hash_1->{$name}->{id}) if $gen_hash_2->{$key} eq $gen_hash_1->{$name}->{name};
+ }
+ } elsif ( $key =~ /TV-Serie/i ) {
+ $params->{istv} = 1;
+ } else {
+ debug ('Unknown genre: '. $key);
+ }
+ }
+ #print Dumper('$ret', $ret);
+ $params->{genres} = $ret;
+ }
+
+ foreach my $key ( keys %$params ) {
+ $params->{$key} =~ s/\r\n/\n/g;
+ }
+ }
+
+ $params->{mediatype} = $obj->{defmediatype} unless defined $params->{mediatype} && $params->{mediatype} != 0;
+ $params->{language} = $obj->{deflanguage} unless defined $params->{language} && $params->{language} ne '';
+
+ #print Dumper($params);
+ $console->table({},
+ {
+ %$params,
+ mediatypes => $obj->_get_mediatype_as_array,
+ allgenres => $obj->_get_videogenres_as_array,
+ range => $range,
+ ranges => $obj->_get_ranges_as_array,
+ }
+ );
+}
+
+# ------------------
+sub searchMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || {};
+ #print Dumper($params);
+ my $erg = [];
+ my $setcount;
+
+ if ( defined $params->{'search'} ) {
+
+ my @wheres;
+ my ($where, $join);
+ my $like = defined $params->{'searchitem'} && $params->{'searchitem'} ne '' ? '%%'.$params->{'searchitem'}.'%%' : '%%';
+ if ( ( defined $params->{'searchitem'} && $params->{'searchitem'} ne '' && defined $params->{'selfields'} ) ||
+ defined $params->{'genres'} ) {
+ $where .= "WHERE ";
+ }
+ if ( defined $params->{'searchitem'} && $params->{'searchitem'} ne '' && defined $params->{'searchitem'} ) {
+ if ( ref $params->{'selfields'} ) {
+ foreach my $field ( @{$params->{'selfields'}} ) {
+ if ( $field eq "mediatype" ) {
+ push @wheres, $field. "=". $obj->_get_mediatype_idbyname($params->{'searchitem'});
+ } else {
+ push @wheres, $field. " LIKE ". '"'. $like. '"';
+ }
+ }
+ $where .= join " OR ", @wheres;
+ } else {
+ if ( $params->{'selfields'} eq "mediatype" ) {
+ $where .= $params->{'selfields'}. "=". $obj->_get_mediatype_idbyname($params->{'searchitem'});
+ } else {
+ $where .= $params->{'selfields'}. " LIKE ". '"'. $like. '"';
+ }
+ }
+ }
+ if ( defined $params->{'searchitem'} && $params->{'searchitem'} ne '' && defined $params->{'selfields'} && defined $params->{'genres'} ) {
+ $where .= " AND ";
+ }
+ if ( defined $params->{'genres'} ) {
+ if ( ref $params->{'genres'} ) {
+ $where .= 'genre_id IN ('. join (",", @{ $params->{'genres'} } ). ')';
+ } else {
+ $where .= 'genre_id='. $params->{'genres'};
+ }
+ $join = qq|
+LEFT JOIN
+ MEDIALIB_VIDEOGENRE
+ON
+ id=video_id
+|;
+ }
+
+ my $sql = qq|
+SELECT
+ id, imgurl, title, subtitle, year, director, SUBSTRING(plot,1,200)
+FROM
+ MEDIALIB_VIDEODATA
+$join
+$where
+ORDER BY title
+|;
+
+ $erg = $obj->{dbh}->selectall_arrayref($sql);
+ $setcount = defined $erg ? scalar @$erg : 0;
+ map {
+ $_->[1] =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
+ } @$erg if $obj->{usecache} eq "y";
+ }
+
+ $console->table($erg,
+ {
+ %$params,
+ fields => $obj->_getsearchsfields_as_array,
+ allgenres => $obj->_get_videogenres_as_array,
+ setcount => $setcount,
+ range => defined $params->{range} && $params->{range} ne '' ? $params->{range} : $obj->{defrange},
+ ranges => $obj->_get_ranges_as_array,
+ cols => $obj->{listcols},
+ usecache => $obj->{usecache},
+ }
+ );
+}
+
+# ------------------
+sub editMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || {};
+ #print Dumper($params);
+ my $range = delete $params->{range} if defined $params->{range};
+ if ( $id ) {
+ my $sql = qq|
+SELECT
+ *
+FROM
+ MEDIALIB_VIDEODATA
+WHERE
+ id = ?
+|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($id)
+ or return $console->err(sprintf(gettext("Media with ID '%s' does not exist in the database!"),$id));
+ $params = $sth->fetchrow_hashref();
+ $params->{genres} = $obj->_get_videogenres_byvideoid($id);
+ $params->{filedate} = '' if $params->{filedate} eq '0000-00-00 00:00:00';
+ }
+
+ if ( ref $params ) {
+ foreach my $key ( keys %$params ) {
+ $params->{$key} =~ s/\r\n/\n/g if($params->{$key});
+ }
+ }
+
+ $params->{mediatype} = $obj->{defmediatype} unless defined $params->{mediatype} && $params->{mediatype} != 0;
+ $params->{language} = $obj->{deflanguage} unless defined $params->{language} && $params->{language} ne '';
+
+ #print Dumper($params);
+ $console->table({},
+ {
+ %$params,
+ mediatypes => $obj->_get_mediatype_as_array,
+ allgenres => $obj->_get_videogenres_as_array,
+ range => $range,
+ ranges => $obj->_get_ranges_as_array,
+ }
+ );
+}
+
+# ------------------
+sub listMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || "";
+ my $params = shift;
+
+ my ($r, $range,$where);
+ $r = uc($params->{range})
+ if ( $params && defined $params->{range} && $params->{range}=~ /^.+?/ );
+ $r = uc($obj->{defrange})
+ if ( ! $r );
+
+ if ( $r eq "SEEN" ) {
+ $where = qq| WHERE seen=1 |;
+ } elsif ( $r eq "UNSEEN" ) {
+ $where = qq| WHERE seen=0 |;
+ } elsif ( $r eq "WANTED" ) {
+ $where = qq| WHERE mediatype=50 |;
+ } elsif( $r ne "ALL" ) {
+ if ( $r eq "NUM" ) {
+ $range = '"1","2","3","4","5","6","7","8","9","0"';
+ } else {
+ $range = '"'. join( '","', split( //, $r)). '"';
+ }
+ $where = qq| WHERE UCASE(SUBSTRING(title,1,1)) IN ($range) |;
+ } else {
+ $where = "";
+ }
+
+ my $sql = qq|
+SELECT
+ id, imgurl, title, subtitle, year, director, SUBSTRING(plot,1,200)
+FROM
+ MEDIALIB_VIDEODATA
+$where
+ORDER BY title
+|;
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ my $setcount = defined $erg ? scalar @$erg : 0;
+
+ # Hier darf in Feld 6 \n nicht durch \r\n ersetzt werden
+ # da sonst die Formatierung des Listenansicht nicht mehr passt
+ map {
+ $_->[1] =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
+ } @$erg if $obj->{usecache} eq "y";
+
+ my $param = {
+ setcount => $setcount,
+ range => $r,
+ ranges => $obj->_get_ranges_as_array,
+ cols => $obj->{listcols},
+ usecache => $obj->{usecache},
+ };
+ return $console->table($erg, $param);
+}
+
+# ------------------
+sub displayMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || "";
+ my $params = shift;
+
+ lg("Details for mediaid:". $id);
+
+ my $sql = qq|
+SELECT
+ *
+FROM
+ MEDIALIB_VIDEODATA
+WHERE id = ?
+|;
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($id)
+ or return $console->err(sprintf(gettext("Media with ID '%s' does not exist in the database!"),$id));
+ my $erg = $sth->fetchall_arrayref();
+ my $actors = $obj->_get_actors( $erg->[0][12] );
+ my $actorcount = ref $actors ? scalar @$actors : 0;
+ $erg->[0][10] =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg if $obj->{usecache} eq "y";
+ $erg->[0][18] = '' if $erg->[0][18] eq '0000-00-00 00:00:00';
+
+ #\r\n will be replace with <br/> by output from OUTPUT::HTML
+ $erg->[0][6] =~ s/\n/\r\n/g;
+ $erg->[0][15] =~ s/\n/\r\n/g;
+
+ my $param = {
+ mediatype => $obj->_get_mediatype_namebyid( $erg->[0][26] ),
+ actors => $actors,
+ actorcount => $actorcount,
+ genres_all => $obj->_get_videogenres_as_hash_by_id,
+ genres_sel => $obj->_get_videogenres_byvideoid( $id ),
+ range => defined $params->{range} && $params->{range} ne '' ? $params->{range} : $obj->{defrange},
+ ranges => $obj->_get_ranges_as_array,
+ actorcols => $obj->{actorcols},
+ usecache => $obj->{usecache},
+ };
+ return $console->table($erg, $param);
+}
+
+# ------------------
+sub saveMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || "";
+ my $params = shift;
+
+ if ( ref $params && defined $params->{save} ) {
+
+ delete $params->{save};
+ delete $params->{source};
+ my $range = delete $params->{range};
+
+ foreach my $key ( keys %$params ) {
+ $params->{$key} =~ s/\r\n/\n/g;
+ }
+
+ $obj->_saveActors($console, $watcher, $params->{actors});
+ my $genres = delete $params->{genres};
+ $obj->_saveMedia($params);
+
+ if(not $params->{id}) {
+ lg("Got no id from CGI");
+ $params->{id} = $obj->{dbh}->selectrow_arrayref('SELECT max(ID) FROM MEDIALIB_VIDEODATA')->[0];
+ lg("ID fetched from DB: ". $params->{id});
+ }
+
+ if ( $params->{id} ) {
+ $obj->_saveGenres($params->{id}, $genres);
+ }
+
+ $console->message(sprintf gettext('Media %s saved!'), $params->{id});
+ debug sprintf('%s media is saved%s',
+ ($id ? 'New' : 'Changed'),
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+ $console->redirect({url => '?cmd=mldisplay&data='. $params->{id}."&__range=". $range, wait => 2})
+ if($console->typ eq 'HTML');
+
+ }
+}
+
+# ------------------
+sub deleteMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || "";
+ my $params = shift;
+
+ my @media = reverse sort{ $a <=> $b } split(/[^0-9]/, $id);
+
+ {
+ my $sql = sprintf('DELETE FROM MEDIALIB_VIDEODATA WHERE id IN (%s)', join(',' => ('?') x @media));
+ my $sth = $obj->{dbh}->prepare($sql);
+ if(!$sth->execute(@media)) {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ $console->err(sprintf gettext("Media with ID '%s' does not exist in the database!"), join(',', @media));
+ return 0;
+ }
+ }
+
+ {
+ my $sql = sprintf('DELETE FROM MEDIALIB_VIDEOGENRE WHERE video_id IN (%s)', join(',' => ('?') x @media));
+ my $sth = $obj->{dbh}->prepare($sql);
+ if(!$sth->execute(@media)) {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ $console->err(sprintf gettext("Genres for Media with ID '%s' does not exist in the database!"), join(',', @media));
+ return 0;
+ }
+ }
+
+ $console->message(sprintf gettext("Media %s is deleted."), join(',', @media));
+ debug sprintf('media with id "%s" is deleted%s',
+ join(',', @media),
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+ $console->redirect({url => '?cmd=mllist', wait => 1})
+ if($console->typ eq 'HTML');
+}
+
+# ------------------
+sub _saveActors {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $input = shift || '';
+
+ $input =~ s/\r\n/\n/g;
+ my @actors = split /\n/, $input;
+ #print Dumper( $input, @actors );
+
+ my $rob = main::getModule('ROBOT')
+ or return error('No ROBOT Module installed!');
+
+ $rob->saveRobot('actorimage', sub{
+ my $dbh = shift || return;
+ my $name = shift || return;
+
+ lg( sprintf("Looking for image of %s", $name ));
+
+ eval "use MediaLibParser::IMDb";
+ return panic("\nCan not load Module: MediaLibParser::DVDPalace") if($@);
+
+ my $mlp = MediaLibParser::IMDb->new(
+ 'lookup_actor' => $name,
+ 'regex_actor' => qr/$name/,
+ );
+
+ my $image = $mlp->actor || '';
+ if ( $image ) {
+ lg( sprintf("Found image for %s at %s",($name, $image)));
+ } else {
+ lg( sprintf("No image found for %s", $name));
+ }
+
+
+ my $sql = sprintf("INSERT INTO MEDIALIB_ACTORS (%s) VALUES (%s)",
+ 'name, imgurl',
+ '?, ?',
+ );
+ my $sth = $dbh->prepare( $sql );
+ $sth->execute( $name, $image );
+ $sth->finish;
+ return 1;
+ }
+ );
+
+
+ my $db_actors = $obj->_get_actors_as_hash_by_name($input);
+
+ my $needrobot;
+ foreach my $name ( @actors ) {
+
+ next if grep /^$name$/i, ( keys %$db_actors );
+ $rob->register('actorimage', $obj->{dbh}, $name);
+ lg( sprintf("Registered ROBOT for %s", $name));
+ $needrobot = 1;
+ }
+
+ if ( $needrobot ) {
+ $rob->start('actorimage');
+ $rob->clean('actorimage');
+ }
+}
+
+# ------------------
+sub _saveGenres {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $videoid = shift || 0;
+ my $genres = shift || [];
+ #print Dumper( $videoid,$genres);
+
+ my $sql = sprintf("REPLACE INTO MEDIALIB_VIDEOGENRE (%s) VALUES (%s)",
+ 'video_id, genre_id',
+ '?, ?',
+ );
+ my $sth = $obj->{dbh}->prepare( $sql );
+
+ if ( ref $genres ) {
+ foreach my $genre ( @$genres ) {
+ $sth->execute( $videoid, $genre );
+ }
+ } else {
+ $sth->execute( $videoid, $genres );
+ }
+ $sth->finish;
+}
+
+
+
+
+
+# ------------------
+sub _saveMedia {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to Save!' );
+
+ if(ref $data eq 'HASH') {
+ my ($names, $vals, $kenn);
+ map {
+ push(@$names, $_);
+ push(@$vals, $data->{$_}),
+ push(@$kenn, '?'),
+ } sort keys %$data;
+
+ my $sql = sprintf("REPLACE INTO MEDIALIB_VIDEODATA (%s) VALUES (%s)",
+ join(', ', @$names),
+ join(', ', @$kenn),
+ );
+ my $sth = $obj->{dbh}->prepare( $sql );
+ $sth->execute( @$vals );
+ $sth->finish;
+ } else {
+ #my $sth = $obj->{dbh}->prepare('REPLACE INTO AUTOTIMER VALUES (?,?,?,?,?,?,?,?,?)');
+ #$sth->execute( @$data );
+ }
+}
+
+# ------------------
+sub _get_actors {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $input = shift || return '';
+
+ $input =~ s/\r\n/\n/g;
+ my @actors = split /\n/, $input;
+ map {
+ $_ = uc($_);
+ } @actors;
+
+ my $sql = sprintf(qq|
+SELECT
+ UPPER(name) as name, imgurl
+FROM
+ MEDIALIB_ACTORS
+WHERE
+ UPPER(name) IN (%s)
+|, join(',' => ('?') x @actors));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@actors)
+ or return undef;
+ my $erg = $sth->fetchall_hashref('name');
+ #print Dumper( $sql, $erg );
+ foreach my $actor ( split /\n/, $input ) {
+ if ( not defined $erg->{uc($actor)} ) {
+ $erg->{$actor} = {
+ 'name' => $actor,
+ 'imgurl' => '',
+ };
+ }
+ }
+
+ my $ret = [];
+ foreach my $actor ( split /\n/, $input ) {
+ $erg->{uc($actor)}->{imgurl} =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
+ push @$ret, [$actor, $erg->{uc($actor)}->{imgurl}];
+ }
+
+ #print Dumper($erg, $ret);
+ return $ret;
+}
+
+# ------------------
+sub _get_actors_as_hash_by_name {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $input = shift || return '';
+
+ $input =~ s/\r\n/\n/g;
+ my @actors = split /\n/, $input;
+
+ my $sql = sprintf(qq|
+SELECT
+ name, imgurl
+FROM
+ MEDIALIB_ACTORS
+WHERE
+ name IN (%s)
+|, join(',' => ('?') x @actors));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@actors)
+ or return undef;
+ my $erg = $sth->fetchall_hashref('name');
+ return $erg;
+}
+
+sub _nocover {
+ my $obj = shift || return error ('No Object!' );
+ my $HTTPD = main::getModule('HTTPD');
+ my $nocover = sprintf('%s/%s/images/nocover', $HTTPD->{paths}->{HTMLDIR}, $HTTPD->{HtmlRoot});
+
+ if(-r $nocover . ".png") {
+ return ($nocover . ".png");
+ }
+ elsif(-r $nocover . ".gif") {
+ return ($nocover . ".gif");
+ } else {
+ $nocover = sprintf('%s/default/images/nocover', $HTTPD->{paths}->{HTMLDIR});
+
+ if(-r $nocover . ".png") {
+ return ($nocover . ".png");
+ } else {
+ return ($nocover . ".gif");
+ }
+ }
+}
+
+
+# ------------------
+sub mediacache {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $params = shift || '';
+
+ my $image;
+ if ( ref $params ) {
+ if ( defined $params->{source} && $params->{source} ne '') {
+ my $ctx = Digest::MD5->new;
+ $ctx->add($params->{source});
+ my $digest = $ctx->hexdigest;;
+ my $cache_image = $obj->{libraryimagedir}. '/'. $digest. '.jpg';
+ my $cache_noimage = $obj->{libraryimagedir}. '/'. $digest. '.missed';
+ if ( -e $cache_image ) {
+ $image = $cache_image;
+ } elsif ( -e $cache_noimage ) {
+ $image = $obj->_nocover();
+ } else {
+ my $ret = getstore( $params->{source}, $cache_image );
+ lg( 'Get store return code: '. $ret );
+ if ( $ret == 200 ) {
+ $image = $cache_image;
+ } else {
+ touch($cache_noimage);
+ $image = $obj->_nocover();
+ }
+ }
+ } else {
+ $image = $obj->_nocover();
+ }
+ } else {
+ $image = $obj->_nocover();
+ }
+ $console->image( $image );
+}
+
+# ------------------
+sub _get_mediatype_as_array {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return [
+ [1,'DVD'],
+ [2,'SVCD'],
+ [3,'VCD'],
+ [4,'CD-R'],
+ [5,'CD-RW'],
+ [6,'VHS'],
+ [7,'DVD-R'],
+ [8,'DVD-RW'],
+ [9,'DVD+R'],
+ [10,'DVD+RW'],
+ [11,'DVD-DL'],
+ [12,'DVD+DL'],
+ [13,'Divx'],
+ [14,'Xvid'],
+ [50,gettext('wanted')],
+ ];
+}
+
+# ------------------
+sub _get_mediatype_as_hash {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $types = $obj->_get_mediatype_as_array;
+
+ my $erg;
+ foreach my $set ( @$types ) {
+ $erg->{$set->[1]} = {
+ 'id' => $set->[0],
+ 'name' => $set->[1],
+ };
+ }
+ return $erg;
+}
+
+# ------------------
+sub _get_mediatype_namebyid {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return error ('No Mediatype ID given');
+
+ my $types = $obj->_get_mediatype_as_array;
+
+ foreach my $set ( @$types ) {
+ return $set->[1] if $set->[0] == $id;
+ }
+ return 0;
+}
+
+# ------------------
+sub _get_mediatype_idbyname {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Mediatype Name given');
+
+ my $types = $obj->_get_mediatype_as_array;
+
+ foreach my $set ( @$types ) {
+ return $set->[0] if $set->[1] == $name;
+ }
+ return '';
+}
+
+# ------------------
+sub _get_videogenres_byvideoid {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || 0;
+
+ return [] unless $id;
+
+ my $sql = qq|
+SELECT
+ genre_id
+FROM
+ MEDIALIB_VIDEOGENRE
+WHERE
+ video_id = ?
+|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($id)
+ or return undef;
+ my $erg = $sth->fetchall_arrayref();
+ my $ret;
+ map {
+ push @$ret, $_->[0];
+ } @$erg;
+ return $ret;
+}
+
+# ------------------
+sub _get_videogenres_as_hash_by_id {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $genres = $obj->_get_videogenres_as_array;
+
+ my $erg;
+ foreach my $set ( @$genres ) {
+ $erg->{$set->[0]} = {
+ 'id' => $set->[0],
+ 'name' => $set->[1],
+ };
+ }
+ return $erg;
+}
+
+# ------------------
+sub _get_videogenres_as_hash_by_name {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $genres = $obj->_get_videogenres_as_array;
+
+ my $erg;
+ foreach my $set ( @$genres ) {
+ $erg->{$set->[1]} = {
+ 'id' => $set->[0],
+ 'name' => $set->[1],
+ };
+ }
+ return $erg;
+}
+
+# ------------------
+sub _get_videogenres_as_hash {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return {
+ gettext('Action') => {
+ 'id' => 1,
+ 'name' => 'Action',
+ },
+ gettext('Adventure') => {
+ 'id' => 2,
+ 'name' => 'Adventure',
+ },
+ gettext('Animation') => {
+ 'id' => 3,
+ 'name' => 'Animation',
+ },
+ gettext('Comedy') => {
+ 'id' => 4,
+ 'name' => 'Comedy',
+ },
+ gettext('Crime') => {
+ 'id' => 5,
+ 'name' => 'Crime',
+ },
+ gettext('Documentary') => {
+ 'id' => 6,
+ 'name' => 'Documentary',
+ },
+ gettext('Drama') => {
+ 'id' => 7,
+ 'name' => 'Drama',
+ },
+ gettext('Family') => {
+ 'id' => 8,
+ 'name' => 'Family',
+ },
+ gettext('Fantasy') => {
+ 'id' => 9,
+ 'name' => 'Fantasy',
+ },
+ gettext('Film-Noir') => {
+ 'id' => 10,
+ 'name' => 'Film-Noir',
+ },
+ gettext('Horror') => {
+ 'id' => 11,
+ 'name' => 'Horror',
+ },
+ gettext('Musical') => {
+ 'id' => 12,
+ 'name' => 'Musical',
+ },
+ gettext('Mystery') => {
+ 'id' => 13,
+ 'name' => 'Mystery',
+ },
+ gettext('Romance') => {
+ 'id' => 14,
+ 'name' => 'Romance',
+ },
+ gettext('Sci-Fi') => {
+ 'id' => 15,
+ 'name' => 'Sci-Fi',
+ },
+ gettext('Short') => {
+ 'id' => 16,
+ 'name' => 'Short',
+ },
+ gettext('Thriller') => {
+ 'id' => 17,
+ 'name' => 'Thriller',
+ },
+ gettext('War') => {
+ 'id' => 18,
+ 'name' => 'War',
+ },
+ gettext('Western') => {
+ 'id' => 19,
+ 'name' => 'Western',
+ },
+ gettext('Adult') => {
+ 'id' => 20,
+ 'name' => 'Adult',
+ },
+ gettext('Music') => {
+ 'id' => 21,
+ 'name' => 'Music',
+ },
+ gettext('Biography') => {
+ 'id' => 22,
+ 'name' => 'Biography',
+ },
+ gettext('MLHistory') => {
+ 'id' => 23,
+ 'name' => 'History',
+ },
+ gettext('Childs') => {
+ 'id' => 24,
+ 'name' => 'Childs',
+ },
+ gettext('Splatter') => {
+ 'id' => 25,
+ 'name' => 'Splatter',
+ },
+ };
+
+}
+
+# ------------------
+sub _get_videogenres_as_array {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $hashref = $obj->_get_videogenres_as_hash;
+
+ my $ret = [];
+ foreach my $key ( sort keys %$hashref ) {
+ push @$ret, [ $hashref->{$key}->{id}, $key ];
+ }
+ return $ret;
+}
+
+# ------------------
+sub _getsearchsfields_as_array {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return [
+ [ 'title', gettext('Title') ],
+ [ 'subtitle', gettext('Subtitle') ],
+ [ 'director', gettext('Director') ],
+ [ 'actors', gettext('Actors') ],
+ [ 'plot', gettext('Plot') ],
+ [ 'year', gettext('Year') ],
+ [ 'country', gettext('Country') ],
+ [ 'diskid', gettext('DiskID') ],
+ [ 'mediatype', gettext('Mediatype') ],
+ [ 'language', gettext('Language') ],
+ [ 'comment', gettext('Comment') ],
+ ];
+}
+
+# ------------------
+sub _get_ranges_as_array {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return [
+ [ 'NUM', gettext('#') ],
+ [ 'ABC', gettext('ABC') ],
+ [ 'DEF', gettext('DEF') ],
+ [ 'GHI', gettext('GHI') ],
+ [ 'JKL', gettext('JKL') ],
+ [ 'MNO', gettext('MNO') ],
+ [ 'PQRS', gettext('PQRS') ],
+ [ 'TUV', gettext('TUV') ],
+ [ 'WXYZ', gettext('WXYZ') ],
+ [ 'ALL', gettext('All') ],
+ [ 'SEEN', gettext('Seen') ],
+ [ 'UNSEEN', gettext('Unseen') ],
+ [ 'WANTED', gettext('Wanted') ]
+ ];
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/MUSIC.pm b/lib/XXV/MODULES/MUSIC.pm
new file mode 100644
index 0000000..1b8d1fb
--- /dev/null
+++ b/lib/XXV/MODULES/MUSIC.pm
@@ -0,0 +1,1352 @@
+package XXV::MODULES::MUSIC;
+use strict;
+
+use Tools;
+use Locale::gettext;
+use File::Basename;
+use File::Path;
+use File::Find;
+
+$SIG{CHLD} = 'IGNORE';
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'MUSIC',
+ Prereq => {
+ 'DBI' => 'Database independent interface for Perl ',
+ 'DBD::mysql' => 'MySQL driver for the Perl5 Database Interface (DBI)',
+ 'MP3::Icecast' => 'Generate Icecast streams, as well as M3U and PLSv2 playlists',
+ 'MP3::Info' => 'Manipulate / fetch info from MP3 audio files ',
+ 'CGI' => 'Simple Common Gateway Interface Class',
+ 'LWP::Simple' => 'get, head, getprint, getstore, mirror - Procedural LWP interface',
+ 'Net::Amazon' => 'Framework for accessing amazon.com via SOAP and XML/HTTP',
+ 'Net::Amazon::Request::Artist' =>
+ 'Class for submitting Artist requests',
+ },
+ Description => gettext('This module managed music files.'),
+ Version => '0.92',
+ Date => '2007-02-04',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ path => {
+ description => gettext('Directory with the music files'),
+ default => '/music',
+ type => 'dir',
+ required => gettext('This is required!'),
+ },
+ port => {
+ description => gettext('Port to listen for icecast clients.'),
+ default => 8100,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Interface => {
+ description => gettext('Local interface to bind service'),
+ default => '0.0.0.0',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ proxy => {
+ description => gettext('Proxy URL to music server. e.g. (http://vdr/xxv) Please remember you must write the Port to icecast server in your Proxy Konfiguration!'),
+ default => '',
+ type => 'string',
+ },
+ clients => {
+ description => gettext('Maximum Clients to connect at the same time.'),
+ default => 5,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ coverimages => {
+ description => gettext('common directory for cover images'),
+ default => '/var/cache/xxv/cover',
+ type => 'dir',
+ required => gettext('This is required!'),
+ },
+ muggle => {
+ description => gettext('DSN for muggle Database'),
+ default => 'DBI:mysql:database=GiantDisc;host=localhost;port=3306',
+ type => 'string',
+ check => sub{
+ my $value = shift;
+ $obj->{mdbh} = $obj->ConnectToMuggleDB($value);
+ return $value;
+ },
+ },
+ mugglei => {
+ description => gettext('Path to the binary from mugglei.'),
+ default => 'mugglei',
+ type => 'file',
+ },
+ },
+ Commands => {
+ mrefresh => {
+ description => gettext('Rereading of the music directory.'),
+ short => 'mr',
+ callback => sub{ $obj->refresh(@_) },
+ Level => 'admin',
+ DenyClass => 'mlist',
+ },
+ mcovers => {
+ description => gettext('Download album covers.'),
+ short => 'mc',
+ callback => sub{ $obj->getcovers(@_) },
+ Level => 'admin',
+ DenyClass => 'mlist',
+ },
+ mplay => {
+ description => gettext("play music file 'fid'"),
+ short => 'mp',
+ callback => sub{ $obj->play(@_) },
+ DenyClass => 'stream',
+ },
+ mplaylist => {
+ description => gettext("get a m3u playlist for 'fid'"),
+ short => 'm3',
+ callback => sub{ $obj->playlist(@_) },
+ DenyClass => 'stream',
+ },
+ mlist => {
+ description => gettext("list music 'dir'"),
+ short => 'ml',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'mlist',
+ },
+ msearch => {
+ description => gettext("search music 'txt'"),
+ short => 'mf',
+ callback => sub{ $obj->search(@_) },
+ DenyClass => 'mlist',
+ },
+ mcoverimage => {
+ description => gettext('Show album covers.'),
+ short => 'mi',
+ callback => sub{ $obj->coverimage(@_) },
+ DenyClass => 'mlist',
+ },
+ mgetfile => {
+ description => gettext("Get music file 'fid'"),
+ short => 'mg',
+ callback => sub{ $obj->getfile(@_) },
+ DenyClass => 'mlist',
+ },
+ msuggest => {
+ hidden => 'yes',
+ callback => sub{ $obj->suggest(@_) },
+ DenyClass => 'mlist',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 1
+ if($obj->{active} eq 'n');
+
+ $obj->{Amazon} = Net::Amazon->new(
+ token => '1CCSPM94SQW5RNWY6682',
+ );
+
+ $obj->{mdbh} = $obj->ConnectToMuggleDB($obj->{muggle});
+
+ #create an instance to find all files below /usr/local/mp3
+ $obj->{ICE} = MP3::Icecast->new();
+# $obj->{ICE}->recursive(1);
+
+# Use "file::find" & "add_file" instead of use "add_directory"
+# avoid dead of modul via link-loops like cd /mp3; ln -s foo ../mp3
+# $obj->{ICE}->add_directory($obj->{path});
+ find( {
+ wanted => sub{
+ if(-r $File::Find::name) {
+ $obj->{ICE}->add_file($File::Find::name)
+ if($File::Find::name =~ /\.mp3$/sig); # Lookup for *.mp3
+ } else {
+ lg "Permissions deny, can't read : $File::Find::name";
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $obj->{path}
+ );
+
+ $obj->{SOCK} = IO::Socket::INET->new(
+ LocalPort => $obj->{port}, #standard Icecast port
+ LocalAddr => $obj->{Interface},
+ Listen => $obj->{clients},
+ Proto => 'tcp',
+ Reuse => 1,
+ Timeout => 3600
+ );
+
+ my $channels;
+
+ Event->io(
+ fd => $obj->{SOCK},
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ # accept client
+ my $client = $obj->{SOCK}->accept;
+ panic "Can't connect to new icecast client." and return unless $client;
+ $client->autoflush;
+
+ # make "channel" number
+ my $channel=++$channels;
+
+ # install a communicator
+ Event->io(
+ fd => $client,
+ prio => -1, # -1 very hard ... 6 very low
+ poll => 'r',
+ cb => sub {
+ my $watcher = shift;
+ # report
+ lg(sprintf("Talking on icecast channel %d", $channel));
+
+ # read new line and report it
+ my $handle=$watcher->w->fd;
+ my $data = $obj->parseRequest($handle);
+ my $files = $obj->handleInput($data);
+ unless(ref $files eq 'ARRAY') {
+ $watcher->w->cancel;
+ $client->close();
+ undef $watcher;
+ return 1;
+ }
+
+ $obj->stream($files, $client);
+
+ $watcher->w->cancel;
+ undef $watcher;
+ $client->close;
+ },
+ );
+
+ # report
+ lg(sprintf("Open new icecast channel %d", $channel));
+ },
+ );
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+
+ unless($obj->{mdbh}) {
+ # don't remove old table, if updated rows => warn only
+ tableUpdated($obj->{dbh},'MUSIC',12,0);
+
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS MUSIC (
+ Id int(11) unsigned auto_increment NOT NULL,
+ FILE text NOT NULL,
+ ARTIST varchar(128) default 'unknown',
+ ALBUM varchar(128) default 'unknown',
+ TITLE varchar(128) default 'unknown',
+ COMMENT varchar(128),
+ TRACKNUM varchar(10) default '0',
+ YEAR smallint(4) unsigned,
+ GENRE varchar(128),
+ BITRATE smallint(4) unsigned,
+ FREQUENCY varchar(4),
+ SECS int (11) NOT NULL,
+ PRIMARY KEY (ID)
+ ) COMMENT = '$version'
+ |);
+
+ $obj->{fields} = fields($obj->{dbh}, 'select * from MUSIC');
+
+ # Read File to Database, if the DB empty and Musicdir exists
+ $obj->refresh()
+ unless($obj->{dbh}->selectrow_arrayref("select count(*) from MUSIC")->[0]);
+ }
+
+ return 1;
+
+}
+
+# ------------------
+sub refresh {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ if( ref $console and not -d $obj->{path} ) {
+ my $errmsg = sprintf(gettext("Directory of the music files '%s' not found"), $obj->{path});
+ error($errmsg);
+ $console->err($errmsg);
+ $console->link({
+ text => gettext("Back to music list"),
+ url => "?cmd=mlist",
+ }) if($console->typ eq 'HTML');
+ return;
+ }
+
+ if($obj->{mugglei} and $obj->{mdbh}) {
+ my $usr = main::getGeneralConfig->{USR};
+ my $pwd = main::getGeneralConfig->{PWD};
+ my $host = (split(/ /, $dbh->{'mysql_hostinfo'}))[0];
+ # /usr/local/bin/mugglei -h 127.0.0.1 -c -u xpix -w xpix97 -t /NAS/Music .
+ my $command = sprintf('%s -h %s -z -c -u %s -w %s -t %s . 2>&1',
+ $obj->{mugglei}, lc($host), $usr, $pwd, $obj->{path});
+ lg sprintf("Execute: cd '%s';%s",$obj->{path},$command);
+ chdir($obj->{path});
+ my @erg = (`$command`);
+
+ if( ref $console) {
+ $console->message(gettext("Reread the music files ..."));
+ $console->link({
+ text => gettext("Back to music list"),
+ url => "?cmd=mlist",
+ }) if($console->typ eq 'HTML');
+ }
+ undef $obj->{GENRES}; # delete genres cache
+
+ return 1;
+ }
+
+ my $waiter;
+ # Show waiter, early as is possible
+ if(ref $console && $console->typ eq 'HTML') {
+ $waiter = $console->wait(gettext("Get information from music files ..."), 0, 1000, 'no');
+ }
+
+ lg('Please wait! I search for new Musicfiles!');
+
+ #create an instance to find all files below /usr/local/mp3
+ $obj->{ICE} = MP3::Icecast->new();
+ $obj->{ICE}->recursive(1);
+ $obj->{ICE}->add_directory($obj->{path});
+
+ $obj->{CACHE} = {};
+
+ my $data = $dbh->selectall_hashref("select ID, FILE from MUSIC", 'FILE');
+ my @files = $obj->{ICE}->files;
+
+ lg sprintf('Found %d music files !', scalar @files);
+
+ return unless(scalar @files);
+
+ if( ref $console and not scalar @files ) {
+
+ # last call of waiter
+ $waiter->end() if(ref $waiter);
+
+ $console->start() if(ref $waiter);
+
+ $console->err(gettext("No music files found!"));
+ $console->link({
+ text => gettext("Back to music list"),
+ url => "?cmd=mlist",
+ }) if($console->typ eq 'HTML');
+
+ return;
+ }
+
+ # Adjust waiter max value now.
+ $waiter->max(scalar @files)
+ if(ref $waiter);
+
+ my $c = 0;
+ my $new = 0;
+ foreach my $file (@files) {
+ ++$c;
+ $waiter->next($c)
+ if(ref $waiter);
+ next if(delete $data->{$file});
+ my $info = MP3::Info->new($file);
+ $new++
+ if($obj->insert($info));
+ }
+
+ foreach my $f (sort keys %$data) {
+ unless(-e $f) {
+ $dbh->do(sprintf('DELETE FROM MUSIC WHERE ID = %lu', $data->{$f}->{ID}));
+ }
+ }
+
+ # last call of waiter
+ $waiter->end() if(ref $waiter);
+
+ if(ref $console) {
+ $console->start()
+ if(ref $waiter);
+ my $msg = sprintf(gettext("%d new music files in database saved and %d non exists entries deleted!"), $new, scalar keys %$data);
+ $console->message($msg);
+ lg $msg;
+ $console->link({
+ text => gettext("Back to music list"),
+ url => "?cmd=mlist",
+ }) if($console->typ eq 'HTML');
+ }
+}
+
+# ------------------
+sub play {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift || return error ('No data');
+
+ debug sprintf('Call play%s',
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $console->player("?cmd=mplaylist&data=${data}&binary=1");
+}
+
+# ------------------
+sub playlist {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift || return error ('No data');
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ my $host = main::getModule('STREAM')->{host} || main::getModule('STATUS')->IP;
+ my $output;
+
+ foreach my $id (split('_', $data)) {
+ my $data;
+ if($obj->{mdbh}) {
+ $data = $dbh->selectrow_hashref("select * from tracks where id = '$id'");
+ } else {
+ $data = $dbh->selectrow_hashref("select * from MUSIC where ID = '$id'");
+ }
+ next unless($data);
+
+ $output .= "#EXTM3U\r\n" unless($output);
+
+ my $file;
+ my $proxy = $obj->{proxy};
+ $proxy =~ s/^\s+//; # no leading white space
+ $proxy =~ s/\s+$//; # no trailing white space
+ if(length($proxy)) {
+ $file = sprintf('%s/?cmd=play&data=%s&field=id', $proxy, $id);
+ } else {
+ $file = sprintf('http://%s:%lu/?cmd=play&data=%s&field=%s', $host, $obj->{port}, $id, ($obj->{mdbh} ? 'id' : 'ID'));
+ }
+ if($obj->{mdbh}) {
+ $output .= sprintf("#EXTINF:%d,%s - %s (%s)\r\n",$data->{'length'},$data->{title},$data->{artist},$data->{sourceid});
+ } else {
+ $output .= sprintf("#EXTINF:%d,%s - %s (%s)\r\n",$data->{SECS},$data->{TITLE},$data->{ARTIST},$data->{ALBUM});
+ }
+ $output .= sprintf("%s\r\n", $file);
+ }
+
+ if($output && $console->typ eq 'HTML') {
+ $console->{noFooter} = 1;
+ $console->{nopack} = 1;
+ $console->{nocache} = 1;
+
+ my $arg;
+ $arg->{'attachment'} = "playlist.m3u";
+ $arg->{'Content-Length'} = length($output);
+
+ $console->out($output, "audio/x-mpegurl", %{$arg} );
+ } else {
+ $console->err(gettext("Sorry, playback is'nt supported"));
+ }
+}
+
+# ------------------
+sub search {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $text = shift || return $console->err(gettext("No Text to search! Please use msearch 'text'"));
+
+ return $obj->list($watcher,$console,"search:".$text);
+}
+
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+ return 0
+ if(!$dbh);
+
+ # Genres cachen
+ $obj->{GENRES} = $dbh->selectall_hashref('select * from genre', 'id')
+ if($obj->{mdbh} && !$obj->{GENRES});
+
+ if($obj->{mdbh} && ! $search) {
+ my $eg = $dbh->selectrow_arrayref('select title from album limit 1')
+ || return $console->err($obj->{mdbh}->errstr);
+ $search = sprintf('album:%s', $eg->[0]);
+ } elsif(! $search) {
+ my $eg = $dbh->selectrow_arrayref('select ALBUM from MUSIC limit 1')
+ || return $console->err($dbh->errstr);
+ $search = sprintf('album:%s', $eg->[0]);
+ }
+
+ my @field = split(':',$search);
+ my $typ = $field[0];
+
+ # Muggleübersetzer ;)
+ my $translate = {
+ artist => 'artist',
+ album => 'title',
+ genre => 'genre1',
+ title => 'title',
+ year => 'year',
+ };
+
+ shift @field;
+ my $text = join(':',@field);
+
+ my $t;
+ if($typ eq 'genre') {
+ $t = ($obj->{mdbh} ? 'tracks.'.$translate->{$typ} : uc($typ));
+ $text = $obj->{GENRES}->{$text}->{id} if($obj->{mdbh});
+ } elsif($typ eq 'year') {
+ $t = ($obj->{mdbh} ? 'tracks.'.$translate->{$typ} : uc($typ));
+ } elsif($typ eq 'album') {
+ $t = ($obj->{mdbh} ? 'album.'.$translate->{$typ} : uc($typ));
+ } else {
+ $t = ($obj->{mdbh} ? 'tracks.'.$translate->{$typ} : uc($typ));
+ }
+
+ my $where;
+ if($typ eq 'search') {
+ if($obj->{mdbh}) {
+ $where = buildsearch("album.artist,tracks.artist,album.title,tracks.title,album.covertxt",$text);
+ } else {
+ $where = buildsearch("ALBUM,ARTIST,TITLE,COMMENT",$text);
+ }
+ } elsif($typ eq 'genre' && $obj->{mdbh}) {
+ $text =~ s/\'/\\'/sg;#'
+ $where = sprintf("%s LIKE '%s%%'", $t, $text);
+ } else {
+ $text =~ s/\'/\\'/sg;#'
+ $where = sprintf("%s LIKE '%%%s%%'", $t, $text);
+ }
+
+ my %f = (
+ 'Id' => umlaute(gettext('Sv')),
+ 'Artist' => umlaute(gettext('Artist')),
+ 'Album' => umlaute(gettext('Album')),
+ 'Title' => umlaute(gettext('Title')),
+ 'Tracknum' => umlaute(gettext('Tracknum')),
+ 'Year' => umlaute(gettext('Year')),
+ 'Length' => umlaute(gettext('Length')),
+ );
+
+ my $sql;
+ if($obj->{mdbh}) {
+
+ $sql = qq|
+ SELECT
+ tracks.id as $f{'Id'},
+ tracks.artist as $f{'Artist'},
+ album.title as $f{'Album'},
+ tracks.title as $f{'Title'},
+ tracks.tracknb as $f{'Tracknum'},
+ tracks.year as $f{'Year'},
+ IF(tracks.length >= 3600,SEC_TO_TIME(tracks.length),DATE_FORMAT(FROM_UNIXTIME(tracks.length), '%i:%s')) as $f{'Length'},
+ genre.genre as __GENRE,
+ album.covertxt as __COMMENT
+ FROM
+ tracks, album, genre
+ WHERE
+ tracks.sourceid = album.cddbid and
+ tracks.genre1 = genre.id and
+ ( $where )
+ |;
+
+ $sql .= qq|
+
+ UNION
+ SELECT
+ tracks.id as $f{'Id'},
+ tracks.artist as $f{'Artist'},
+ album.title as $f{'Album'},
+ tracks.title as $f{'Title'},
+ tracks.tracknb as $f{'Tracknum'},
+ tracks.year as $f{'Year'},
+ IF(tracks.length >= 3600,SEC_TO_TIME(tracks.length),DATE_FORMAT(FROM_UNIXTIME(tracks.length), '%i:%s')) as $f{'Length'},
+ "" as __GENRE,
+ album.covertxt as __COMMENT
+ FROM
+ tracks, album
+ WHERE
+ tracks.sourceid = album.cddbid and
+ tracks.genre1 = 'NULL' and
+ ( $where )
+ | if($typ ne 'genre');
+
+
+ $sql .= qq|
+ ORDER BY
+ $f{'Album'},
+ $f{'Tracknum'}
+ |;
+
+ } else {
+
+ $sql = qq|
+ SELECT
+ ID as $f{'Id'},
+ ARTIST as $f{'Artist'},
+ ALBUM as $f{'Album'},
+ TITLE as $f{'Title'},
+ TRACKNUM as $f{'Tracknum'},
+ YEAR as $f{'Year'},
+ IF(SECS >= 3600,SEC_TO_TIME(SECS),DATE_FORMAT(FROM_UNIXTIME(SECS), '%i:%s')) as $f{'Length'},
+ GENRE as __GENRE,
+ COMMENT as __COMMENT
+ FROM
+ MUSIC
+ WHERE
+ ( $where )
+ ORDER BY
+ FILE
+ |;
+ }
+
+ my $fields = fields($dbh, $sql);
+ my $erg = $dbh->selectall_arrayref($sql);
+
+ unshift(@$erg, $fields);
+
+ my $params = {
+ albums => ($obj->{mdbh} ? $obj->GroupArray('title', 'album', 'cddbid') : $obj->GroupArray('ALBUM')),
+ artists => ($obj->{mdbh} ? $obj->GroupArray('artist', 'tracks', 'id'): $obj->GroupArray('ARTIST')),
+ genres => $obj->GenreArray(),
+ getCover => sub{ return $obj->_findcoverfromcache(@_, 'relative') },
+ proxy => $obj->{proxy},
+ };
+
+ $console->table($erg, $params);
+}
+
+# ------------------
+sub handleInput {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Request!' );
+ my $cgi = CGI->new( $data->{Query} );
+
+ my $ucmd = $cgi->param('cmd') || 'play';
+ my $ufield = $cgi->param('field') || ($obj->{mdbh} ? 'id' : 'ID');
+ my $udata = $cgi->param('data') || '*';
+
+ my $files;
+ if($ucmd eq 'play' and $ufield and my @search = split(',',$udata)) {
+ $files = $obj->field2path($ufield, \@search);
+ } else {
+ return error "I don't understand this command '$ucmd'";
+ }
+ return $files;
+}
+
+# ------------------
+sub field2path {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $field = shift || return error ('No Field!' );
+ my $data = shift || return error ('No ids!' );
+ my $pathfield;
+ my $sql;
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ return 0
+ if(!$dbh);
+
+ map {$_ = $dbh->quote($_)} @$data;
+
+ if($obj->{mdbh}) {
+ $pathfield = 'mp3file';
+ $sql = sprintf "select %s, %s from tracks", $pathfield, $field;
+ } else {
+ $pathfield = 'FILE';
+ $sql = sprintf "select %s, %s from MUSIC", $pathfield, $field;
+ }
+ $sql .= sprintf " where %s in (%s)", $field, join(',', @$data)
+ if($data->[0] ne '*');
+
+ my $ret = $dbh->selectall_hashref($sql, $pathfield);
+ my @files = sort keys %$ret;
+ return \@files;
+}
+
+# ------------------
+sub insert {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return 0;
+
+ my @setdata;
+ foreach my $name (keys %$data) {
+ next unless(grep($name eq $_, @{$obj->{fields}}));
+ push(@setdata, sprintf("%s=%s", $name, $obj->{dbh}->quote($data->{$name})));
+ }
+
+ # MD5(File) as ID
+ my $sql = sprintf('INSERT INTO MUSIC SET %s', join(', ', @setdata));
+ # dumper($sql);
+ $obj->{dbh}->do( $sql );
+ return 1;
+}
+
+# ------------------
+sub stream {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $files = shift || return error ('No Files!' );
+ my $client = shift || return error ('No Client!' );
+
+ my %seen = ();
+ my @uniqu = grep { ! $seen{$_} ++ } @$files;
+
+ defined(my $child = fork()) or die "Can't fork: $!";
+ if($child == 0) {
+ $obj->{SOCK}->close;
+ $obj->{dbh}->{InactiveDestroy} = 1;
+
+ foreach my $file (@uniqu) {
+
+ $file = $obj->{path} . "/" . $file
+ if($obj->{mdbh});
+
+ debug('Stream file "%s" to Client: %s',
+ $file,$client);
+ my $erg = $obj->{ICE}->stream($file,0,$client)
+ || last;
+ }
+ exit 0;
+ }
+}
+
+# ------------------
+sub parseRequest {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $hdl = shift || return error ('No Request!' );
+
+ my ($Req, $size) = getFromSocket($hdl);
+
+ if(ref $Req eq 'ARRAY' and $Req->[0] =~ /^GET (\/[\w\.\/-\:]*)([\?[\w=&\.\+\%-\:\!]*]*)[\#\d ]+HTTP\/1.\d$/) {
+ my $data = {};
+ ($data->{Request}, $data->{Query}) = ($1, $2 ? substr($2, 1, length($2)) : undef);
+
+ # parse header
+ foreach my $line (@$Req) {
+ if($line =~ /Referer: (.*)/) {
+ $data->{Referer} = $1;
+ }
+ if($line =~ /Host: (.*)/) {
+ $data->{HOST} = $1;
+ }
+ if($line =~ /Authorization: basic (.*)/i) {
+ ($data->{username}, $data->{password}) = split(":", MIME::Base64::decode_base64($1), 2);
+ }
+ if($line =~ /User-Agent: (.*)/i) {
+ $data->{http_useragent} = $1;
+ }
+ }
+
+lg sprintf(qq|
+-----------------------------------------
+New Request from User: %s at Host: %s!
+Query: %s
+-----------------------------------------
+|, $data->{username}, $data->{HOST}, $data->{Query});
+
+ return $data;
+ } else {
+
+error sprintf(qq|
+-----------------------------------------
+Unknown Request :
+%s
+-----------------------------------------
+|, join("\n", @$Req));
+
+ return;
+ }
+}
+
+# ------------------
+sub GroupArray {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $field = shift || return undef;
+ my $table = shift;
+ my $idfield = shift;
+ my $search = shift;
+ my $limitquery = shift;
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ my $where = '';
+ $where = sprintf("WHERE %s LIKE '%%%%%s%%%%'",$field, $search)
+ if($search);
+ my $limit = '';
+ $limit = sprintf("LIMIT %s",$limitquery)
+ if($limitquery && $limitquery > 0);
+
+ my $sql;
+ if($obj->{mdbh}) {
+ $sql = sprintf('select %s, %s from %s %s group by %s order by %s %s', $field, $idfield, $table, $where, $field, $field, $limit);
+ } else {
+ $sql = sprintf('select %s, ID from MUSIC %s group by %s order by %s %s %s ', $field, $where, $field, $field, $limit);
+ }
+ my $erg = $dbh->selectall_arrayref($sql);
+
+ return $erg;
+}
+
+# ------------------
+sub GenreArray {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ my $sql;
+ if($obj->{mdbh}) {
+ $sql = "select genre, genre.id as id from genre,tracks where genre.id = tracks.genre1 group by id order by id";
+ } else {
+ my $field = 'genre';
+ $sql = sprintf('select %s, %s from MUSIC group by %s order by %s', $field, $field, $field, $field);
+ }
+ my $erg = $dbh->selectall_arrayref($sql);
+
+ return $erg;
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $lastReportTime = shift || 0;
+
+ return
+ if($obj->{active} eq 'n');
+
+ my $report = {};
+ if($obj->{mdbh}) {
+ $report->{FILE} = $obj->{mdbh}->selectrow_arrayref('select count(*) from tracks')->[0];
+ $report->{ALBUM} = $obj->{mdbh}->selectrow_arrayref('select count(*) from album')->[0];
+ my $d = $obj->{mdbh}->selectall_arrayref('select artist from tracks group by artist');
+ $report->{ARTIST} = scalar @$d;
+ $d = $obj->{mdbh}->selectall_arrayref('select genre1 from tracks group by genre1');
+ $report->{GENRE} = scalar @$d;
+ } else {
+ foreach my $field (qw/FILE ALBUM ARTIST GENRE/) {
+ my $data = $obj->GroupArray($field);
+ $report->{$field} = scalar @$data;
+ }
+ }
+
+
+ return {
+ message => sprintf(gettext('Music database contains %d entries with %d albums from %d artists in %d genres'),
+ $report->{FILE}, $report->{ALBUM},$report->{ARTIST}, $report->{GENRE}),
+ };
+}
+
+# ------------------
+sub getcovers {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $force = shift;
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ return error('No valid Amazon token exists. Please sign up at http://amazon.com/soap!')
+ unless($obj->{Amazon});
+
+ debug sprintf('Call getcovers%s',
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ my $waiter = $console->wait(gettext("Please wait, search for new covers ..."),0,1000,'no')
+ if(ref $console);
+
+ unless(-d $obj->{coverimages}) {
+ mkpath($obj->{coverimages}) or error "Can't mkpath $obj->{coverimages} : $!";
+ lg sprintf('mkdir path "%s"',
+ $obj->{coverimages}
+ );
+ }
+
+ my $rob = main::getModule('ROBOT')
+ or return error('No ROBOT Module installed!');
+
+ $rob->saveRobot('coverimage', sub{
+ my $artist = shift || return 0, "Missing artist";
+ my $album = shift || return 0, "Missing album";
+ my $year = shift || 0;
+ my $target = shift || return 0, "Missing target";
+ my $current = shift || 0;
+
+ my $msg = sprintf(gettext("Lookup for cover from '%s-%s'"), $artist,$album);
+ lg $msg;
+ # Anzeige der ProcessBar
+ $waiter->next($current,undef, $msg) if(ref $waiter);
+
+ my $req = Net::Amazon::Request::Artist->new(
+ artist => $artist,
+ );
+ my $resp = $obj->{Amazon}->request($req);
+
+ $album =~ s/([\)\(\-\?\+\*\[\]\{\}])/\\$1/g; # Replace regex groupsymbols "),(,-,?,+,*,[,],{,}"
+ $album =~ s/([\/])/\./g; # Replace splash
+
+ foreach my $item ($resp->properties) {
+
+ if($item->album() =~ /$album/i or
+ ($year and $item->year() and $item->year() == $year)) {
+ my $image = $item->ImageUrlMedium()
+ or $item->ImageUrlLarge()
+ or $item->ImageUrlSmall();
+ lg sprintf("Try to get cover %s.", $image);
+ getstore($image, $target) if($image);
+ last;
+ }
+ }
+
+ return 1;
+ });
+
+ my $erg;
+ if($obj->{mdbh}) {
+ $erg = $dbh->selectall_hashref('select DISTINCT t.id as ID,t.mp3file as FILE, a.artist as ARTIST, a.title as ALBUM, t.year as YEAR from album as a, tracks as t where a.cddbid = t.sourceid group by a.title', 'ID');
+ } else {
+ $erg = $dbh->selectall_hashref('select DISTINCT Id as ID, FILE, ARTIST, ALBUM, YEAR from MUSIC group by ALBUM', 'ID');
+ }
+
+ my $current = 0;
+ foreach my $id (sort keys %$erg) {
+ my $e = $erg->{$id};
+
+ my $file = sprintf('%s/%s', $obj->{path}, $e->{FILE});
+ my $target = $obj->_findcover($file,$e->{ARTIST},$e->{ALBUM});
+
+ next if($target and -e $target and not $force);
+
+ my $dest = $obj->_findcoverfromcache($e->{ALBUM},$e->{ARTIST});
+ $rob->register('coverimage', $e->{ARTIST}, $e->{ALBUM}, $e->{YEAR}, $dest, ++$current);
+ }
+
+ # Adjust waiter max value now.
+ $waiter->max($current || 1)
+ if(ref $waiter);
+
+ if(ref $waiter and $current) {
+ $waiter->endcallback(
+ sub{
+ if(ref $console) {
+ $console->start();
+ $console->message(my $msg = gettext("New covers search was successfully!"));
+ lg sprintf($msg);
+
+ $console->link({
+ text => gettext("Back to music list"),
+ url => "?cmd=mlist",
+ }) if($console->typ eq 'HTML');
+ $console->footer();
+ }
+ }
+ );
+ }
+
+ if(ref $waiter and not $current) {
+ $waiter->endcallback(
+ sub{
+ if(ref $console) {
+ $console->start();
+ $console->message(gettext("It is not necessary to look for new covers because already all albums possess cover!"));
+
+ $console->link({
+ text => gettext("Back to music list"),
+ url => "?cmd=mlist",
+ }) if($console->typ eq 'HTML');
+ $console->footer();
+ }
+ }
+ );
+ lg sprintf('All covers exists!');
+ }
+
+ # Start Robots
+ $rob->start( 'coverimage', $watcher, $console, sub{ $waiter->end if(ref $waiter and $current); } );
+
+ return $erg;
+}
+
+# ------------------
+sub _findcoverfromcache {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $album = shift || return error ('No Album!' );
+ my $artist = shift || 0;
+ my $typ = shift || 'absolute';
+
+ my $absolute;
+ my $relative;
+
+ if($artist) {
+ $absolute = sprintf('%s/%s-%s.jpg', $obj->{coverimages}, $obj->unique($artist), $obj->unique($album));
+ $relative = sprintf('/coverimages/%s-%s.jpg', $obj->unique($artist), $obj->unique($album));
+ } else {
+ $absolute = sprintf('%s/%s.jpg', $obj->{coverimages}, $obj->unique($album));
+ $relative = sprintf('/coverimages/%s.jpg', $obj->unique($album));
+ }
+ return $absolute
+ if($typ eq 'absolute');
+ return $relative
+ if(-r $absolute);
+
+ lg sprintf("Don't find cover for %s - %s, as file %s",$artist,$album,$absolute);
+ return undef;
+}
+
+# ------------------
+sub unique {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $text = shift || return '';
+
+ $text =~ s/[^0-9a-z]//sig;
+ return $text;
+}
+
+# ------------------
+sub ConnectToMuggleDB {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $dsn = shift || return 0;
+
+ $dsn =~ s/^\s+//;
+ $dsn =~ s/\s+$//;
+
+ #try to connect to muggle database
+ if(length($dsn) and $obj->{active} eq 'y') {
+ my $usr = main::getGeneralConfig->{USR};
+ my $pwd = main::getGeneralConfig->{PWD};
+
+ my $mdbh = DBI->connect(
+ $dsn, $usr, $pwd,
+ { PrintError => 1,
+ AutoCommit => 1,
+ }) || error($DBI::errstr);
+ if($mdbh) {
+ $mdbh->{InactiveDestroy} = 1;
+ $mdbh->{mysql_auto_reconnect} = 1;
+ debug('Successfully connect to: %s', $dsn);
+ return $mdbh;
+ } else {
+ debug('No GiantDisc data base. Use standard music data base!');
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+}
+
+# ------------------
+sub _findcover {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $file = shift || return error ('No file!' );
+ my $artist = shift;
+ my $album = shift;
+
+ my $coverimage;
+ my $directory = dirname($file);
+
+ if($obj->{coverimages} && -d $obj->{coverimages}) {
+ my $cache = $obj->_findcoverfromcache($album,$artist);
+ $coverimage = $cache
+ if($cache && -r $cache);
+ }
+
+ if(!$coverimage && -d $directory) {
+
+ my @images = [];
+ find(
+ {
+ wanted => sub{
+ if(-r $File::Find::name) {
+ push(@images,$File::Find::name)
+ if($File::Find::name =~ /\.jpg$|\.jpeg$|\.gif$|\.png/sig); # Lookup for images
+ } else {
+ lg "Permissions deny, can't read : $File::Find::name";
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $directory
+ );
+
+ # An image in the same directory as the song, named like the song but with the
+ # song extension replaced with the image format extension
+ # e.g. test.mp3 -> test.jpg
+ my $song = basename($file);
+ $song =~ s/([\)\(\-\?\+\*\[\]\{\}])/\\$1/g; # Replace regex groupsymbols "),(,-,?,+,*,[,],{,}"
+ $song =~ s/([\/])/\./g; # Replace splash
+ $song =~ s/(.*)\.mp3$/$1./ig;
+ my @f = grep { /$song/i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+
+ if(!$coverimage && $artist) {
+ $artist =~ s/([\)\(\-\?\+\*\[\]\{\}])/\\$1/g; # Replace regex groupsymbols "),(,-,?,+,*,[,],{,}"
+ $artist =~ s/([\/])/\./g; # Replace splash
+ @f = grep { /\/$artist\./i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+ }
+
+ if(!$coverimage && $album) {
+ $album =~ s/([\)\(\-\?\+\*\[\]\{\}])/\\$1/g; # Replace regex groupsymbols "),(,-,?,+,*,[,],{,}"
+ $album =~ s/([\/])/\./g; # Replace splash
+ @f = grep { /\/$album\./i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+ }
+
+ # An image named "cover" with the image format extension in the same directory
+ # as the song (album cover).
+ # e.g. cover.gif
+ if(!$coverimage) {
+ @f = grep { /\/cover\./i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+ }
+
+ # An image named "artist" with the image format extension in the parent
+ # directory of the song (artist image).
+ # e.g. artist.png
+ if(!$coverimage) {
+ @f = grep { /\/artist\./i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+ }
+ # An image named "album" with the image format extension in the parent
+ # directory of the song (album image).
+ # e.g. album.png
+ if(!$coverimage) {
+ @f = grep { /\/album\./i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+ }
+
+ # An image named "background" with the image format extension in the base
+ # directory of the MP3 source.
+ if(!$coverimage) {
+ @f = grep { /\/background\./i } @images;
+ $coverimage = $f[0]
+ if(scalar @f > 0 && -r $f[0]);
+ }
+ }
+ return $coverimage;
+}
+
+# ------------------
+sub coverimage {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift || return error ('No data');
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ if($dbh) {
+ my $sql;
+ my @id = split('_',$data);
+
+ my $coverimage;
+ map {$_ = $dbh->quote($_)} @id;
+
+ if($obj->{mdbh}) {
+ $sql = sprintf qq|
+ select id, mp3file as file,
+ tracks.artist as artist,
+ album.title as album
+ from tracks, album
+ where tracks.sourceid = album.cddbid
+ and id in (%s)|, join(',', @id);
+ } else {
+ $sql = sprintf qq|
+ select ID as id,
+ FILE as file,
+ ARTIST as artist,
+ ALBUM as album
+ from MUSIC
+ where id in (%s)|, join(',', @id);
+ }
+
+ my $ret = $dbh->selectrow_hashref($sql);
+
+ if($ret && $ret->{'id'})
+ {
+ my $file = sprintf('%s/%s', $obj->{path}, $ret->{'file'});
+
+ $coverimage = $obj->_findcover($file,$ret->{'artist'},$ret->{'album'});
+ }
+
+ if($console->typ eq 'HTML') {
+ if($coverimage) {
+ $console->datei($coverimage);
+ } else {
+ my $HTTPD = main::getModule('HTTPD');
+ my $nocover = sprintf('%s/%s/images/nocover', $HTTPD->{paths}->{HTMLDIR}, $HTTPD->{HtmlRoot});
+ if(-r $nocover . ".png") {
+ $console->datei($nocover . ".png");
+ }
+ elsif(-r $nocover . ".gif") {
+ $console->datei($nocover . ".gif");
+ } else {
+ $nocover = sprintf('%s/default/images/nocover', $HTTPD->{paths}->{HTMLDIR});
+ if(-r $nocover . ".png") {
+ $console->datei($nocover . ".png");
+ } else {
+ $console->datei($nocover . ".gif");
+ }
+ }
+ }
+ }
+ return 1;
+ }
+ $console->err(gettext("Sorry, images for cover is'nt supported"));
+ return 0;
+}
+
+# ------------------
+sub getfile {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift || return error ('No data');
+
+ my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});
+
+ if($dbh) {
+ my $sql;
+ my @id = split('_',$data);
+
+ map {$_ = $dbh->quote($_)} @id;
+
+ if($obj->{mdbh}) {
+ $sql = sprintf qq|
+ select id, mp3file as file from tracks
+ where id in (%s)|, join(',', @id);
+ } else {
+ $sql = sprintf qq|
+ select ID as id, FILE as file from MUSIC
+ where id in (%s)|, join(',', @id);
+ }
+
+ my $ret = $dbh->selectrow_hashref($sql);
+ if($ret
+ && $ret->{'id'}
+ && $ret->{'file'}
+ && $console->typ eq 'HTML') {
+ $console->datei(sprintf('%s/%s', $obj->{path}, $ret->{'file'}));
+ return 1;
+ }
+ }
+ $console->err(gettext("Sorry, can't get file."));
+ return 0;
+}
+
+# ------------------
+sub suggest { # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+ my $params = shift;
+
+ if(exists $params->{get}) {
+ my $result;
+ $result = ($obj->{mdbh} ? $obj->GroupArray('title', 'album', 'cddbid',$search, 25) : $obj->GroupArray('ALBUM',undef,undef,$search, 25))
+ if($params->{get} eq 'album');
+
+ $result = ($obj->{mdbh} ? $obj->GroupArray('artist', 'tracks', 'id',$search, 25): $obj->GroupArray('ARTIST',undef,undef,$search, 25))
+ if($params->{get} eq 'artist');
+
+ $result = ($obj->{mdbh} ? $obj->GroupArray('title', 'tracks', 'id',$search, 25): $obj->GroupArray('TITLE',undef,undef,$search, 25))
+ if($params->{get} eq 'title');
+
+ $console->table($result)
+ if(ref $console && $result);
+ }
+
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/RECORDS.pm b/lib/XXV/MODULES/RECORDS.pm
new file mode 100644
index 0000000..ded7e70
--- /dev/null
+++ b/lib/XXV/MODULES/RECORDS.pm
@@ -0,0 +1,2136 @@
+package XXV::MODULES::RECORDS;
+
+use strict;
+
+use Tools;
+use File::Find;
+use File::Copy;
+use File::Path;
+use File::Basename;
+use File::stat;
+use Locale::gettext;
+
+$SIG{CHLD} = 'IGNORE';
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $args = {
+ Name => 'RECORDS',
+ Prereq => {
+ 'Time::Local' => 'efficiently compute time from local and GMT time ',
+ },
+ Description => gettext('This module managed recordings.'),
+ Version => '0.93',
+ Date => '2007-07-22',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ commandfile => {
+ description => gettext('Location of reccmds.conf on your system.'),
+ default => '/var/lib/vdr/reccmds.conf',
+ type => 'file',
+ required => gettext("This is required!"),
+ },
+ interval => {
+ description => gettext('How often recordings are to be updated (in seconds)'),
+ default => 30 * 60,
+ type => 'integer',
+ required => gettext("This is required!"),
+ },
+ fullreading => {
+ description => gettext('How often recordings are to be completely read in (in hours)'),
+ default => 24,
+ type => 'integer',
+ required => gettext("This is required!"),
+ },
+ videodir => {
+ description => gettext('Directory, where vdr recordings are stored.'),
+ default => '/var/lib/video',
+ type => 'dir',
+ required => gettext("This is required!"),
+ },
+ previewbinary => {
+ description => gettext('Location of used program to produce preview images on your system.'),
+ default => '/usr/bin/mplayer',
+ type => 'file',
+ required => gettext("This is required!"),
+ },
+ previewcommand => {
+ description => gettext('Please choose the used program to produce preview images.'),
+ type => 'list',
+ choices => [
+ [gettext('Nothing'), 'Nothing'],
+ ['MPlayer1.0pre5', 'MPlayer1.0pre5'],
+ ['MPlayer1.0pre6', 'MPlayer1.0pre6'],
+ ['vdr2jpeg', 'vdr2jpeg'],
+ ],
+ default => 'Nothing',
+ required => gettext("This is required!"),
+ },
+ previewcount => {
+ description => gettext('How many preview images produce?'),
+ default => 3,
+ type => 'integer',
+ },
+ previewlistthumbs => {
+ description => gettext('Display list records with thumbnails?'),
+ default => 'n',
+ type => 'confirm',
+ },
+ previewimages => {
+ description => gettext('common directory for preview images'),
+ default => '/var/cache/xxv/preview',
+ type => 'dir',
+ required => gettext('This is required!'),
+ },
+ vfat => {
+ description => gettext('Set this if your filename encoded for vfat filesystems'),
+ default => 'y',
+ type => 'confirm',
+ },
+ },
+ Commands => {
+ rdisplay => {
+ description => gettext("Display recording 'rid'"),
+ short => 'rd',
+ callback => sub{ $obj->display(@_) },
+ DenyClass => 'rlist',
+ },
+ rlist => {
+ description => gettext('List recordings'),
+ short => 'rl',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'rlist',
+ },
+ rsearch => {
+ description => gettext("Search recordings 'text'"),
+ short => 'rs',
+ callback => sub{ $obj->search(@_) },
+ DenyClass => 'rlist',
+ },
+ rupdate => {
+ description => gettext('Update recordings'),
+ short => 'ru',
+ callback => sub{ $obj->refresh(@_) },
+ Level => 'user',
+ DenyClass => 'redit',
+ },
+ rdelete => {
+ description => gettext("Delete recording 'rid'"),
+ short => 'rr',
+ callback => sub{ $obj->delete(@_) },
+ Level => 'user',
+ DenyClass => 'redit',
+ },
+ redit => {
+ description => gettext("Edit recording 'rid'"),
+ short => 're',
+ callback => sub{ $obj->redit(@_) },
+ Level => 'user',
+ DenyClass => 'redit',
+ },
+ rconvert => {
+ description => gettext("Convert recording 'rid'"),
+ short => 'rc',
+ callback => sub{ $obj->conv(@_) },
+ Level => 'user',
+ DenyClass => 'redit',
+ },
+ rplay => {
+ description => gettext("Play recording 'rid' in vdr"),
+ short => 'rpv',
+ callback => sub{ $obj->play(@_) },
+ Level => 'user',
+ DenyClass => 'remote',
+ },
+ rcut => {
+ description => gettext("Cut recording 'rid' in vdr"),
+ short => 'rcu',
+ callback => sub{ $obj->cut(@_) },
+ Level => 'user',
+ DenyClass => 'remote',
+ },
+ rsuggest => {
+ hidden => 'yes',
+ callback => sub{ $obj->suggest(@_) },
+ DenyClass => 'rlist',
+ },
+ },
+ RegEvent => {
+ 'deleteRecord' => {
+ Descr => gettext('Create event entries, if a record deleted.'),
+
+ # You have this choices (harmless is default):
+ # 'harmless', 'interesting', 'veryinteresting', 'important', 'veryimportant'
+ Level => 'important',
+
+ # Search for a spezial Event.
+ # I.e.: Search for an LogEvent with match
+ # "Sub=>text" = subroutine =~ /text/
+ # "Msg=>text" = logmessage =~ /text/
+ # "Mod=>text" = modname =~ /text/
+ SearchForEvent => {
+ Sub => 'RECORDS',
+ Msg => 'delr',
+ },
+ # Search for a Match and extract the information
+ # of the RecordId
+ # ...
+ Match => {
+ RecordId => qr/delr\s+(\d+)/s,
+ },
+ Actions => [
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $record = getDataById($args->{RecordId}, 'RECORDS', 'RecordId');
+ my $epg = main::getModule('EPG')->getId($record->{eventid}, 'title, subtitle, description');
+
+
+ my $title = sprintf(gettext("Record deleted: %s"), $epg->{title});
+ my $description = "";
+ $description .= sprintf(gettext("Subtitle: %s\n"),
+ $epg->{subtitle}) if($epg->{subtitle});
+ $description .= sprintf(gettext("Description: %s\n"),
+ $epg->{description}) if($epg->{description});
+
+ main::getModule('REPORT')->news($title, $description, "display", $record->{eventid}, "important");
+ }
+ |,
+ ],
+
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # define framerate PAL 25, NTSC 30
+ $self->{framerate} = 25;
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ unless($obj->{dbh}) {
+ panic("Session to database is'nt connected");
+ return 0;
+ }
+
+ # remove old table, if updated rows
+ tableUpdated($obj->{dbh},'RECORDS',10,1);
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS RECORDS (
+ eventid bigint unsigned NOT NULL,
+ RecordId int(11) unsigned not NULL,
+ RecordMD5 varchar(32) NOT NULL,
+ Path text NOT NULL,
+ Prio tinyint NOT NULL,
+ Lifetime tinyint NOT NULL,
+ State tinyint NOT NULL,
+ Marks text,
+ Type enum('TV', 'RADIO', 'UNKNOWN') default 'TV',
+ addtime timestamp,
+ PRIMARY KEY (eventid),
+ UNIQUE KEY (eventid)
+ ) COMMENT = '$version'
+ |);
+
+ $obj->{JOBS} = [];
+ $obj->{after_updated} = [];
+ $obj->{countReading} = 0;
+
+ main::after(sub{
+ $obj->{svdrp} = main::getModule('SVDRP');
+ unless($obj->{svdrp}) {
+ panic ("Can't get modul SVDRP");
+ return 0;
+ }
+
+ # Interval to read recordings and put to DB
+ Event->timer(
+ interval => $obj->{interval},
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ $obj->readData();
+ $obj->{countReading} += 1;
+ },
+ );
+ $obj->readData();
+ $obj->{countReading} += 1;
+ return 1;
+ }, "RECORDS: Store records in database ...", 20);
+
+ 1;
+}
+
+# ------------------
+sub dot1000 {
+# ------------------
+ my $t = reverse shift;
+ $t =~ s/(\d{3})(?=\d)(?!\d*\.)/$1./g;
+ return scalar reverse $t;
+}
+
+# ------------------
+sub parseData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vdata = shift || return error('Problem to read Data!');
+ my ($event, $hash, $id, $date, $hour, $minute, $state, $duration, $title, $day, $month, $year);
+ my $dataHash = {};
+
+ foreach my $record (@{$vdata}) {
+ if($record =~ /\s+\d+´\s+/) { # VDR is patched with recording length patch
+ # 250-1 01.11 15:14* 50´ Discovery~Die Rose von Kerrymore Spielfilm D/2000
+ ($id, $date, $hour, $minute, $state, $duration, $title)
+ = $record =~ /^250[\-|\s](\d+)\s+([\d|\.]+)\s+(\d+)\:(\d+)(.?)\s*(\d*).*?\s+(.+)/si;
+ } else { # Vanilla VDR
+ # 250-1 01.11 15:14* Discovery~Die Rose von Kerrymore Spielfilm D/2000
+ ($id, $date, $hour, $minute, $state, $title)
+ = $record =~ /^250[\-|\s](\d+)\s+([\d|\.]+)\s+(\d+)\:(\d+)(.?).*?\s+(.+)/si;
+ }
+
+ unless($id) {
+ error sprintf("Can't parse svdrp data : '%s'",$record);
+ next;
+ }
+
+ # Split date
+ ($day,$month,$year) = $date =~ /^(\d+)\.(\d+)\.(\d+)$/;
+
+ $year += 100
+ if($year < 70); # Adjust year, 0-69 => 100-169 (2000-2069)
+ $year += 1900
+ if($year < 1900); # Adjust year, 70-99 => 1977-1999 ... 2000-2069
+
+ $event->{id} = $id;
+ $event->{state} = $state eq '*' ? 1 : 0;
+ $event->{starttime} = timelocal(0,$minute,$hour,$day,$month-1, $year);
+ $event->{title} = $title;
+
+ $hash = sprintf("%s~%s",$title,$event->{starttime});
+ %{$dataHash->{$hash}} = %{$event};
+ }
+ return ($dataHash);
+}
+
+# ------------------
+sub scandirectory { # ------------------
+ my $obj = shift || return error ('No Object!');
+
+ find(
+ {
+ wanted => sub{
+ if(-r $File::Find::name) {
+ push(@{$obj->{FILES}},[$File::Find::name,$obj->converttitle($File::Find::name)])
+ if($File::Find::name =~ /\.rec\/\d{3}.vdr$/sig); # Lookup for *.rec/001.vdr
+ } else {
+ lg "Permissions deny, can't read : $File::Find::name";
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $obj->{videodir}
+ );
+}
+
+# ------------------
+sub readData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $waiter = shift;
+ my $forceUpdate = shift;
+
+ # Read recording over SVDRP
+ my $lstr = $obj->{svdrp}->command('lstr');
+ my $vdata = [ grep(/^250/, @$lstr) ];
+
+ unless(scalar @$vdata) {
+ # Delete old Records
+ $obj->{dbh}->do('DELETE FROM RECORDS');
+
+ my $msg = gettext('No recordings available!');
+ $console->err($msg)
+ if(ref $console);
+ return error($msg);
+ }
+
+ # Get state from used harddrive (/video)
+ my $stat = $obj->{svdrp}->command('stat disk');
+ my ($total, $totalUnit, $free, $freeUnit, $percent);
+ my $totalDuration = 0;
+
+ if($stat->[1] and $stat->[1] =~ /^250/s) {
+ #250 473807MB 98028MB 79%
+ ($total, $totalUnit, $free, $freeUnit, $percent)
+ = $stat->[1] =~ /^250[\-|\s](\d+)(\S+)\s+(\d+)(\S+)\s+(\S+)/s;
+
+ $obj->{CapacityMessage} = sprintf(gettext("Used %s, Total %s%s, Free %s%s"),$percent, dot1000($total), $totalUnit, dot1000($free), $freeUnit);
+ $obj->{CapacityPercent} = int($percent);
+
+ } else {
+ error("Can't get disc state : ".join("\n", @$stat));
+ $obj->{CapacityMessage} = gettext("Unknown disc capacity!");
+ $obj->{CapacityPercent} = 0;
+
+ }
+
+ my @merkIds;
+ my $insertedData = 0;
+ my $updatedState = 0;
+ my $l = 0;
+ my $err = [];
+
+ my $vdrData = $obj->parseData($vdata);
+
+ # Adjust waiter max value now.
+ $waiter->max(scalar keys %$vdrData)
+ if(ref $console && ref $waiter);
+
+ $obj->{FILES} = undef;
+
+ my $db_data;
+ if($forceUpdate || $obj->{countReading} % ( $obj->{fullreading} * 3600 / $obj->{interval} ) == 0) {
+ # Once at day, make full scan
+ $obj->{dbh}->do('DELETE FROM RECORDS');
+ } else {
+ # read database for compare with vdr data
+ my $sql = qq|select r.eventid as eventid, r.RecordId as id,
+ UNIX_TIMESTAMP(e.starttime) as starttime,
+ e.duration as duration, r.State as state,
+ CONCAT_WS('~',e.title,e.subtitle) as title,
+ CONCAT_WS('~',e.title,e.subtitle,UNIX_TIMESTAMP(e.starttime)) as hash,
+ UNIX_TIMESTAMP(e.addtime) as addtime,
+ r.Path as path,
+ r.Type as type,
+ r.Marks as marks,
+ r.RecordMD5
+ from RECORDS as r,OLDEPG as e
+ where r.eventid = e.eventid |;
+ $db_data = $obj->{dbh}->selectall_hashref($sql, 'hash');
+
+ lg sprintf( 'Compare recording database with data from vdr : %d / %d',
+ scalar keys %$db_data,scalar keys %$vdrData );
+ }
+
+ # Compare this Hashes
+ foreach my $h (keys %{$vdrData}) {
+ my $event = $vdrData->{$h};
+
+ # Exists in DB ... update
+ if($db_data && exists $db_data->{$h}) {
+
+ $waiter->next(++$l,undef, sprintf(gettext("Update recording '%s'"),
+ $db_data->{$h}->{title}))
+ if(ref $waiter);
+
+ # Compare fields
+ foreach my $field (qw/id state/) {
+ if($db_data->{$h}->{$field} != $event->{$field}) {
+
+ $obj->_updateState($db_data->{$h}, $event);
+
+ $updatedState++;
+ last;
+ }
+ }
+
+ # Update Duration and maybe preview images, if recordings added during timer run
+ if(($db_data->{$h}->{starttime} + $db_data->{$h}->{duration} + 60) > $db_data->{$h}->{addtime}) {
+ my $duration = $obj->_recordinglength($db_data->{$h}->{path});
+ if($duration != $db_data->{$h}->{duration}) {
+
+ unless($console) {
+ # set addtime only if called from EVENT::TIMER
+ # avoid generating preview image during user actions
+ # it's should speedup reading recordings
+ $db_data->{$h}->{addtime} = time;
+
+ # Make Preview and remove older Preview images
+ my $command = $obj->videoPreview( $db_data->{$h}->{eventid}, $db_data->{$h}, 1);
+ push(@{$obj->{JOBS}}, $command)
+ if($command && not grep(/\Q$command/g,@{$obj->{JOBS}}));
+ }
+ # Update duration at database entry
+ $db_data->{$h}->{duration} = $duration;
+
+ $obj->_updateEvent($db_data->{$h});
+
+ $updatedState++;
+ }
+ }
+ $totalDuration += $db_data->{$h}->{duration};
+
+ push(@merkIds,$db_data->{$h}->{eventid});
+
+ # delete updated rows from hash
+ delete $db_data->{$h};
+
+ } else {
+ $waiter->next(++$l,undef, sprintf(gettext("Analyze recording '%s'"),
+ $event->{title}))
+ if(ref $waiter);
+
+ # Read VideoDir only at first call
+ if(not defined $obj->{FILES}) {
+ $obj->{FILES} = [];
+ $obj->scandirectory();
+ }
+
+ my $anahash = $obj->analyze($event);
+ if(ref $anahash eq 'HASH') {
+ $totalDuration += $anahash->{Duration};
+
+ if($obj->insert($anahash)) {
+ push(@merkIds,$anahash->{eventid});
+ $insertedData++;
+ } else {
+ push(@{$err},$anahash->{title});
+ }
+ } else {
+ push(@{$err},$event->{title});
+ }
+ }
+ }
+
+ if($db_data && scalar keys %$db_data > 0) {
+ my @todel;
+ foreach my $t (keys %{$db_data}) {
+ push(@todel,$db_data->{$t}->{RecordMD5});
+ }
+
+ my $sql = sprintf('DELETE FROM RECORDS WHERE RecordMD5 IN (%s)', join(',' => ('?') x @todel));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@todel)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+
+ debug sprintf 'Finish .. %d recordings inserted, %d recordings updated, %d recordings removed',
+ $insertedData, $updatedState, $db_data ? scalar keys %$db_data : 0;
+
+ $obj->{CapacityTotal} = $totalDuration;
+ $obj->{CapacityPercent} = (100.0 / $total) * ($total - $free)
+ if($total && $totalUnit eq $freeUnit);
+ $obj->{CapacityFree} = ($totalDuration * 100.0 / $obj->{CapacityPercent})
+ - $obj->{CapacityTotal};
+
+ # Previews im fork erzeugen
+ if(scalar @{$obj->{JOBS}}) {
+ #Changes made after the fork() won't be visible in the parent process
+ my @jobs = @{$obj->{JOBS}};
+ $obj->{JOBS} = [];
+
+ defined(my $child = fork()) or return error sprintf("Can't fork : %s",$!);
+ if($child == 0) {
+ $obj->{dbh}->{InactiveDestroy} = 1;
+
+ while(scalar @jobs > 0) {
+ my $command = shift (@jobs);
+ lg sprintf('Call cmd "%s" now',
+ $command,
+ );
+ my $erg = system("nice -n 19 $command");
+ }
+ exit 0;
+ }
+ }
+
+ # alte PreviewDirs loeschen
+ foreach my $dir (glob(sprintf('%s/*_shot', $obj->{previewimages}))) {
+ my $oldEventNumber = (split('/', $dir))[-1];
+ unless(grep(sprintf('%lu_shot',$_) eq $oldEventNumber, @merkIds)) {
+ deleteDir($dir);
+ }
+ }
+
+ # Delete all old EPG entrys without the RecordIds which old as one day.
+ if(scalar @merkIds) {
+ my $sql = sprintf('DELETE FROM OLDEPG where (UNIX_TIMESTAMP(starttime) + duration) < (UNIX_TIMESTAMP() - 86400) and eventid not in (%s)', join(',' => ('?') x @merkIds));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@merkIds)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+
+ $obj->updated() if($insertedData);
+
+ # last call of waiter
+ $waiter->end() if(ref $waiter);
+
+ if(ref $console) {
+ $console->start() if(ref $waiter);
+ if(scalar @{$err} == 0) {
+ $console->message(sprintf(gettext("Write %d recordings in database."), scalar @merkIds));
+ } else {
+ unshift(@{$err}, sprintf(gettext("Write only %d recordings in database. Can\'t assign %d recordings."), scalar @merkIds , scalar @{$err}));
+ lg join("\n", @$err);
+ $console->err($err);
+ }
+
+ $console->redirect({url => '?cmd=rlist', wait => 1})
+ if($console->typ eq 'HTML');
+ }
+ return 1;
+}
+
+# Routine um Callbacks zu registrieren und
+# diese nach dem Aktualisieren der Aufnahmen zu starten
+# ------------------
+sub updated {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cb = shift || 0;
+ my $log = shift || 0;
+
+ if($cb) {
+ push(@{$obj->{after_updated}}, [$cb, $log]);
+ } else {
+ foreach my $CB (@{$obj->{after_updated}}) {
+ next unless(ref $CB eq 'ARRAY');
+ lg $CB->[1]
+ if($CB->[1]);
+ &{$CB->[0]}()
+ if(ref $CB->[0] eq 'CODE');
+ }
+ }
+}
+
+# ------------------
+sub refresh {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ my $waiter;
+ if(ref $console) {
+ if($console->typ eq 'HTML') {
+ $waiter = $console->wait(gettext("Get informations from recordings ..."),0,1000,'no');
+ } else {
+ $console->msg(gettext("Get informations from recordings ..."));
+ }
+ }
+
+ return $obj->readData($watcher,$console,$waiter,1);
+}
+
+# ------------------
+sub insert {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $attr = shift || return 0;
+
+ my $sth = $obj->{dbh}->prepare(
+ qq|
+ REPLACE INTO RECORDS
+ (eventid, RecordId, RecordMD5, Path, Prio, Lifetime, State, Marks, Type )
+ VALUES (?,?,md5(?),?,?,?,?,?,?)
+ |);
+
+ $attr->{Marks} = ""
+ if(not $attr->{Marks});
+
+ return $sth->execute(
+ $attr->{eventid},
+ $attr->{RecordId},
+ $attr->{Path},
+ $attr->{Path},
+ $attr->{Prio},
+ $attr->{Lifetime},
+ $attr->{State},
+ $attr->{Marks},
+ $attr->{Type},
+ );
+}
+
+# ------------------
+sub _updateEvent {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $event = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('UPDATE OLDEPG SET duration=?, starttime=FROM_UNIXTIME(?), addtime=FROM_UNIXTIME(?) where eventid=?');
+ if(!$sth->execute($event->{duration},$event->{starttime},$event->{addtime},$event->{eventid})) {
+ error sprintf("Can't update Event!: '%s' !",$event->{eventid});
+ return undef;
+ }
+ return $event;
+}
+
+# ------------------
+sub _updateState { # ------------------
+ my $obj = shift || return error ('No Object!');
+ my $oldattr = shift || return error ('Missing data');
+ my $attr = shift || return error ('No data to replace!');
+
+ my $sth = $obj->{dbh}->prepare('UPDATE RECORDS SET RecordId=?, State=?, addtime=FROM_UNIXTIME(?) where RecordMD5=?');
+ return $sth->execute($attr->{id},$attr->{state},time,$oldattr->{RecordMD5});
+}
+
+# ------------------
+sub analyze {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $recattr = shift || return error ('No data to analyze!');
+
+ lg sprintf('Analyze record "%s" from system',
+ $recattr->{title},
+ );
+
+ my $info = $obj->videoInfo($recattr->{title}, $recattr->{starttime});
+ unless($info && ref $info eq 'HASH') {
+ error sprintf("Can't find recording '%s' with id : '%s' !",$recattr->{title}, $recattr->{id});
+ return 0;
+ }
+
+ my @t = split('~', $recattr->{title});
+ my $title = $recattr->{title};
+ my $subtitle;
+ if(scalar @t > 1) { # Splitt genre~title | subtitle
+ my @p = split('/', $info->{path});
+ $subtitle = delete $t[-1]
+ if(scalar @p > 3 && $p[-2] ne '_');
+ $subtitle = undef if(defined $subtitle and $subtitle eq ' ');
+ $title = join('~',@t);
+ }
+
+ my $event = $obj->SearchEpgId( $recattr->{starttime}, $info->{duration}, $title, $subtitle, $info->{channel} );
+ if($event) {
+ my $id = $event->{eventid};
+ $event->{addtime} = time;
+ $event->{duration} = int($info->{duration});
+ $event->{starttime} = $recattr->{starttime};
+ $event = $obj->_updateEvent($event);
+ unless($event) {
+ return 0;
+ }
+ } else {
+ # Sollte kein Event gefunden werden so muss dieser in OLDEPG mit
+ # den vorhandenen Daten (lstr nummer) eingetragen werden und eine PseudoEventId (min(eventid)-1)
+ # erfunden werden ;)
+ $event = $obj->createOldEventId($recattr->{id}, $recattr->{starttime}, $info->{duration}, $title, $subtitle, $info);
+ unless($event) {
+ error sprintf("Can't create Event!: '%s' !",$recattr->{id});
+ return 0;
+ }
+ }
+
+ # Make Preview
+ my $command = $obj->videoPreview( $event->{eventid}, $info );
+ push(@{$obj->{JOBS}}, $command)
+ if($command && not grep(/\Q$command/g,@{$obj->{JOBS}}));
+
+ my $ret = {
+ title => $recattr->{title},
+ RecordId => $recattr->{id},
+ Duration => $info->{duration},
+ Start => $recattr->{starttime},
+ Path => $info->{path},
+ Prio => $info->{Prio},
+ Lifetime => $info->{Lifetime},
+ eventid => $event->{eventid},
+ Type => $info->{type} || 'UNKNOWN',
+ State => $recattr->{state}
+ };
+ $ret->{Marks} = join(',', @{$info->{marks}})
+ if(ref $info->{marks} eq 'ARRAY');
+ return $ret;
+}
+
+# ------------------
+sub videoInfo {
+# ------------------
+ my $obj = shift || return error ('No object!' );
+ my $title = shift || return error ('No title!' );
+ my $starttime = shift || return error ('No title!' );
+
+ lg sprintf('Get videoInfo from record "%s"', $title );
+
+ my $month=sprintf("%02d",(localtime($starttime))[4]+1);
+ my $day=sprintf("%02d",(localtime($starttime))[3]);
+ my $hour=sprintf("%02d",(localtime($starttime))[2]);
+ my $minute=sprintf("%02d",(localtime($starttime))[1]);
+
+ my @files;
+
+ $title =~ s/([\)\(\-\?\+\*\[\]\{\}])/\\$1/g; # Replace regex groupsymbols "),(,-,?,+,*,[,],{,}"
+ $title =~ s/([\/])/\./g; # Replace splash
+
+ foreach my $f (@{$obj->{FILES}})
+ {
+ push (@files, $f->[0])
+ if(grep(/\~$title.*?\d{4}\-$month\-$day\.$hour[\:|\.]$minute.+?\d{3}\.vdr/,$f->[1]));
+ }
+
+ unless(scalar @files) {
+ error sprintf("Can't assign recording with title: '%s' (%s/%s %s:%s)", $title,$month,$day,$hour,$minute);
+ return 0;
+ }
+
+ my $status = {};
+
+ # Dateigröße von index.vdr für Aufnahmedauer ermitteln
+ if($files[0] && -e $files[0]) {
+
+ my $path = dirname($files[0]);
+
+ #Splitt 2005-01-16.04:35.88.99.rec
+ my ($year, $month, $day, $hour, $minute, $prio, $lifetime)
+ = (basename($path)) =~ /^(\d+)\-(\d+)\-(\d+)\.(\d+)[\:|\.](\d+)\.(\d+)\.(\d+)\.rec/si;
+# if($year && $month && $day && $hour && $minute && $year >= 1970 && $year < 2038 ) {
+# @{$status->{mtime}} = localtime(timelocal(0,int($minute),int($hour),$day,$month-1,$year-1900));
+# } else {
+# my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+# $atime,$mtime,$ctime,$blksize,$blocks) = stat $file;
+# @{$status->{mtime}} = localtime $mtime;
+# }
+ $status->{Prio} = $prio;
+ $status->{Lifetime} = $lifetime;
+
+ $status->{duration} = $obj->_recordinglength($path);
+
+ # Schnittmarken ermitteln
+ my $marks = sprintf("%s/marks.vdr", $path);
+ if(-r $marks) {
+ my $data = load_file($marks)
+ or error sprintf("I can't read file '%s'",$marks);
+ if($data) {
+ foreach my $zeile (split("\n", $data)) {
+ # 0:35:07.09 moved from [0:35:13.24 Logo start] by checkBlackFrameOnMark
+ my ($mark) = $zeile =~ /^(\d+\:\d+\:\d+\.\d+)/sg;
+ push(@{$status->{marks}}, $mark)
+ if(defined $mark);
+ }
+ }
+ }
+
+ # Summary ermitteln
+ my $file = sprintf("%s/info.vdr", $path);
+ $file = sprintf("%s/summary.vdr", $path ) if(main::getVdrVersion() < 10325);
+
+ $status->{type} = 'UNKNOWN';
+ if(-r $file) {
+ my $text = load_file($file);
+
+ # Neue Vdr Version 1.3.25!
+ if(main::getVdrVersion() >= 10325) {
+ my $cmod = main::getModule('CHANNELS');
+ foreach my $zeile (split(/[\r\n]/, $text)) {
+ if($zeile =~ /^D\s+(.+)/s) {
+ $status->{summary} = $1;
+ $status->{summary} =~ s/\|/\r\n/g; # pipe used from vdr as linebreak
+ $status->{summary} =~ s/^\s+//; # no leading white space
+ $status->{summary} =~ s/\s+$//; # no trailing white space
+ }
+ elsif($zeile =~ /^C\s+(.+)$/s) {
+ $status->{channel} = $1;
+ $status->{type} = $cmod->getChannelType($status->{channel});
+ }
+ elsif($zeile =~ /^T\s+(.+)$/s) {
+ $status->{title} = $1;
+ }
+ elsif($zeile =~ /^S\s+(.+)$/s) {
+ $status->{subtitle} = $1;
+ }
+ elsif($zeile =~ /^X\s+1\s+(.+)$/s) {
+ $status->{video} = $1;
+ }
+ elsif($zeile =~ /^X\s+2\s+(.+)$/s) {
+ $status->{audio} .= "\n" if($status->{audio});
+ $status->{audio} .= $1;
+ }
+ }
+ } else {
+ $status->{summary} = $text;
+ }
+ }
+
+ $status->{path} = $path;
+ }
+
+ return $status;
+}
+
+# ------------------
+sub videoPreview {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eventid = shift || return error ('No eventid!');
+ my $info = shift || return error ('No InfoHash!');
+ my $rebuild = shift || 0;
+
+ if ($obj->{previewcommand} eq 'Nothing') {
+ return 0;
+ }
+ if($info->{type} and $info->{type} eq 'RADIO') {
+ return 0;
+ }
+
+ # Videodir
+ my $vdir = $info->{path};
+ if(! -d $vdir ) {
+ error sprintf("Missing path ! %s",$!);
+ return 0;
+ }
+
+ # Save dir
+ my $count = $obj->{previewcount};
+ my $outdir = sprintf('%s/%lu_shot', $obj->{previewimages}, $eventid);
+
+ # Stop here if enough files present
+ my @images = glob("$outdir/*.jpg");
+ return 0
+ if(scalar @images >= $count && !$rebuild);
+
+ deleteDir($outdir) if(scalar @images && $rebuild);
+
+ # or stop if two log's present, use two logs avoid to early run on current live recording
+ my $log = sprintf('%s/preview_1st.log', $outdir);
+ if(-e $log) {
+ $log = sprintf('%s/preview_2nd.log', $outdir);
+ if(-e $log) {
+ return 0;
+ }
+ }
+
+ # Mplayer
+ unless(-x $obj->{previewbinary}) {
+ error("I can't find executable file as usable preview command !");
+ return 0;
+ }
+
+ unless(-d $outdir) {
+ if(!mkpath($outdir)) {
+ error sprintf("Can't mkpath '%s' : %s",$outdir,$!);
+ return 0;
+ }
+ }
+
+ my $tmod = main::getModule('TIMERS');
+ my $startseconds = ($tmod->{prevminutes} * 60) * 2;
+ my $endseconds = ($tmod->{afterminutes} * 60) * 2;
+ my $stepseconds = ($info->{duration} - ($startseconds + $endseconds)) / $count;
+ # reduced interval on short movies
+ if($stepseconds <= 0 or ($startseconds + ($count * $stepseconds)) > $info->{duration}) {
+ $stepseconds = $info->{duration} / ( $count + 2 ) ;
+ $startseconds = $stepseconds;
+ }
+
+ my @files;
+ my @frames;
+ if ($obj->{previewcommand} eq 'vdr2jpeg') {
+
+ my $m = ref $info->{marks} eq 'ARRAY' ? scalar(@{$info->{marks}}) : 0;
+ if($m > 1 && $info->{duration}) {
+ my $total = $info->{duration} * $obj->{framerate};
+ my $limit = $count * 4;
+ my $x = 2;
+ my $y = 1;
+ while (scalar @frames < $count && $x < $limit) {
+ my $f = int($total / $x * $y); # 1/2, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 2/5, 3/5 ...
+ for (my $n = 0;$n < $m; $n += 2 ) {
+ my $fin = $obj->_mark2frames(@{$info->{marks}}[$n]);
+ my $fout = $total;
+ $fout = $obj->_mark2frames(@{$info->{marks}}[$n+1]) if($n+1 < $m);
+
+ if ($f >= $fin && $f <= $fout
+ && 0 == (grep {$f == $_;} @frames)
+ ) {
+ push(@frames, $f);
+ last;
+ }
+ }
+ ++$y;
+ if($y >= $x) { $x += 2; $y = 1; }
+ }
+ }
+
+ my $s = int($startseconds * $obj->{framerate});
+ while (scalar @frames < $count) {
+ push(@frames, $s);
+ $s += int( $stepseconds * $obj->{framerate} );
+ }
+ } else {
+ @files = glob("$vdir/[0-9][0-9][0-9].vdr");
+ foreach (@files) { s/(\")/\\$1/g; }
+ }
+
+ $vdir =~ s/(\")/\\$1/g;
+
+ my $scalex = 180;
+ my $mversions = {
+ 'MPlayer1.0pre5' => sprintf("%s -noautosub -noconsolecontrols -nosound -nolirc -nojoystick -quiet -vo jpeg -jpeg outdir=\"%s\" -ni -ss %d -sstep %d -vf scale -zoom -xy %d -frames %d \"%s\" >> \"%s\" 2>&1",
+ $obj->{previewbinary}, $outdir, $startseconds / 5, $stepseconds / 5, $scalex, $count, join("\" \"",@files), $log),
+ 'MPlayer1.0pre6' => sprintf("%s -noautosub -noconsolecontrols -nosound -nolirc -nojoystick -quiet -vo jpeg:outdir=\"%s\" -ni -ss %d -sstep %d -vf scale -zoom -xy %d -frames %d \"%s\" >> \"%s\" 2>&1",
+ $obj->{previewbinary}, $outdir, $startseconds / 5, $stepseconds / 5, $scalex, $count, join("\" \"",@files), $log),
+ 'vdr2jpeg' => sprintf("%s -r \"%s\" -f %s -x %d -o \"%s\" >> \"%s\" 2>&1",
+ $obj->{previewbinary}, $vdir, join(" -f ", @frames), $scalex, $outdir, $log),
+ };
+ return $mversions->{$obj->{previewcommand}};
+}
+
+
+sub _mark2frames{
+ my $self = shift;
+ my $mark = shift;
+ my($h, $m, $s, $f) = split /[:.]/, $mark;
+ my $frame = (3600 * $h + 60 * $m + $s) * $self->{framerate} + $f ;
+ return $frame;
+};
+
+# ------------------
+sub SearchEpgId {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $start = shift || return error ('No Start!' );
+ my $dur = shift || return;
+ my $title = shift || return error ('No Title!' );
+ my $subtitle = shift;
+ my $channel = shift;
+
+ my $sth;
+ my $bis = int($start + $dur);
+ if($subtitle && $channel && $channel ne "") {
+ $sth = $obj->{dbh}->prepare(
+qq|SELECT * FROM OLDEPG WHERE
+ UNIX_TIMESTAMP(starttime) >= ?
+ AND UNIX_TIMESTAMP(starttime)+duration <= ?
+ AND title = ?
+ AND subtitle = ?
+ AND channel_id = ?|);
+ $sth->execute($start,$bis,$title,$subtitle,$channel)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ } elsif($subtitle) {
+ $sth = $obj->{dbh}->prepare(
+qq|SELECT * FROM OLDEPG WHERE
+ UNIX_TIMESTAMP(starttime) >= ?
+ AND UNIX_TIMESTAMP(starttime)+duration <= ?
+ AND title = ?
+ AND subtitle = ?|);
+ $sth->execute($start,$bis,$title,$subtitle)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ } else {
+ $sth = $obj->{dbh}->prepare(
+qq|SELECT * FROM OLDEPG WHERE
+ UNIX_TIMESTAMP(starttime) >= ?
+ AND UNIX_TIMESTAMP(starttime)+duration <= ?
+ AND title = ?|);
+ $sth->execute($start,$bis,$title)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+ return 0 if(!$sth);
+
+ my $erg = $sth->fetchrow_hashref();
+ return $erg
+ if($erg->{eventid}
+ and ( # check for equal subtitle
+ (not $subtitle and not $erg->{subtitle})
+ or (($subtitle and $erg->{subtitle}) and ($subtitle eq $erg->{subtitle}))
+ )
+ );
+}
+
+# ------------------
+sub createOldEventId {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return error ('No Id!' );
+ my $start = shift || return error ('No Starttime!' );
+ my $duration = shift || 0;
+ my $title = shift || return error ('No Title!' );
+ my $subtitle = shift;
+ my $info = shift;
+
+#warn($title);
+ my $attr = {
+ title => $title,
+ subtitle => $subtitle,
+ description => $info->{summary} || "",
+ channel => $info->{channel} || "<undef>",
+ duration => $duration,
+ starttime => $start,
+ video => $info->{video} || "",
+ audio => $info->{audio} || "",
+ addtime => time
+ };
+
+ $attr->{eventid} = $obj->{dbh}->selectrow_arrayref('select max(eventid)+1 from OLDEPG')->[0];
+ $attr->{eventid} = 1000000000 if(not defined $attr->{eventid} or $attr->{eventid} < 1000000000 );
+
+ # dumper($attr);
+
+ lg sprintf('Create OldEventId from event "%s" - "%s"',
+ $title,
+ $subtitle ? $subtitle : '',
+ );
+
+ my $sth = $obj->{dbh}->prepare('REPLACE INTO OLDEPG(eventid, title, subtitle, description, channel_id, duration, tableid, starttime, video, audio, addtime) VALUES (?,?,?,?,?,?,?,FROM_UNIXTIME(?),?,?,FROM_UNIXTIME(?))');
+ $sth->execute(
+ $attr->{eventid},
+ $attr->{title},
+ $attr->{subtitle},
+ $attr->{description},
+ $attr->{channel},
+ int($attr->{duration}),
+ $attr->{tableid},
+ $attr->{starttime},
+ $attr->{video},
+ $attr->{audio},
+ $attr->{addtime}
+ );
+
+ return $attr;
+}
+
+# ------------------
+sub display {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $recordid = shift || return $console->err(gettext("No RecordID to display the recording! Please use rdisplay 'rid'"));
+
+ my $start = "e.starttime";
+ my $stopp = "FROM_UNIXTIME(UNIX_TIMESTAMP(e.starttime) + e.duration)";
+
+ $start = "UNIX_TIMESTAMP(e.starttime)" if($console->typ eq "HTML");
+ $stopp = "UNIX_TIMESTAMP(e.starttime) + e.duration" if($console->typ eq "HTML");
+
+ my $sql = qq|
+select
+ r.RecordMD5 as RecordId,
+ r.eventid,
+ e.Duration,
+ r.Marks,
+ r.Prio,
+ r.Lifetime,
+ $start as StartTime,
+ $stopp as StopTime,
+ e.title as Title,
+ e.subtitle as SubTitle,
+ e.description as Description,
+ r.State as New,
+ r.Type as Type,
+ e.channel_id
+from
+ RECORDS as r,OLDEPG as e
+where
+ r.eventid = e.eventid
+ and RecordMD5 = ?
+|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($recordid)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchrow_hashref();
+
+ $obj->_loadreccmds;
+
+ my $param = {
+ previews => $obj->getPreviewFiles($erg->{eventid}),
+ reccmds => [@{$obj->{reccmds}}],
+ };
+
+ my $cmod = main::getModule('CHANNELS');
+ $erg->{Channel} = $cmod->ChannelToName($erg->{channel_id})
+ if($erg->{channel_id} && $erg->{channel_id} ne "<undef>");
+ delete $erg->{channel_id};
+
+ $console->table($erg, $param);
+}
+
+# ------------------
+sub play {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $recordid = shift || return $console->err(gettext("No RecordID to play the recording! Please use rplay 'rid'"));
+
+ my $sql = qq|SELECT RecordID FROM RECORDS WHERE RecordMD5 = ?|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ my $rec;
+ if(!$sth->execute($recordid)
+ || !($rec = $sth->fetchrow_hashref())) {
+ return $console->err(sprintf(gettext("RecordID '%s' does not exist in the database!"),$recordid));
+ }
+
+ my $cmd = sprintf('PLAY %d begin', $rec->{RecordID});
+ return $obj->{svdrp}->scommand($watcher, $console, $cmd);
+}
+
+# ------------------
+sub cut {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $recordid = shift || return $console->err(gettext("No RecordID to play the recording! Please use rplay 'rid'"));
+
+ my $sql = qq|SELECT RecordID FROM RECORDS WHERE RecordMD5 = ?|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ my $rec;
+ if(!$sth->execute($recordid)
+ || !($rec = $sth->fetchrow_hashref())) {
+ return $console->err(sprintf(gettext("RecordID '%s' does not exist in the database!"),$recordid));
+ }
+
+ my $cmd = sprintf('EDIT %d', $rec->{RecordID});
+ return $obj->{svdrp}->scommand($watcher, $console, $cmd);
+}
+
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $text = shift || "";
+ my $params = shift;
+
+ my $deep = 1;
+ my $folder = scalar (my @a = split('/',$obj->{videodir})) + 1;
+
+ my $select = "e.eventid = r.eventid";
+ if($text) {
+ $deep = scalar (my @c = split('~',$text));
+ $folder += $deep;
+ $deep += 1;
+
+ $text =~ s/\'/\\\'/sg;
+ $text =~ s/%/\\%/sg;
+ $select .= qq|
+AND (
+ SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) LIKE '$text'
+ OR
+ SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) LIKE '$text~%'
+)
+|;
+
+ }
+
+ my %f = (
+ 'Id' => umlaute(gettext('Service')),
+ 'Title' => umlaute(gettext('Title')),
+ 'Subtitle' => umlaute(gettext('Subtitle')),
+ 'Duration' => umlaute(gettext('Duration')),
+ );
+
+ my $start = "e.starttime";
+ $start = "UNIX_TIMESTAMP(e.starttime)" if($console->typ eq "HTML");
+
+ my $sql = qq|
+SELECT
+ r.RecordMD5 as $f{'Id'},
+ r.eventid as __EventId,
+ e.title as $f{'Title'},
+ e.subtitle as $f{'Subtitle'},
+ SUM(e.duration) as $f{'Duration'},
+ $start as __RecordStart,
+ SUM(State) as __New,
+ r.Type as __Type,
+ COUNT(*) as __Group,
+ SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) as __fulltitle,
+ IF(COUNT(*)>1,0,1) as __IsRecording,
+ e.description as __description
+FROM
+ RECORDS as r,
+ OLDEPG as e
+WHERE
+ $select
+GROUP BY
+ SUBSTRING_INDEX(r.Path, '/', IF(Length(e.subtitle)<=0, $folder + 1, $folder))
+|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+
+ my $sortby = "__fulltitle";
+ $sortby = '__RecordStart'
+ if($text);
+
+ $sortby = $params->{sortby}
+ if(exists $params->{sortby} && grep(/^$params->{sortby}$/i,@{$fields}));
+ $sql .= "order by __IsRecording asc, $sortby";
+ if(exists $params->{desc} && $params->{desc} == 1) {
+ $sql .= " desc"; }
+ else {
+ $sql .= " asc"; }
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+
+ my $param = {
+ sortable => 1,
+ usage => $obj->{CapacityMessage},
+ used => $obj->{CapacityPercent},
+ total => $obj->{CapacityTotal},
+ free => $obj->{CapacityFree},
+ previewcommand => $obj->{previewlistthumbs},
+ getPreview => sub{ return $obj->getPreviewFiles(@_) },
+ };
+ return $console->table($erg, $param);
+}
+
+# ------------------
+sub search {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $text = shift || return $obj->list($watcher,$console);
+ my $params = shift;
+
+ my $search = buildsearch("e.title,e.subtitle,e.description",$text);
+
+ my %f = (
+ 'Id' => umlaute(gettext('Service')),
+ 'Title' => umlaute(gettext('Title')),
+ 'Subtitle' => umlaute(gettext('Subtitle')),
+ 'Duration' => umlaute(gettext('Duration')),
+ );
+
+ my $start = "e.starttime";
+ $start = "UNIX_TIMESTAMP(e.starttime)" if($console->typ eq "HTML");
+
+ my $sql = qq|
+SELECT
+ r.RecordMD5 as $f{'Id'},
+ r.eventid as __EventId,
+ e.title as $f{'Title'},
+ e.subtitle as $f{'Subtitle'},
+ e.duration as $f{'Duration'},
+ $start as __RecordStart ,
+ r.State as __New,
+ r.Type as __Type,
+ 0 as __Group,
+ CONCAT_WS('~',e.title,e.subtitle) as __fulltitle,
+ 1 as __IsRecording,
+ e.description as __description
+FROM
+ RECORDS as r,
+ OLDEPG as e
+WHERE
+ e.eventid = r.eventid
+ AND ( $search )
+|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+
+ my $sortby = "e.starttime";
+ $sortby = $params->{sortby}
+ if(exists $params->{sortby} && grep(/^$params->{sortby}$/i,@{$fields}));
+ $sql .= "order by $sortby";
+ if(exists $params->{desc} && $params->{desc} == 1) {
+ $sql .= " desc"; }
+ else {
+ $sql .= " asc"; }
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+
+ my $param = {
+ sortable => 1,
+ usage => $obj->{CapacityMessage},
+ used => $obj->{CapacityPercent},
+ total => $obj->{CapacityTotal},
+ free => $obj->{CapacityFree},
+ previewcommand => $obj->{previewcommand},
+ getPreview => sub{ return $obj->getPreviewFiles(@_) },
+ };
+ return $console->table($erg, $param);
+}
+
+# ------------------
+sub delete {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $record = shift || return $console->err(gettext("No Recording ID to delete! Please use rdelete 'id'"));
+ my $answer = shift || 0;
+
+ my @rcs = split(/_/, $record);
+ my @todelete;
+ my %rec;
+
+ foreach my $item (@rcs) {
+ if($item =~ /^all\:(\w+)/i) {
+ my $ids = $obj->getGroupIds($1);
+ for(@$ids) {
+ $rec{$_} = 1;
+ }
+ } else {
+ $rec{$item} = 1;
+ }
+ }
+ my @recordings = keys %rec;
+
+ my $sql = sprintf("SELECT r.RecordId,CONCAT_WS('~',e.title,e.subtitle),r.RecordMD5 FROM RECORDS as r,OLDEPG as e WHERE e.eventid = r.eventid and r.RecordMD5 IN (%s) ORDER BY r.RecordId desc", join(',' => ('?') x @recordings));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@recordings)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $data = $sth->fetchall_arrayref(); # Query as array to hold ordering !
+
+ foreach my $recording (@$data) {
+ # Make hash for better reading
+ my $r = {
+ Id => $recording->[0],
+ Title => $recording->[1]
+ };
+
+ if(ref $console and $console->{TYP} eq 'CONSOLE') {
+ $console->table($r);
+ my $confirm = $console->confirm({
+ typ => 'confirm',
+ def => 'y',
+ msg => gettext('Are you sure to delete this recording?'),
+ }, $answer);
+ next if(! $answer eq 'y');
+ }
+
+ debug sprintf('Call delete recording with title "%s", id: %d%s',
+ $r->{Title},
+ $r->{Id},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+
+ $obj->{svdrp}->queue_cmds(sprintf("delr %s",$r->{Id}));
+ push(@todelete,$r->{Title}); # Remember title
+
+ # Remove recordings from request, if found in database
+ my $i = 0;
+ for my $x (@recordings) {
+ if ( $x eq $recording->[2] ) { # Remove known MD5 from user request
+ splice @recordings, $i, 1;
+ } else {
+ $i++;
+ }
+ }
+ }
+
+ $console->err(sprintf(gettext("Recording with number '%s' does not exist in the database!"),
+ join('\',\'',@recordings))) if(ref $console and scalar @recordings);
+
+ if($obj->{svdrp}->queue_cmds('COUNT')) {
+
+ my $msg = sprintf(gettext("Recording '%s' to delete"),join('\',\'',@todelete));
+
+ my $erg = $obj->{svdrp}->queue_cmds("CALL"); # Aufrufen der Kommandos
+
+ my $waiter;
+ if($obj->{svdrp}->err) {
+ $console->err($erg) if(ref $console);
+ } else {
+ if(ref $console) {
+ if($console->typ eq 'HTML') {
+ $waiter = $console->wait($msg,0,1000,'no');
+ }else {
+ $console->msg($msg);
+ }
+ }
+ }
+ sleep(1);
+
+ $obj->readData($watcher,$console,$waiter);
+
+ } else {
+ $console->err(gettext("No recording to delete!"));
+ }
+
+ return 1;
+}
+
+
+sub is_empty_dir {
+ my $dir = shift;
+ local (*DIR);
+ return unless opendir DIR, $dir;
+ while (defined($_ = readdir DIR)) {
+ next if /^\.\.?$/;
+ closedir DIR;
+ return 0;
+ }
+ closedir DIR;
+ return 1;
+}
+
+# ------------------
+sub redit {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $recordid = shift || return $console->err(gettext("No RecordID to edit!"));
+ my $data = shift || 0;
+
+ my $rec;
+ if($recordid) {
+ my $sql = qq|
+SELECT
+ CONCAT_WS('~',e.title,e.subtitle) as title,
+ e.eventid as EventId,
+ r.Path,
+ r.Prio,
+ r.Lifetime
+FROM
+ RECORDS as r,
+ OLDEPG as e
+WHERE
+ e.eventid = r.eventid
+ AND ( r.RecordMD5 = ? )
+|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($recordid)
+ or return $console->err(sprintf(gettext("RecordID '%s' does not exist in the database!"),$recordid));
+ $rec = $sth->fetchrow_hashref();
+ }
+
+ my $file = sprintf("%s/info.vdr", $rec->{Path});
+
+ my $desc;
+ my $channel;
+ my $video;
+ my $audio;
+
+ if(-r $file) {
+ my $text = load_file($file)
+ or $console->err(sprintf(gettext("Can't open file '%s' : %s"),$file,$!));
+
+ foreach my $zeile (split(/[\r\n]/, $text)) {
+ if($zeile =~ /^D\s+(.+)/s) {
+ $desc = $1;
+ $desc =~ s/\|/\r\n/g; # pipe used from vdr as linebreak
+ $desc =~ s/^\s+//; # no leading white space
+ $desc =~ s/\s+$//; # no trailing white space
+ }
+ elsif($zeile =~ /^C\s+(.+)$/s) {
+ $channel = $1;
+ }
+ elsif($zeile =~ /^X\s+1\s+(.+)$/s) {
+ $video = $1;
+ }
+ elsif($zeile =~ /^X\s+2\s+(.+)$/s) {
+ $audio .= "\n" if($audio);
+ $audio .= $1;
+ }
+ }
+ }
+
+ my $marksfile = sprintf('%s/%s', $rec->{Path}, 'marks.vdr');
+ my $marks = (-r $marksfile ? load_file($marksfile) : '');
+
+ $rec->{title} =~s#~+#~#g;
+ $rec->{title} =~s#^~##g;
+ $rec->{title} =~s#~$##g;
+
+ my $mod = main::getModule('CHANNELS');
+
+ my $questions = [
+ 'title' => {
+ msg => gettext('Title of recording'),
+ def => $rec->{title},
+ req => gettext("This is required!"),
+ },
+ 'lifetime' => {
+ typ => 'integer',
+ msg => gettext('Lifetime (0 .. 99)'),
+ def => int($rec->{Lifetime}),
+ check => sub{
+ my $value = shift || 0;
+ if($value >= 0 and $value < 100) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ req => gettext("This is required!"),
+ },
+ 'priority' => {
+ typ => 'integer',
+ msg => gettext('Priority (0 .. 99)'),
+ def => int($rec->{Prio}),
+ check => sub{
+ my $value = shift || 0;
+ if($value >= 0 and $value < 100) {
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ req => gettext("This is required!"),
+ },
+ 'channel' => {
+ typ => 'list',
+ def => $mod->ChannelToPos($channel),
+ choices => sub {
+ my $erg = $mod->ChannelArray('Name');
+ unshift(@$erg, gettext("Undefined"));
+ return $erg;
+ },
+ msg => gettext('Channel'),
+ check => sub{
+ my $value = shift || return;
+
+ if(my $ch = $mod->PosToChannel($value) || $mod->NameToChannel($value) ) {
+ return $ch;
+ } elsif( ! $mod->NameToChannel($value)) {
+ return undef, sprintf(gettext("This channel '%s' does not exist!"),$value);
+ } else {
+ return undef, gettext("This is required!");
+ }
+ },
+ },
+ 'summary' => {
+ msg => gettext("Summary"),
+ def => $desc || '',
+ },
+ 'video' => {
+ msg => gettext('Video'),
+ def => $video,
+ },
+ 'audio' => {
+ msg => gettext('Audio'),
+ def => $audio,
+ },
+ 'marks' => {
+ param => {type => 'text'},
+ msg => gettext("Marks"),
+ def => $marks || '',
+ },
+ ];
+
+ $data = $console->question(gettext("Edit recording"), $questions, $data);
+
+ if(ref $data eq 'HASH') {
+ my $touchVDR = 0;
+ my $dropEPGEntry = 0;
+ my $ChangeRecordingData = 0;
+
+ debug sprintf('Record "%s" is changed%s',
+ $rec->{title},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ if($data->{summary} ne $desc
+ or $data->{channel} ne $channel
+ or $data->{video} ne $video
+ or $data->{audio} ne $audio) {
+ my $out;
+ $data->{summary} =~ s/\r\n/\|/g; # pipe used from vdr as linebreak
+ $data->{summary} =~ s/\n/\|/g; # pipe used from vdr as linebreak
+ $data->{summary} =~ s/^\s+//; # no leading white space
+ $data->{summary} =~ s/\s+$//; # no trailing white space
+ if(-r $file) {
+ my $text = load_file($file)
+ or $console->err(sprintf(gettext("Can't open file '%s' : %s"),$file,$!));
+ foreach my $zeile (split(/[\r\n]/, $text)) {
+ $zeile =~ s/^\s+//;
+ $zeile =~ s/\s+$//;
+ if($zeile =~ /^D\s+(.+)/s) {
+ if(defined $data->{summary} && $data->{summary}) {
+ $out .= "D ". $data->{summary} . "\n";
+ undef $data->{summary};
+ }
+ }
+ elsif($zeile =~ /^C\s+(.+)$/s) {
+ if(defined $data->{channel} && $data->{channel}) {
+ $data->{channel} =~ s/^\s+//;
+ $data->{channel} =~ s/\s+$//;
+ $out .= "C ". $data->{channel} . "\n" if($data->{channel});
+ undef $data->{channel};
+ }
+ }
+ elsif($zeile =~ /^X\s+1\s+(.+)$/s) {
+ if(defined $data->{video} && $data->{video}) {
+ $data->{video} =~ s/^\s+//;
+ $data->{video} =~ s/\s+$//;
+ $out .= "X 1 ". $data->{video} . "\n" if($data->{video});
+ undef $data->{video};
+ }
+ }
+ elsif($zeile =~ /^X\s+2\s+(.+)$/s) {
+ if(defined $data->{audio} && $data->{audio}) {
+ foreach my $line (split(/[\r\n]/, $data->{audio})) {
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
+ next unless($line);
+ $out .= "X 2 ". $line . "\n";
+ }
+ undef $data->{audio};
+ }
+ } else {
+ $out .= $zeile . "\n" if($zeile);
+ }
+ }
+ }
+ if(defined $data->{channel} && $data->{channel}) {
+ $data->{channel} =~ s/^\s+//;
+ $data->{channel} =~ s/\s+$//;
+ $out .= "C ". $data->{channel} . "\n" if($data->{channel});
+ }
+ if(defined $data->{summary} && $data->{summary}) {
+ $out .= "D ". $data->{summary} . "\n";
+ }
+ if(defined $data->{video} && $data->{video}) {
+ $data->{video} =~ s/^\s+//;
+ $data->{video} =~ s/\s+$//;
+ $out .= "X 1 ". $data->{video} . "\n" if($data->{video});
+ }
+ if(defined $data->{audio} && $data->{audio}) {
+ foreach my $line (split(/[\r\n]/, $data->{audio})) {
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
+ $out .= "X 2 ". $line . "\n" if($line);
+ }
+ }
+
+ save_file($file, $out)
+ or return $console->err(sprintf(gettext("Can't write file '%s' : %s"),$file,$!));
+ $dropEPGEntry = 1;
+ }
+
+ if($data->{marks} ne $marks) {
+ save_file($marksfile, $data->{marks})
+ or return $console->err(sprintf(gettext("Can't write file '%s' : %s"),$marksfile,$!));
+ $ChangeRecordingData = 1;
+ }
+
+
+ if($data->{lifetime} ne $rec->{Lifetime}
+ or $data->{priority} ne $rec->{Prio}) {
+
+ my @options = split('\.', $rec->{Path});
+
+ $options[-2] = sprintf("%02d",$data->{lifetime})
+ if($data->{lifetime} ne $rec->{Lifetime});
+
+ $options[-3] = sprintf("%02d",$data->{priority})
+ if($data->{priority} ne $rec->{Prio});
+
+ my $newPath = join('.', @options);
+
+ move($rec->{Path}, $newPath)
+ or return $console->err(sprintf(gettext("Recording: '%s', can't move to '%s' : %s"),$rec->{title},$newPath,$!));
+
+ $rec->{Path} = $newPath;
+ $touchVDR = 1;
+ $ChangeRecordingData = 1;
+ }
+
+ $data->{title} =~s#~+#~#g;
+ $data->{title} =~s#^~##g;
+ $data->{title} =~s#~$##g;
+
+ if($data->{title} ne $rec->{title}) {
+
+ # Rename auf der Platte
+ my $newPath = sprintf('%s/%s/%s', $obj->{videodir}, $obj->translate($data->{title}),basename($rec->{Path}));
+
+ my $parentnew = dirname($newPath);
+ unless( -d $parentnew) {
+ mkpath($parentnew)
+ or return $console->err(sprintf(gettext("Recording: '%s', can't mkpath: '%s' : %s"),$rec->{title},$parentnew,$!));
+ }
+
+ move($rec->{Path},$newPath)
+ or return $console->err(sprintf(gettext("Recording: '%s', can't move to '%s' : %s"),$rec->{title},$data->{title},$!));
+
+ my $parentold = dirname($rec->{Path});
+ if($obj->{videodir} ne $parentold
+ and -d $parentold
+ and is_empty_dir($parentold)) {
+ rmdir($parentold)
+ or return $console->err(sprintf(gettext("Recording: '%s', can't remove '%s' : %s"),$rec->{title},$parentold,$!));
+ }
+
+ $ChangeRecordingData = 1;
+ $dropEPGEntry = 1;
+ $touchVDR = 1;
+ }
+
+ if($dropEPGEntry) { # Delete EpgOld Entrys
+ my $sth = $obj->{dbh}->prepare('DELETE FROM OLDEPG WHERE eventid = ?');
+ $sth->execute($rec->{EventId})
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+
+ if($ChangeRecordingData) {
+ my $sth = $obj->{dbh}->prepare('DELETE FROM RECORDS WHERE RecordMD5 = ?');
+ $sth->execute($recordid)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ }
+
+ if($touchVDR) { #Ab 1.3.11 resync with touch /video/.update
+ touch($obj->{videodir}."/.update");
+ }
+
+ my $waiter;
+ if(ref $console) {
+ if($console->typ eq 'HTML') {
+ $waiter = $console->wait(gettext('Recording is edited!'),0,1000,'no');
+ }else {
+ $console->msg(gettext('Recording is edited!'));
+ }
+ }
+ $obj->readData($watcher,$console,$waiter);
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+ }
+
+ return 1;
+}
+
+# ------------------
+# Load Reccmds's
+sub _loadreccmds {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ unless($obj->{reccmds}) {
+ $obj->{reccmds} = [];
+ if(-r $obj->{commandfile} and my $text = load_file($obj->{commandfile})) {
+ foreach my $zeile (split(/\n/, $text)) {
+ if($zeile !~ /^\#/ and $zeile !~ /^$/ and $zeile !~ /true/) {
+ push(@{$obj->{reccmds}}, $zeile);
+ }
+ }
+ }
+ }
+}
+
+# ------------------
+sub conv {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $data = shift || 0;
+
+ $obj->_loadreccmds;
+
+ unless(scalar @{$obj->{reccmds}}) {
+ $console->err(gettext('No reccmds.conf on your System!'));
+ return 1;
+ }
+
+ unless($data) {
+ $console->err(gettext("Please use rconvert 'cmdid_rid'"));
+ unshift(@{$obj->{reccmds}}, ['Descr.', 'Command']);
+ $console->table($obj->{reccmds});
+ $obj->list($watcher, $console);
+ }
+
+ my ($cmdid, $recid) = split(/[\s_]/, $data);
+ my $cmd = (split(':', $obj->{reccmds}->[$cmdid-1]))[-1] || return $console->err(gettext("I can't find this CommandID"));
+ my $path = $obj->IdToPath($recid) || return $console->err(gettext("I can't find this RecordID"));
+
+ debug sprintf('Call command "%s" on record "%s"%s',
+ $cmd,
+ $path,
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ my $call = "$cmd \"$path\"";
+ my $output = `$call`;
+ if( $? >> 8 > 0) {
+ $console->message(sprintf(gettext("Sorry! Call %s %s Error output: %s"), $cmd, $path, $output));
+ } else {
+ $console->message(sprintf(gettext("Call %s %s With output: %s"), $cmd, $path, $output));
+ }
+ $console->link({
+ text => gettext("Back to recordings list"),
+ url => "?cmd=rlist",
+ }) if($console->typ eq 'HTML');
+ return 1;
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $lastReportTime = shift;
+
+ my $sql = qq|
+SELECT
+ r.RecordId as __Id,
+ r.eventid as __EventId,
+ e.title,
+ e.subtitle,
+ FROM_UNIXTIME(e.Duration,'%h:%i:%s') as Duration,
+ e.starttime as __RecordStart
+FROM
+ RECORDS as r,
+ OLDEPG as e
+WHERE
+ e.eventid = r.eventid
+ and UNIX_TIMESTAMP(e.starttime) > ?
+ORDER BY
+ e.starttime asc
+|;
+ my $fields = fields($obj->{dbh}, $sql);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($lastReportTime)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+ unshift(@$erg, $fields);
+ return {
+ message => sprintf(gettext('%d new recordings since last report time %s'),
+ (scalar @$erg -1), scalar localtime($lastReportTime)),
+ table => $erg,
+ };
+}
+
+
+# ------------------
+sub IdToPath {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return undef;
+
+ my $sth = $obj->{dbh}->prepare('select Path from RECORDS where RecordMD5 = ?');
+ $sth->execute($id)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ return $sth->fetchrow_hashref()->{Path};
+}
+
+# ------------------
+sub getPreviewFiles {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $id = shift || return error ('No EventID!' );
+
+ # look for pictures
+ my $outdir = sprintf('%s/%lu_shot', $obj->{previewimages}, $id);
+ if(my @previews = glob("$outdir/[0-9]*.jpg")) {
+ splice(@previews,$obj->{previewcount},scalar(@previews))
+ if(scalar(@previews) > $obj->{previewcount});
+ map {
+ $_ =~ s/^$obj->{previewimages}/previewimages/
+ } @previews;
+ return \@previews;
+ } else {
+ return undef;
+ }
+}
+
+# ------------------
+sub getGroupIds {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $recid = shift || return error ('No Record ID!' );
+
+ my $epgid = getDataById($recid, 'RECORDS', 'RecordMD5');
+ if(!$epgid) {
+ error sprintf("Can't find Record for id %s!", $recid);
+ return;
+ }
+ my $epgdata = main::getModule('EPG')->getId($epgid->{eventid});
+
+ my $text = $epgdata->{title};
+
+ my $deep = 1;
+ my $folder = scalar (my @a = split('/',$obj->{videodir})) + 1;
+
+ my $select = "e.eventid = r.eventid";
+ if($text) {
+ $deep = scalar (my @c = split('~',$text));
+ $folder += $deep;
+ $deep += 1;
+
+ $text =~ s/\'/\\\'/sg;
+ $text =~ s/%/\\%/sg;
+ $select .= qq|
+AND (
+ SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) LIKE '$text'
+ OR
+ SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) LIKE '$text~%'
+)
+|;
+
+ }
+
+ my $sql = qq|
+SELECT
+ r.RecordMD5
+FROM
+ RECORDS as r,
+ OLDEPG as e
+WHERE
+ $select
+GROUP BY
+ SUBSTRING_INDEX(r.Path, '/', IF(Length(e.subtitle)<=0, $folder + 1, $folder))
+|;
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+
+ my $ret = [];
+ for(@{$erg}) {
+ push(@$ret, $_->[0]);
+ }
+ return $ret;
+}
+
+
+# ------------------
+sub translate {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $title = shift || return error ('No Title in translate!');
+ my $vfat = shift || $obj->{vfat};
+
+ if($vfat eq 'y')
+ {
+ $title =~ s/([^üäößa-z0-9\&\!\-\s\.\@\~\,\(\)\%\+])/sprintf('#%X', ord($1))/seig;
+ $title =~ s/[^üäößa-z0-9\!\&\-\#\.\@\~\,\(\)\%\+]/_/sig;
+ # Windows can't handle '.' at the end of directory names
+ $title =~ s/(\.$)/\#2E/sig;
+ $title =~ s/(\.~)/\#2E~/sig;
+ } else {
+ $title =~ s/\'/\x01/sg;
+ $title =~ s/\//\x02/sg;
+ $title =~ s/ /_/sg;
+ }
+ $title =~ s/~/\//sg;
+
+ return $title;
+}
+
+# ------------------
+# Length of recording in seconds,
+# return value as integer
+sub _recordinglength {
+# ------------------
+ my $obj = shift || return 0, error ('No Object!' );
+ my $path = shift || return 0, error ('Missing path from recording!' );
+
+ my $f = sprintf("%s/index.vdr", $path);
+ my $r = sprintf("%s/001.vdr", $path);
+
+ # Pseudo Recording (DIR)
+ return 0 if(! -r $f and ! -s $r);
+
+ if(-r $f) {
+ my $bytes = stat($f)->size;
+ return int(($bytes / 8) / 25);
+ } else {
+ error sprintf("Couldn't read : '%s'", $f);
+ }
+ return 0;
+}
+
+# ------------------
+sub converttitle {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $title = shift || return error ('No Title in translate!');
+ my $vfat = shift || $obj->{vfat};
+
+ if($vfat eq 'y') {
+ $title =~ s/\#([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ $title =~ s/\x03/:/g; # See backward compat.. at recordings.c
+ }
+
+ $title =~ s/\x01/\'/g;
+ $title =~ s/\x02/\\/g;
+
+ $title =~ s/_/ /g;
+ $title =~ s/\//~/g;
+
+ return $title;
+}
+
+# ------------------
+sub suggest { # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+ my $params = shift;
+
+ if($search) {
+ my $sql = qq|
+ SELECT
+ e.title as title
+ FROM
+ RECORDS as r,
+ OLDEPG as e
+ WHERE
+ e.eventid = r.eventid
+ AND ( e.title LIKE ? )
+ GROUP BY
+ title
+UNION
+ SELECT
+ e.subtitle as title
+ FROM
+ RECORDS as r,
+ OLDEPG as e
+ WHERE
+ e.eventid = r.eventid
+ AND ( e.subtitle LIKE ? )
+ GROUP BY
+ title
+ORDER BY
+ title
+LIMIT 25
+ |;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute('%'.$search.'%','%'.$search.'%')
+ or return error "Can't execute query: $sth->errstr.";
+ my $result = $sth->fetchall_arrayref();
+ $console->table($result)
+ if(ref $console && $result);
+ }
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/REMOTE.pm b/lib/XXV/MODULES/REMOTE.pm
new file mode 100644
index 0000000..dab705b
--- /dev/null
+++ b/lib/XXV/MODULES/REMOTE.pm
@@ -0,0 +1,279 @@
+package XXV::MODULES::REMOTE;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'REMOTE',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module emulate a remote control.'),
+ Version => '0.01',
+ Date => '10.10.2004',
+ Author => 'xpix',
+ Level => 'user',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ monitor => {
+ description => gettext('Grab video framebuffer, as preview on remotecontrol.'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ commands => {
+ description => gettext('The path to the commands.conf'),
+ default => '/var/lib/vdr/commands.conf',
+ type => 'file',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ remote => {
+ description => gettext("Display ir remote 'cmd'"),
+ short => 'r',
+ callback => sub{ $obj->remote(@_) },
+ DenyClass => 'remote',
+ },
+ switch => {
+ description => gettext("Switch to channel 'cid'"),
+ short => 'sw',
+ callback => sub{ $obj->switch(@_) },
+ DenyClass => 'remote',
+ },
+ command => {
+ description => gettext("Call the command 'cid'"),
+ short => 'cmd',
+ callback => sub{ $obj->command(@_) },
+ DenyClass => 'remote',
+ },
+ cmdlist => {
+ description => gettext("List the commands"),
+ short => 'cmdl',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'remote',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ main::after(sub{
+ $obj->{svdrp} = main::getModule('SVDRP');
+ unless($obj->{svdrp}) {
+ panic ("Can't get modul SVDRP");
+ return 0;
+ }
+
+ $obj->{CMDS} = $obj->parse();
+ return 1;
+ }, "REMOTE: Parse Commandfile ...");
+
+ return 1;
+}
+
+# ------------------
+sub parse {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0
+ unless (exists $obj->{commands});
+
+ if(! -r $obj->{commands}) {
+ error (sprintf("can't open file '%s' : %s",$obj->{commands},$!));
+ return 0;
+ }
+
+ my $cmds = load_file($obj->{commands});
+
+ my $c = 0;
+ my $ret = {};
+ foreach my $zeile (split("\n", $cmds)) {
+ next if($zeile =~ /^\#/);
+ my ($cmd, $batch) = split('\s*\:\s*', $zeile);
+
+ $ret->{$c++} = {
+ cmd => $cmd,
+ bat => $batch,
+ } if($cmd and $batch);
+ }
+ return $ret;
+}
+
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $cmds = $obj->parse();
+
+ my @list = (['__Id', 'Name', 'Cmd']);
+ foreach my $id (sort {$a <=> $b} keys %$cmds) {
+ push(@list, [$id, $cmds->{$id}->{cmd}, $cmds->{$id}->{bat}]);
+ }
+
+ return $console->table(\@list);
+}
+
+# ------------------
+sub command {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $command = shift || return error ('No Command!');
+ my $cmds = $obj->parse();
+
+ return $console->err(gettext('This cmd id does not exist!'))
+ unless(exists $cmds->{$command});
+
+ $console->message(my $msg = sprintf(gettext('Try to start command: %s with cmd: %s'),
+ $cmds->{$command}->{cmd}, $cmds->{$command}->{bat}));
+
+ lg $msg;
+
+ my $out;
+ open(README, "$cmds->{$command}->{bat} 2>&1 |") or return error("Can't run program: $!");
+ while(<README>) {
+ $out .= $_;
+ }
+ close(README);
+ return $console->message($out, {
+ tags => {
+ first => "<pre>",
+ last => "</pre>"
+ }
+ } );
+}
+
+
+# ------------------
+sub remote {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $command = shift;
+
+ debug sprintf('Call remote with command "%s"%s',
+ $command,
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ unless($command) {
+ my $mod = main::getModule('GRAB');
+ my $params = {
+ width => $mod->{xsize},
+ height => $mod->{ysize},
+ monitor => $obj->{monitor} eq "y" ? 1 : 0
+ };
+ return $console->remote(undef, $params);
+ } else {
+ # the svdrp module
+ my $svdrp = $obj->{svdrp};
+
+ my $translate = {
+ '<' => 'Channel-',
+ '>' => 'Channel+',
+ '+' => 'Volume+',
+ '-' => 'Volume-',
+ '>>' => 'FastFwd',
+ '<<' => 'FastRew',
+ 'VolumePlus' => 'Volume+',
+ 'VolumeMinus' => 'Volume-',
+ 'Null' => '0',
+ };
+
+ $command = $translate->{$command}
+ if(exists $translate->{$command});
+
+ # the command
+ my $cmd = sprintf('hitk %s', $command);
+ my $erg = $svdrp->command($cmd);
+
+ $console->msg($erg, $svdrp->err)
+ if(ref $console);
+ }
+ return 1;
+}
+
+# ------------------
+sub switch {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $channel = shift || '';
+
+ lg sprintf('Call switch with channel "%s"%s',
+ $channel,
+ ( ref $console && $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ # the svdrp module
+ my $svdrp = $obj->{svdrp};
+
+ # the command
+ my $cmd = sprintf('chan %s', $channel);
+ my $erg = $svdrp->command($cmd);
+
+ my ($ret) = $erg->[1] =~ /^\d{3}\s*(.+)/s;
+
+ $console->msg($erg, $svdrp->err)
+ if(ref $console);
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+
+
+ return $ret;
+}
+
+1;
diff --git a/lib/XXV/MODULES/REPORT.pm b/lib/XXV/MODULES/REPORT.pm
new file mode 100644
index 0000000..b859c95
--- /dev/null
+++ b/lib/XXV/MODULES/REPORT.pm
@@ -0,0 +1,288 @@
+package XXV::MODULES::REPORT;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'REPORT',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module read in every module the status information and display this. Also this module send this informations e.g. as mail report.'),
+ Version => '0.01',
+ Date => '19.04.2005',
+ Author => 'xpix',
+ Level => 'user',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ interval => {
+ description => gettext('Time in hours to send the report.'),
+ default => 6,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ host => {
+ description => gettext('Used host of referred link inside reports.'),
+ default => main::getModule('STATUS')->name,
+ type => 'host',
+ },
+ },
+ Commands => {
+ report => {
+ description => gettext("Display the report screen 'modname'"),
+ short => 'rp',
+ callback => sub{ $obj->report(@_) },
+ },
+ request => {
+ description => gettext("Display the actual news site 'typ'"),
+ short => 'req',
+ callback => sub{ $obj->request(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # the big Config
+ $self->{CONFIG} = $attr{'-config'};
+
+ # the dbh handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{LastReportTime} = time;
+
+ # Interval to send report
+ Event->timer(
+ interval => $self->{interval}*3600,
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ $self->report();
+ $self->{LastReportTime} = time;
+ },
+ );
+
+ # The Initprocess
+ my $erg = $self->init or return error('Problem to initialize module');
+
+ # Initiat after load modules ...
+ main::after(sub{
+ my $start = main::getStartTime;
+ $self->news(
+ sprintf(gettext('Restart the xxv system at: %s!'), datum($start,'voll')),
+ undef,
+ undef,
+ undef,
+ 'important',
+ );
+ return 1;
+ }, "Send restart Message to News channel ...");
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ # Load the NEWS Plugins ...
+ my @mods = glob($obj->{paths}->{NEWSMODS}.'/*.pm');
+
+ # Try to use the news plugins
+ foreach my $module (reverse @mods) {
+ my $moduleName = 'XXV::OUTPUT::NEWS::'.(split('\.',(split('/', $module))[-1]))[0];
+
+ # make an object for the module
+ eval "use $moduleName";
+ error $@ if $@;
+ my $mod = $moduleName->new(
+ -config => $obj->{CONFIG},
+ -dbh => $obj->{dbh},
+ -paths => $obj->{paths},
+ -host => $obj->{host},
+ );
+
+ unless($mod) {
+ error('Problem to load Module %s!',$moduleName);
+ next;
+ }
+
+ $obj->{NEWSMODS}->{$moduleName} = $mod;
+
+ main::addModule($moduleName, $obj->{NEWSMODS}->{$moduleName});
+
+ debug sprintf("Load NEWS Module %s = %s\n",
+ $moduleName,
+ (ref $obj->{NEWSMODS}->{$moduleName})
+ ? $obj->{NEWSMODS}->{$moduleName}->{MOD}->{Version}
+ : 'Problem!');
+ }
+
+ return 1;
+}
+
+
+# ------------------
+sub report {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $modulename = shift || '';
+
+ my $mods = main::getModules();
+ my $cfg = main::getModule('CONFIG')->{config};
+
+ # Look for status entry in modCfg and call his
+ my $result = {};
+ foreach my $modName (sort keys %{$mods}) {
+ my $modCfg = $mods->{$modName}->{MOD};
+ next if($modulename and uc($modulename) ne $modCfg->{Name});
+ next if(exists $mods->{$modName}->{active} and $cfg->{$modCfg->{Name}}->{active} eq 'n');
+ if(exists $modCfg->{Status} and ref $modCfg->{Status} eq 'CODE') {
+ $result->{$modCfg->{Name}} = $modCfg->{Status}($watcher, $console, $obj->{LastReportTime});
+ }
+ }
+
+ $console->table($result, {hide_HeadLine => 1, hide_HeadRow => 1, maxwidth => 80})
+ if(ref $console);
+
+ return 1;
+}
+
+# ------------------
+sub news {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $title = shift || return error ('No Title!' );
+ my $text = shift || '';
+ my $cmd = shift || '';
+ my $id = shift || '';
+ my $levname = shift || 'harmless'; # Level for how important is this news?
+
+ # convert Levelname to integer
+ my $lev = $obj->scala($levname)
+ || return error('Problem to analyze Level!');
+
+ my $url = sprintf("http://%s:%s/", $obj->{host}, main::getModule('HTTPD')->{Port});
+ $url = sprintf("%s?cmd=%s&data=%s", $url, $cmd, $id)
+ if($cmd && $id);
+
+ my $news = {
+ AddDate => time,
+ Title => $title,
+ Text => $text,
+ Cmd => $cmd,
+ Id => $id,
+ Url => $url,
+ Level => $lev,
+ LevelName => $levname,
+ };
+
+ # Send to all activated News modules
+ foreach my $modName (sort keys %{$obj->{NEWSMODS}}) {
+
+ # Active?
+ next if($obj->{NEWSMODS}->{$modName}->{active} ne 'y');
+
+ # Level correct?
+ next if(exists $obj->{NEWSMODS}->{$modName}->{level}
+ and $obj->{NEWSMODS}->{$modName}->{level} >= $lev);
+
+ # Do to send (first read and then send)
+ $obj->{NEWSMODS}->{$modName}->read($news);
+ }
+
+}
+
+# ------------------
+sub request {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!' );
+ my $console = shift || return error ('No Console!' );
+ # To this time you can set on
+ # cmd=request&data=rss&ver=2 or
+ # cmd=request&data=mail
+ # ...
+ my $typ = shift || return error ('No Typ!' );
+ my $params = shift || {};
+
+ my ($mod) = grep(/${typ}$/i, keys %{$obj->{NEWSMODS}});
+
+ return $console->err(sprintf(gettext("Sorry, but this type '%s' does not exist on this system!"), $typ))
+ unless($mod);
+
+ return $console->err(gettext("Sorry, but this module is not active!"))
+ unless($obj->{NEWSMODS}->{$mod}->{active} eq 'y');
+
+ $console->{noFooter} = 1;
+
+ return $console->out(
+ $obj->{NEWSMODS}->{$mod}->req($params),
+ $obj->{NEWSMODS}->{$mod}->{TYP}
+ );
+}
+
+
+# ------------------
+sub scala {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $typ = shift || return 10;
+
+ $obj->{SCALA} = {
+ 'harmless' => 10,
+ 'interesting' => 30,
+ 'veryinteresting'=> 50,
+ 'important' => 70,
+ 'veryimportant' => 100,
+ } unless(exists $obj->{SCALA});
+
+ if($typ and exists $obj->{SCALA}->{$typ}) {
+ return $obj->{SCALA}->{$typ};
+ } else {
+ return error("Level %s does not exist! Please use %s", $typ, join(',', keys %{$obj->{SCALA}}));
+ }
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/ROBOT.pm b/lib/XXV/MODULES/ROBOT.pm
new file mode 100644
index 0000000..d39e0ec
--- /dev/null
+++ b/lib/XXV/MODULES/ROBOT.pm
@@ -0,0 +1,180 @@
+package XXV::MODULES::ROBOT;
+use strict;
+
+use Tools;
+use Locale::gettext;
+
+$SIG{CHLD} = 'IGNORE';
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'ROBOT',
+ Prereq => {
+# 'WWW::Mechanize' => 'Handy web browsing in a Perl object ',
+ },
+ Description => gettext('This module register and run robots to fetch data from internet.'),
+ Version => '0.01',
+ Date => '06.09.2004',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ robot => {
+ description => gettext("Start a robots 'rname'"),
+ short => 'ro',
+ callback => sub{ $obj->start(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ return $self;
+}
+
+# ------------------
+sub saveRobot {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $rname = shift || return error ('No robot Name!' );
+ my $rsub = shift || return error ('No Robot sub!' );
+
+ return error("$rname is not a Code Reference!'")
+ unless(ref $rsub eq 'CODE');
+
+ $obj->clean( $rname );
+ $obj->{robots}->{$rname} = $rsub;
+ return $obj->{robots}->{$rname};
+}
+
+# ------------------
+sub register {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $rname = shift || return error ('No robot Name!' );
+ my @args = @_;
+
+ return error("$rname is not a Robot!'")
+ unless(ref $obj->{robots}->{$rname} eq 'CODE');
+
+ push(@{$obj->{jobs}->{$rname}}, [@args]);
+}
+
+# ------------------
+sub start {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $rname = shift || return error ('No Robot Name to start!');
+ my $watcher = shift;
+ my $console = shift;
+ my $endcb = shift;
+
+ lg sprintf('Start Robots ....');
+
+ unless(exists $obj->{jobs}->{$rname}) {
+ return error("No Robot with name $rname is registered!");
+ }
+
+ # fork and forget
+ defined(my $child = fork()) or die "Can't fork: $!";
+ if($child == 0) {
+ $obj->{dbh}->{InactiveDestroy} = 1;
+ # create a new browser
+ my $count = 0;
+ foreach my $args (@{$obj->{jobs}->{$rname}}) {
+ my ($result, $error);
+ lg sprintf('robot callback %s started (%d)....', $rname, $count);
+ eval {
+ ($result, $error) = &{$obj->{robots}->{$rname}}(@$args);
+ };
+ $error = $@ if($@);
+ if($result) {
+ lg sprintf("robot callback %s successfully ended!", $rname);
+ } else {
+ error sprintf("robot callback %s failed! : %s ", $rname, $error);
+ }
+ $count++;
+ }
+ &$endcb()
+ if(ref $endcb eq 'CODE');
+ exit(0);
+ }
+}
+
+# ------------------
+sub clean {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $rname = shift || return error ('No Robot Name to clean!');
+ delete $obj->{jobs}->{$rname};
+}
+
+
+# ------------------
+sub result {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $rname = shift || return error ('No robot Name!' );
+
+ return $obj->{result}->{$rname};
+
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $rname = shift;
+
+ return 1 unless(ref $console);
+
+ if($rname) {
+ $console->table($obj->{result}->{$rname});
+ } else {
+ $console->table($obj->{result});
+ }
+}
+
+
+
+1;
diff --git a/lib/XXV/MODULES/SHARE.pm b/lib/XXV/MODULES/SHARE.pm
new file mode 100644
index 0000000..3b5d06c
--- /dev/null
+++ b/lib/XXV/MODULES/SHARE.pm
@@ -0,0 +1,280 @@
+package XXV::MODULES::SHARE;
+use strict;
+
+use Tools;
+use Locale::gettext;
+use vars qw($AUTOLOAD);
+
+
+$SIG{CHLD} = 'IGNORE';
+
+# ------------------
+sub AUTOLOAD {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $cmd = (split('::', $AUTOLOAD))[-1];
+ return if($cmd eq 'DESTROY');
+
+ # Den Hash per Hand nachpflegen
+ # bis zum nächsten Refresh ...
+ if($cmd eq 'setEventLevel' and exists $obj->{EventLevels} and ref $obj->{EventLevels} eq 'HASH') {
+ $obj->{EventLevels}->{$_[0]}->{Level} = $_[1];
+ }
+
+ if($obj->{SOAP} && $obj->{active} eq 'y') {
+ my $erg = $obj->CmdToSoap($obj->{SOAP}, $cmd, $obj->{SessionId}, @_);
+ return $erg;
+ }
+}
+
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'SHARE',
+ Prereq => {
+ 'SOAP::Lite' => 'Client and server side SOAP implementation.',
+ },
+ Description => gettext('This module send and read shared data from SOAP Server.'),
+ Version => '0.03',
+ Date => '30.06.2006',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'n',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ uri => {
+ description => gettext('The uri identifies the class on the server. The url (with port) for the XXV-SOAP-Server Address.'),
+ default => 'http://xpix.dyndns.org:81/XXV/Server',
+ type => 'url',
+ required => gettext('This is required!'),
+ },
+ proxy => {
+ description => gettext('The proxy identifies the CGI script that provides access to the class, Is simply the address of the server to contact that provides the methods.'),
+ default => 'http://xpix.dyndns.org:81/',
+ type => 'url',
+ required => gettext('This is required!'),
+ },
+ interval => {
+ description => gettext('How often shared data are to be updated (in seconds).'),
+ default => 3600,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ topten => {
+ description => gettext("Display the TopTen list of timers."),
+ short => 't10',
+ callback => sub{ $obj->TopTen(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # The Initprocess
+ $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ $obj->{SessionId} = $obj->generateUniqueId
+ unless($obj->{SessionId});
+
+ main::after(sub{
+
+ $obj->{SOAP} = $obj->ConnectToSOAP($obj->{SessionId});
+
+ unless($obj->{SOAP}) {
+ error("Can't connect to SOAP server %s!", $obj->{uri});
+ return 0;
+ } else {
+ $obj->getSoapData();
+ Event->timer(
+ interval => $obj->{interval},
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{ $obj->getSoapData() },
+ );
+ }
+ return 1;
+ }, "SHARE: Connect To SOAP Server ...",4) if($obj->{active} eq 'y');
+
+ return 1;
+}
+
+# ------------------
+sub getSoapData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return unless($obj->{SOAP} and $obj->{active} eq 'y');
+ lg 'Start interval share to get for Levels!';
+ $obj->{EventLevels} = $obj->getEventLevels();
+ lg 'Start interval share to get for TopTen!';
+ $obj->{TopTen} = $obj->getTopTen(1000);
+}
+
+
+# ------------------
+sub generateUniqueId {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $sessionId;
+ for(my $i=0 ; $i< 16 ;)
+ {
+ my $j = chr(int(rand(127)));
+
+ if($j =~ /[a-zA-Z0-9]/)
+ {
+ $sessionId .=$j;
+ $i++;
+ }
+ }
+ return $sessionId;
+}
+
+# ------------------
+sub ConnectToSOAP {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $sid = shift || $obj->{SessionId} || return error ('No SesionID!' );
+ my $uri = shift || $obj->{uri};
+ my $prx = shift || $obj->{proxy};
+
+ return undef
+ if($obj->{active} ne 'y');
+
+ my $soap = SOAP::Lite
+ ->uri($uri)
+ ->proxy($prx, timeout => 5)
+ ->on_fault(sub{});
+
+ my $usrkey;
+ if($soap) {
+ $usrkey = $obj->CmdToSoap($soap,'getUsrKey',$obj->{SessionId}) or error "Can't get user key";
+ error "Response contain wrong answer" if($usrkey ne $obj->{SessionId});
+ }
+
+ return $soap
+ if($usrkey eq $obj->{SessionId});
+
+ return undef;
+}
+
+# ------------------
+sub getEventLevel {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $eid = shift || return;
+
+ return unless($obj->{EventLevels});
+
+ return $obj->{EventLevels}->{$eid}->{Level}
+ if(exists $obj->{EventLevels}->{$eid});
+}
+
+# ------------------
+sub TopTen {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $anzahl = shift || 10;
+
+ $obj->{TopTen} = $obj->getTopTen(1000)
+ unless($obj->{TopTen});
+
+ my $data = $obj->{TopTen};
+
+ my $epg = main::getModule('EPG');
+ my $tim = main::getModule('TIMERS');
+ my $can = main::getModule('CHANNELS');
+
+ my @fields = ('eventid','title','subtitle','description','channel_id','starttime','video','audio');
+ my @query = @fields;
+
+ @query = ('eventid','title','subtitle','description','channel_id','UNIX_TIMESTAMP(starttime) as starttime','video','audio')
+ if($console->typ eq 'HTML');
+
+
+ my $out = [];
+ foreach my $entry (@$data) {
+ my $edata = $epg->getId( $entry->[0], join(", ", @query) );
+ next unless(keys %$edata);
+ push(@$out, [ @fields, 'Rank', '__Level', '__Count' ])
+ unless(scalar @$out);
+ my @val = map { $edata->{$_} } @fields;
+ push(@$out, [ @val, $entry->[3], $entry->[1],$entry->[2] ]);
+ last if(scalar @$out > $anzahl);
+ }
+
+ return $console->table($out, {
+ channels => $can->ChannelHash('Id'),
+ timers => $tim->getEpgIds
+ });
+}
+
+# ------------------
+sub CmdToSoap {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $soap = shift || return error ('No SOAP!' );
+ my $cmd = shift || return error ('No Command!' );
+ my @arg = @_;
+
+ lg(sprintf("CmdToSoap : %s - %s",$cmd, join(", ",@arg)));
+
+ $obj->{CAN}->{$cmd} = $soap->can($cmd)
+ unless(exists $obj->{CAN}->{$cmd});
+
+ my $res = eval "\$soap->$cmd(\@arg)";
+ $@ ? return error('SyntaxError: $@') :
+ defined($res) && $res->fault ?
+ return error('Fault %s-%s', $res->faultcode, $res->faultstring) :
+ !$soap->transport->is_success ?
+ return error('Transport Error: %s', $soap->transport->status) :
+ return $res->result;
+}
+
+1;
diff --git a/lib/XXV/MODULES/STATUS.pm b/lib/XXV/MODULES/STATUS.pm
new file mode 100644
index 0000000..f566855
--- /dev/null
+++ b/lib/XXV/MODULES/STATUS.pm
@@ -0,0 +1,771 @@
+package XXV::MODULES::STATUS;
+use strict;
+
+use Tools;
+use Socket;
+use Sys::Hostname;
+use Locale::gettext;
+use File::Basename;
+use File::Find;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'STATUS',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module analyze your system and print the result.'),
+ Version => '0.45',
+ Date => '06.09.2005',
+ Author => 'xpix',
+ Level => 'user',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ whoBinary => {
+ description => sprintf(gettext("Path of command '%s'"),'who'),
+ default => "/usr/bin/who",
+ type => "file",
+ required => gettext('This is required!'),
+ },
+ wcBinary => {
+ description => sprintf(gettext("Path of command '%s'"),'wc'),
+ default => "/usr/bin/wc",
+ required => gettext('This is required!'),
+ type => "file",
+ },
+ dfBinary => {
+ description => sprintf(gettext("Path of command '%s'"),'df'),
+ default => "/bin/df",
+ required => gettext('This is required!'),
+ type => "file",
+ },
+ interval => {
+ description => gettext('Interval in seconds to remember data'),
+ default => 60,
+ type => "integer",
+ },
+ history => {
+ description => gettext('How long to remember the historical data in hours'),
+ default => 3,
+ type => "integer",
+ },
+ font => {
+ description => gettext('True type font to draw image text.'),
+ default => 'Vera.ttf',
+ type => 'list',
+ choices => $obj->findttf,
+ },
+ graphic => {
+ description => gettext('Show collected data as diagram?'),
+ default => 'y',
+ type => 'confirm',
+ },
+ },
+ Commands => {
+ all => {
+ description => gettext('Display all relevant informations about this system'),
+ short => 'sa',
+ callback => sub{
+ my ($watcher, $console) = @_;
+ $console->setCall('vitals');
+ $obj->vitals(@_);
+
+ $console->setCall('filesys');
+ $obj->filesys(@_);
+
+ $console->setCall('memory');
+ $obj->memory(@_);
+
+ $console->setCall('network');
+ $obj->network(@_);
+
+ $console->setCall('hardware');
+ $obj->hardware(@_);
+ },
+ },
+ vitals => {
+ description => gettext('Display the vitals informations'),
+ short => 'sv',
+ callback => sub{ $obj->vitals(@_) },
+ },
+ network => {
+ description => gettext('Display the network informations'),
+ short => 'sn',
+ callback => sub{ $obj->network(@_) },
+ },
+ hardware => {
+ description => gettext('Display the hardware informations'),
+ short => 'sh',
+ callback => sub{ $obj->hardware(@_) },
+ },
+ memory => {
+ description => gettext('Display the memory informations'),
+ short => 'sm',
+ callback => sub{ $obj->memory(@_) },
+ },
+ filesys => {
+ description => gettext('Display the file system informations'),
+ short => 'sf',
+ callback => sub{ $obj->filesys(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # Interval to read timers and put to DB
+ Event->timer(
+ interval => $self->{interval},
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ $self->remember();
+ },
+ ) if($self->{active} eq 'y');
+
+ $self->{LastWarning} = 0;
+
+ return $self;
+}
+
+# ------------------
+sub remember {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $longsteps = int(($obj->{history} * 60 * 60) / $obj->{interval});
+
+ $obj->watchDog($obj->mounts());
+
+ my $data = {
+ timestamp => time,
+ load => $obj->load('clear'),
+ util => $obj->util('clear'),
+ users => $obj->users('clear'),
+ usage => $obj->mounts('clear'),
+ memory => $obj->meminfo('clear'),
+ network => $obj->netDevs('clear'),
+
+ };
+ push(@{$obj->{rememberstack}}, $data);
+
+ if(scalar @{$obj->{rememberstack}} >= $longsteps) {
+ shift @{$obj->{rememberstack}};
+ }
+}
+
+
+# ------------------
+sub vitals {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my $output = {
+ name => $obj->name(),
+ IP => $obj->IP(),
+ kernel => $obj->kernel(),
+ uptime => $obj->uptime(),
+ users => $obj->users(),
+ load => $obj->load(),
+ util => $obj->util(),
+ };
+
+ my $param = {
+ headingText => gettext('Vitals'),
+ stack => $obj->{rememberstack},
+ history => $obj->{history} * 60 * 60,
+ interval => $obj->{interval},
+ font => sprintf("%s/%s",$obj->{paths}->{FONTPATH},$obj->{font}),
+ };
+ return $console->table($output,$param);
+}
+
+# ------------------
+sub network {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my $interfaces = $obj->netDevs();
+ my $param = {
+ headingText => gettext('Network'),
+ stack => $obj->{rememberstack},
+ history => $obj->{history} * 60 * 60,
+ interval => $obj->{interval},
+ font => sprintf("%s/%s",$obj->{paths}->{FONTPATH},$obj->{font}),
+ };
+ return $console->table($interfaces,$param);
+}
+
+# ------------------
+sub hardware {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my ($number, $model, $speed, $cache, $bogomips) = $obj->CPU();
+ my $pci = $obj->pci();
+ my $ide = $obj->ide();
+ my $scsi = $obj->scsi();
+
+ my $output = {
+ Processors => $number,
+ Model => $model,
+ ChipSpeed => $speed,
+ CacheSize => $cache,
+ SystemBogomips => $bogomips,
+ };
+ $console->table($output, {headingText => gettext('CPU'), hide_HeadRow => 1});
+ $console->table($pci, {headingText => gettext('PCI'), drawRowLine => 1, hide_HeadRow => 1})
+ if($pci);
+ $console->table($ide, {headingText => gettext('IDE')})
+ if($ide && scalar @{$ide} > 1);
+ $console->table($scsi, {headingText => gettext('SCSI')})
+ if($scsi && scalar @{$scsi} > 1);
+}
+
+# ------------------
+sub memory {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my $ret = $obj->meminfo();
+ my $param = {
+ headingText => gettext('Memory'),
+ stack => $obj->{rememberstack},
+ history => $obj->{history} * 60 * 60,
+ interval => $obj->{interval},
+ font => sprintf("%s/%s",$obj->{paths}->{FONTPATH},$obj->{font}),
+ };
+ return $console->table($ret,$param);
+}
+
+# ------------------
+sub filesys {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my $ret = $obj->mounts();
+ my $param = {
+ headingText => gettext('Filesystems'),
+ usage => $ret,
+ font => sprintf("%s/%s",$obj->{paths}->{FONTPATH},$obj->{font}),
+ graphic => ($obj->{graphic} eq 'y' ? 1 : 0),
+ };
+ return $console->table($ret,$param);
+}
+
+
+#############################################################################
+# Helper Functions
+#############################################################################
+
+
+# Takes Celcius temperatures and converts to Farenheit
+
+sub tempConvert {
+ my $obj = shift || return error ('No Object!' );
+ my $celcius = $_[0];
+
+ my $result = (( $celcius * 9) / 5 ) + 32;
+
+ $result = sprintf("%.1f", $result);
+
+ $result .= "&#176 F";
+
+ return $result;
+
+}
+
+# Get the system's name
+
+sub name {
+ my $obj = shift || return error ('No Object!' );
+
+ my $result = hostname();
+ return $result;
+
+}
+
+# Get the system's IP address
+
+sub IP {
+ my $obj = shift || return error ('No Object!' );
+
+ my $result = inet_ntoa(scalar(gethostbyname($obj->name())) || scalar(gethostbyname('localhost')));
+ return $result;
+
+}
+
+# Get the system's kernel version
+
+sub kernel {
+ my $obj = shift || return error ('No Object!' );
+
+ my $result = load_file("/proc/sys/kernel/osrelease");
+ $result =~ s/\n//sig;
+ return $result;
+
+}
+
+# Get the system's uptime
+
+sub uptime {
+ my $obj = shift || return error ('No Object!' );
+
+ my $buffer = load_file('/proc/uptime');
+
+ my @list = split / /, $buffer;
+ my $ticks = sprintf("%.0u", $list[0]);
+ my $mins = $ticks / 60;
+ $mins = sprintf("%.0u", $mins);
+ my $hours = $mins / 60;
+ $hours = sprintf("%.0u", $hours);
+ my $days = ($hours / 24);
+ $days = sprintf("%.0u", $days);
+ $hours = $hours - ($days * 24);
+ $hours = sprintf("%.0u", $hours);
+ $mins = $mins - ($days * 60 * 24) - ($hours * 60);
+
+ my $result = '';
+ if ( $days == 1 ) {
+ $result .= "${days} ".gettext("day");
+ }
+ elsif ( $days == 0 ) {
+ $result .= '';
+ }
+ else {
+ $result .= "${days} ".gettext("days");
+ }
+
+ if ( $days > 0 && ( $hours > 0 || $mins > 0 )) {
+ $result .= ", ";
+ }
+
+ if ( $hours == 1 ) {
+ $result .= "${hours} ".gettext("hour");
+ }
+ elsif ( $hours == 0) {
+ $result .= '';
+ }
+ else {
+ $result .= "${hours} ".gettext("hours");
+ }
+
+ if ( $hours > 0 && $mins > 0 ) {
+ $result .= ", ";
+ }
+
+ if ( $mins == 1 ) {
+ $result .= "${mins} ".gettext("minute");
+ }
+ elsif ($mins == 0 ) {
+ $result .= '';
+ }
+ else {
+ $result .= "${mins} ".gettext("minutes");
+ }
+
+ return $result;
+
+}
+
+# Get information on network devices in the system
+sub netDevs {
+ my $obj = shift || return error ('No Object!' );
+ my $clr = shift || 0;
+ my $buffer = load_file('/proc/net/dev');
+
+ my $interfaces = [[qw/Interface RxBytes RxPackets RxErrs RxDrop TxBytes TxPackets TxErrs TxDrop/]];
+ $interfaces = [] if($clr);
+
+ foreach my $line (split(/\n/, $buffer)) {
+ my @data = split(/[:|\s]+/, $line);
+ next unless($data[2] =~ /^\d+$/);
+ unless($clr) {
+ $data[2] = convert($data[2]);
+ $data[10] = convert($data[10]);
+ }
+ push(@$interfaces, [@data[1..5], @data[10..13]]);
+ }
+
+ return $interfaces;
+}
+
+# Get the current memory info
+sub meminfo {
+ my $obj = shift || return error ('No Object!' );
+ my $clr = shift || 0;
+
+ my $ret = {};
+ my $buffer = load_file "/proc/meminfo";
+ foreach my $zeile (split('\n', $buffer)) {
+ next unless($zeile =~ /kB/);
+ my ($name, $value) = split(':\s+', $zeile);
+ $value =~ s/ kB//sig;
+
+ $value = convert($value * 1024)
+ unless($clr);
+
+ $ret->{$name} = $value;
+ }
+ return $ret;
+}
+
+# Get current cpu info
+
+sub CPU {
+ my $obj = shift || return error ('No Object!' );
+
+ my $buffer = load_file('/proc/cpuinfo');
+
+ my @rows = split /\n/, $buffer;
+ my $number = scalar grep /processor\s+:/, @rows;
+ my @modelList = grep /model name\s+:/, @rows;
+ my @speedList = grep /cpu MHz\s+:/, @rows;
+ my @cacheList = grep /cache size\s+:/, @rows;
+ my @bogomipsList = grep /bogomips\s+:/, @rows;
+
+ my ($crap, $model) = split /:/, $modelList[0], 2;
+ $model =~ s/\s+//;
+
+ ($crap, my $speed) = split /:/, $speedList[0], 2;
+ $speed = sprintf("%.0u", $speed);
+ $speed .= " MHz";
+
+ ($crap, my $bogomips) = split /:/, $bogomipsList[0], 2;
+ $bogomips = sprintf("%.0u", $bogomips);
+
+ my $cache = '';
+
+ ($crap, $cache) = split /:/, $cacheList[0], 2;
+ if ($cache eq '') {
+ $cache = gettext("No on-chip cache.");
+ }
+
+ return ($number, $model, $speed, $cache, $bogomips);
+
+}
+
+# Get CPU usage info and return a percentage
+
+sub util {
+ my $obj = shift || return error ('No Object!' );
+
+ open(STAT, "/proc/stat") or return error "Can't open /proc/stat\n";
+ my $buffer = <STAT>;
+ close(STAT);
+
+ my ($name, $user, $nice, $system, $idle) = split /\s+/, $buffer;
+ my $usage = $user + $nice + $system;
+ my $total = $user + $nice + $system + $idle;
+
+ #Wait 1 second for cpu time to accumulate for comparison
+ #More than 1 second delays the script too much, and sleep won't
+ #take an argument < 1
+ sleep(1);
+
+ open (STAT, "/proc/stat") or return error "Can't open /proc/stat\n";
+ $buffer = <STAT>;
+ close(STAT);
+
+ my ($newName, $newUser, $newNice, $newSystem, $newIdle) = split /\s+/, $buffer;
+ my $newUsage = $newUser + $newNice + $newSystem;
+ my $newTotal = $newUser + $newNice + $newSystem + $newIdle;
+
+ my $deltaUsage = $newUsage - $usage;
+ my $deltaTotal = $newTotal - $total;
+
+ my $percent = 0;
+
+ $percent = ($deltaUsage / $deltaTotal) * 100
+ if($deltaTotal != 0);
+
+ $percent = sprintf("%.1f", $percent);
+
+ return($percent);
+
+}
+
+# Get the number of current users logged in
+
+sub users {
+ my $obj = shift || return error ('No Object!' );
+
+ my $result = `$obj->{whoBinary} | $obj->{wcBinary} -l`
+ or return error "Can't execute $obj->{whoBinary} or $obj->{wcBinary}\n";
+ $result =~ s/\n//g;
+ return $result;
+
+}
+
+# Get the list of PCI devices
+
+sub pci {
+ my $obj = shift || return error ('No Object!' );
+
+ return 0
+ if(! -r "/proc/pci");
+
+ my $buffer = load_file("/proc/pci");
+ my $ret;
+ foreach my $zeile (split(/\n/, $buffer)) {
+ if($zeile =~ /(bridge|controller|interface)\:\s+(.+)/i) {
+ $ret->{ucfirst($1)} .= "$2\n";
+ }
+ }
+ return $ret;
+}
+
+# Get the list of IDE devices
+
+sub ide {
+ my $obj = shift || return error ('No Object!' );
+
+ my @ideModelList;
+ my @ideCapacityList;
+ my $count = 0;
+
+ my @dirList = glob ("/proc/ide/*");
+ my $ret = [[qw/Device Model Capacity Cache/]];
+ foreach my $device (@dirList) {
+ next unless($device =~ /ide\/hd/);
+
+ my $model = load_file("${device}/model");
+ $model =~ s/\n//g;
+
+ my $cap = 0;
+ $cap = load_file("${device}/capacity")
+ if(-e "${device}/capacity");
+ my $cache = 0;
+ $cache = load_file("${device}/cache")
+ if(-e "${device}/cache");
+ push(@$ret,
+ [
+ $device,
+ $model,
+ convert($cap * 512),
+ convert($cache * 1024),
+ ]
+ );
+ }
+
+ return $ret;
+}
+
+# Get the list of SCSI devices
+
+sub scsi {
+ my $obj = shift || return error ('No Object!' );
+
+ my $ret = [[qw/Device Vendor Model Type/]];
+ my $file = "/proc/scsi/scsi";
+
+ if ( -r $file){
+ my ( $host, $channel, $id, $lun, $vendor, $model, $type ) ;
+ my $dev_no = 'a';
+ my $cd_no = '0';
+ my $st_no = '0';
+ open(F,$file)
+ or return error "Can't open $file : $!\n";;
+ while(<F>) {
+ if(/Host: (\S+) Channel: (\d+) Id: (\d+) Lun: (\d+)/) {
+ $host = $1, $channel = $2, $id = $3, $lun = $4;
+ }
+ if(/Vendor: (.+)\s+Model: (.+)\s+Rev:/) {
+ $vendor = $1, $model = $2;
+ $vendor =~ s/^\s+//g;
+ $vendor =~ s/\s+$//g;
+ $model =~ s/^\s+//g;
+ $model =~ s/\s+$//g;
+
+ $_ = <F>;
+ if(/Type:(.+)\s+ANSI/) {
+ $type = $1;
+ $type =~ s/^\s+//g;
+ $type =~ s/\s+$//g;
+ }
+
+ my $device;
+ if($type eq 'Direct-Access') { # Disk
+ $device = "/dev/sd$dev_no";
+ $dev_no++;
+ } elsif($type eq 'CD-ROM') {
+ $device = "/dev/scd$cd_no";
+ $cd_no++;
+ } elsif($type eq 'Sequential-Access') { # Streamer
+ $device = "/dev/st$st_no";
+ $st_no++;
+ }
+
+ push(@$ret,
+ [
+ "$device (ch: $channel, lun: $lun, scsi: $id)",
+ $vendor,
+ $model,
+ $type,
+ ]
+ ) if($device);
+ }
+ }
+ close(F);
+ }
+ return $ret;
+}
+
+# Get the current load averages
+
+sub load {
+ my $obj = shift || return error ('No Object!' );
+ my $clr = shift || 0;
+
+ my $buffer = load_file("/proc/loadavg");
+ my @list = split(' ', $buffer);
+ my $c = 5;
+ my $ret;
+
+ return \@list if($clr);
+
+ foreach my $entry (@list[0..2]) {
+ $ret .= sprintf("%s last %d min\n", $entry, $c);
+ $c += 5;
+ }
+
+ return $ret;
+
+}
+
+# Get the status of currently mounted filesystems
+sub mounts{
+ my $obj = shift || return error ('No Object!' );
+ my $clr = shift || 0;
+
+ my $df = `$obj->{dfBinary} -TP -x cdfs -x iso9660 -x udf`
+ or return error "Can't execute $obj->{dfBinary} $!\n";
+ my $ret = [[qw/FS Typ Space Used Free Cap. Mount/]];
+
+ foreach my $zeile (split('\n', $df)) {
+ my @data = split('\s+', $zeile);
+ next if($data[2] !~ /^\d+$/);
+
+ $data[0] =~ s/[\-\s]/_/sg;
+
+ if($clr) {
+ push(@$ret, $data[5]);
+ } else {
+ map {$_ = convert($_ * 1024)} @data[2..4];
+ push(@$ret, \@data);
+ }
+ }
+ return $ret;
+}
+
+# ------------------
+sub videoMounts {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $videodir = shift || return error ('No Video dir!');
+ my $mounts = $obj->mounts;
+
+ my $ret = [];
+
+ for (@$mounts) {
+ push(@$ret, $_)
+ if($_->[0] =~ /^$videodir/i);
+ }
+
+ $ret = $mounts unless(scalar @$ret);
+
+ return $ret;
+}
+
+# ------------------
+sub findttf
+# ------------------
+{
+ my $obj = shift || return error ('No Object!' );
+ my $found;
+ find({ wanted => sub{
+ if($File::Find::name =~ /\.ttf$/sig) {
+ my $l = basename($File::Find::name);
+ push(@{$found},[$l,$l]);
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $obj->{paths}->{FONTPATH}
+ );
+ error "Can't find useful font at : $obj->{paths}->{FONTPATH}"
+ if(scalar $found == 0);
+ return $found;
+}
+
+# ------------------
+sub watchDog {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $mou = shift || return error ('No Data!' );
+
+ # Not all 15 seconds a panic message ;)
+ return if($obj->{LastWarning}+900 > time);
+
+ foreach my $m (@$mou) {
+ next unless($m->[0] =~ /^\//);
+ if(int($m->[5]) >= 98 ) {
+ my $rm = main::getModule('REPORT');
+ $rm->news(
+ sprintf(gettext("PANIC! Only %s%% space left on device %s"),(100 - int($m->[5])),$m->[0]),
+ sprintf(gettext("Device has space %s from %s used!"), $m->[3], $m->[2]),
+ 'sa',
+ undef,
+ 'important'
+ );
+ $obj->{LastWarning} = time;
+ }
+ }
+}
+
+
+1;
diff --git a/lib/XXV/MODULES/STREAM.pm b/lib/XXV/MODULES/STREAM.pm
new file mode 100644
index 0000000..aa80c22
--- /dev/null
+++ b/lib/XXV/MODULES/STREAM.pm
@@ -0,0 +1,179 @@
+package XXV::MODULES::STREAM;
+use strict;
+
+use Tools;
+use Locale::gettext;
+use File::Basename;
+use File::Find;
+use File::Path;
+
+$SIG{CHLD} = 'IGNORE';
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'STREAM',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module generate streams from recordings.'),
+ Version => '0.92',
+ Date => '2007-01-20',
+ Author => 'xpix',
+ Preferences => {
+ host => {
+ description => gettext('Used host of referred link inside playlist.'),
+ default => 'localhost',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ netvideo => {
+ description => gettext('Path from remote video directory (SambaDir).'),
+ default => '\\\\vdr\\video',
+ type => 'string',
+ },
+ mimetyp => {
+ description => gettext('Used mime-typ to deliver video streams.'),
+ default => 'video/x-mpegurl',
+ type => 'string',
+ },
+ },
+ Commands => {
+ playrecord => {
+ description => gettext("Play the record over samba or nfs."),
+ short => 'pre',
+ callback => sub{ $obj->play_record(@_) },
+ DenyClass => 'stream',
+ },
+ livestream => {
+ description => gettext("Stream a channel 'cid'. This required the streamdev plugin!"),
+ short => 'lst',
+ callback => sub{ $obj->live_stream(@_) },
+ DenyClass => 'stream',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # The Initprocess
+ my $erg = $self->init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ 1;
+}
+
+
+# ------------------
+sub live_stream {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $channel = shift || return $console->err(gettext("No ChannelID to Stream! Please use livestream 'cid'"));
+
+ debug sprintf('Call live stream with channel "%s"%s',
+ $channel,
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ if($channel && $console->typ eq 'HTML') {
+ $console->{nocache} = 1;
+ $console->{nopack} = 1;
+ $console->{noFooter} = 1;
+
+ my $data;
+ $data = "#EXTM3U\r\n";
+ $data .= sprintf("http://%s:3000/PES/%d", $obj->{host}, $channel);
+ $data .= "\r\n";
+
+ my $arg;
+ $arg->{'attachment'} = sprintf("livestream%d.m3u", $channel);
+ $arg->{'Content-Length'} = length($data);
+
+ $console->out($data, $obj->{mimetyp}, %{$arg} );
+ } else {
+ $console->err(gettext("Sorry, stream is'nt supported"));
+ }
+}
+
+# ------------------
+sub play_record {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $recid = shift || return $console->err(gettext("No RecordID to Play! Please use rplay 'rid'"));
+
+ my $rmod = main::getModule('RECORDS');
+ my $videopath = $rmod->{videodir};
+ my $path = $rmod->IdToPath($recid)
+ or return $console->err(gettext(sprintf('I can\'t found recid: %s', $recid)));
+
+ debug sprintf('Call play record "%s"%s',
+ $path,
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ my $data;
+ $data = "#EXTM3U\r\n";
+ foreach my $file (glob("$path/???.vdr")) {
+ $file =~ s/^$videopath//si;
+ $file =~ s/^[\/|\\]//si;
+ my $URL = sprintf("%s/%s\r\n", $obj->{netvideo}, $file);
+ $URL =~s/\//\\/g
+ if($URL =~ /^\\\\/sig # Samba \\host/xxx/yyy => \\host\xxx\yyy
+ || $URL =~ /^[a-z]\:[\/|\\]/sig); # Samba x:/xxx/yyy => x:\xxx\yyy
+ $data .= $URL;
+ }
+
+ if($data && $console->typ eq 'HTML') {
+ $console->{nocache} = 1;
+ $console->{nopack} = 1;
+ $console->{noFooter} = 1;
+
+ my $arg;
+ $arg->{'attachment'} = sprintf("playrecord%d.m3u", $recid);
+ $arg->{'Content-Length'} = length($data);
+
+ $console->out($data, $obj->{mimetyp}, %{$arg} );
+ } else {
+ $console->err(gettext("Sorry, stream is'nt supported"));
+ }
+}
+1;
diff --git a/lib/XXV/MODULES/SVDRP.pm b/lib/XXV/MODULES/SVDRP.pm
new file mode 100644
index 0000000..08ca944
--- /dev/null
+++ b/lib/XXV/MODULES/SVDRP.pm
@@ -0,0 +1,228 @@
+package XXV::MODULES::SVDRP;
+
+use Tools;
+use Locale::gettext;
+use strict;
+
+
+$|++;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'SVDRP',
+ Prereq => {
+ 'Net::Telnet' => 'Net::Telnet allows you to make client connections to a TCP port and do network I/O',
+ },
+ Description => gettext('This module a telnet client for sdvrp.'),
+ Version => '0.93',
+ Date => '2007-02-21',
+ Author => 'xpix',
+ Preferences => {
+ VdrHost => {
+ description => gettext('Name of the host that runs vdr'),
+ default => 'localhost',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ VdrPort => {
+ description => gettext('SVDRP-port of the running vdr client'),
+ default => 2001,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ timeout => {
+ description => gettext('Connection timeout defines after how many seconds an unrequited connection is terminated.'),
+ default => 60,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ sstatus => {
+ description => gettext('Status from svdrp'),
+ short => 'ss',
+ callback => sub{ $obj->status(@_) },
+ Level => 'user',
+ DenyClass => 'remote',
+ },
+ scommand => {
+ description => gettext('Send a command to svdrp'),
+ short => 'sc',
+ callback => sub{ $obj->scommand(@_) },
+ Level => 'admin',
+ DenyClass => 'remote',
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ $self->{COMMANDS} = [];
+
+ return $self;
+}
+
+# ------------------
+sub queue_cmds {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cmd = shift || 'CALL';
+
+ if($cmd eq 'CALL') {
+ my $queue = delete $obj->{COMMANDS};
+ $obj->{COMMANDS} = [];
+ return $obj->command($queue);
+ } elsif($cmd eq 'COUNT') {
+ return scalar @{$obj->{COMMANDS}};
+ } else {
+ push(@{$obj->{COMMANDS}}, $cmd);
+ }
+}
+
+# ------------------
+sub command {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cmd = shift;
+
+ my $host = $obj->{VdrHost};
+ my $port = $obj->{VdrPort};
+
+ my $data;
+ my $line;
+ my @commands = ();
+ push(@commands, (ref $cmd eq 'ARRAY' ? @$cmd : $cmd));
+
+ unless(scalar @commands > 0) {
+ error ('No Commands!');
+ return undef;
+ }
+ push(@commands, "quit");
+
+ $obj->{ERROR} = 0;
+
+ # Put Command follow quit and read Output
+ my $telnet = Net::Telnet->new ( Telnetmode => 0,
+ Timeout => $obj->{timeout},
+ Errmode => 'return');
+
+ if(!$telnet or !$telnet->open(Host => $host, Port => $port)){
+ error sprintf("Couldn't connect to svdrp-socket %s:%s! %s",$host,$port,$telnet ? $telnet->errmsg : $!);
+ return undef;
+ }
+
+ # read first line
+ do {
+ $line = $telnet->getline;
+ last unless $line;
+ chomp($line);
+ push(@$data, $line);
+ } while($line =~ /^\d\d\d\-/);
+
+ unless(scalar @$data){
+ error sprintf("Couldn't read data from svdrp-socket %s:%s! %s",$host,$port,$telnet ? $telnet->errmsg : $!);
+ return undef;
+ }
+
+ main::getVdrVersion($1)
+ if($data->[0] =~ /SVDRP\s+VideoDiskRecorder\s+(\d\.\d\.\d+)[\;|\-]/);
+
+ # send commando queue
+ foreach my $command (@commands) {
+ $telnet->buffer_empty; #clear buffer
+ # send command
+ if(!$telnet->print($command)) {
+ error sprintf("Couldn't send svdrp-command '%s' to %s:%s! %s",$command,$host,$port,$telnet ? $telnet->errmsg : $!);
+ return undef;
+ }
+ # read response
+ do {
+ $line = $telnet->getline;
+ last unless $line;
+ chomp($line);
+
+ if($line =~ /^(\d{3})\s+(.+)/ && (int($1) >= 500)) {
+ my $msg = sprintf("Error at command '%s' to %s:%s! %s", $command,$host,$port, $2);
+ error($msg);
+ $obj->{ERROR} .= $msg . "\n";
+ }
+
+ push(@$data, $line);
+ } while($line =~ /^\d\d\d\-/);
+ }
+
+ # close socket
+ $telnet->close();
+
+ foreach my $command (@commands) {
+ event(sprintf('Call command "%s" on svdrp %s.', $command, $obj->{ERROR} ? " failed" : "successful"))
+ if($command ne "quit");
+ }
+ return \@$data;
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return;
+
+ my $erg = $obj->command('stat disk');
+ $console->msg($erg, $obj->err)
+ if(ref $console);
+}
+
+# ------------------
+sub scommand {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $text = shift || return $console->err(gettext("No Command! Please use scommand 'cmd'"));
+
+ my $erg = $obj->command($text);
+ $console->msg($erg, $obj->err);
+}
+
+
+# ------------------
+sub err {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{ERROR};
+}
+
+1;
diff --git a/lib/XXV/MODULES/TELNET.pm b/lib/XXV/MODULES/TELNET.pm
new file mode 100644
index 0000000..f29b2bb
--- /dev/null
+++ b/lib/XXV/MODULES/TELNET.pm
@@ -0,0 +1,326 @@
+package XXV::MODULES::TELNET;
+
+use strict;
+
+use XXV::OUTPUT::Console;
+use Tools;
+use Locale::gettext;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'TELNET',
+ Prereq => {
+ 'IO::Socket::INET' => 'Object interface for AF_INET domain sockets ',
+ 'Module::Reload' => 'Reload %INC files when updated on disk ',
+ },
+ Description => gettext('This module is a multisession telnet server.'),
+ Version => '0.01',
+ Date => '18.08.2004',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ Clients => {
+ description => gettext('Maximum number from simultaneous connections to the same time'),
+ default => 5,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Port => {
+ description => gettext('Number of port to listen for telnet clients'),
+ default => 8081,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Interface => {
+ description => gettext('Local interface to bind service'),
+ default => '0.0.0.0',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ help => {
+ description => gettext("This will display all commands or the helptext from the 'module name'"),
+ short => 'h',
+ callback => sub{
+ return $obj->usage(@_);
+ },
+ },
+ quit => {
+ description => gettext("This will exit the telnet session"),
+ short => 'q',
+ callback => sub{
+ my ($w, $c, $l) = @_;
+ lg "Telnet session closed.\n";
+ $c->message(gettext("Session closed."));
+ $obj->{LOGOUT} = 1;
+ },
+ },
+ bye => {
+ description => gettext("This will exit the xxv system."),
+ short => 'x',
+ callback => sub{
+ my ($w, $c, $l) = @_;
+ my $answer;
+ my $questions = [
+ 'really' => {
+ typ => 'confirm',
+ msg => gettext("Are you sure to exit the xxv system?"),
+ def => 'n'}
+ ];
+ $answer = $c->question(gettext("This will exit the xxv system."),$questions,$answer);
+ if($answer->{really} eq 'y') {
+ $w->w->fd->close;
+ $w->w->cancel;
+ lg "Closed session and exit.\n";
+ main::quit;
+ }
+ },
+ Level => 'admin'
+ },
+ reload => {
+ description => gettext("This will reload all Modules."),
+ short => 'rel',
+ callback => sub{
+ my ($w, $c, $l) = @_;
+ $Module::Reload::Debug = 2;
+ Module::Reload->check;
+ $c->message(gettext("Modules reloaded."));
+ },
+ Level => 'admin'
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ $self->init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ # globals
+ my $channels;
+
+ my $prompt = "XXV> ";
+
+ # make socket
+ my $socket = IO::Socket::INET->new(
+ Listen => $obj->{Clients},
+ LocalPort => $obj->{Port},
+ LocalAddr => $obj->{Interface},
+ Reuse => 1
+ ) or return error("Can't create Socket: $!");
+
+ # install an initial watcher
+ Event->io(
+ fd => $socket,
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ my $watch = shift;
+
+ # make "channel" number
+ my $channel=++$channels;
+
+ # accept client
+ my $client=$socket->accept;
+ panic "Can't connect telnet to new client.\n" and return unless $client;
+ $client->autoflush;
+
+ my $console = XXV::OUTPUT::Console->new(
+ -handle => $client,
+ -dbh => $obj->{dbh},
+ -paths => $obj->{paths},
+ );
+
+ # install a communicator
+ Event->io(
+ fd => $client,
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ my $watcher = shift;
+
+ # read new line and report it
+ my $handle=$watcher->w->fd;
+
+ my $line=<$handle>;
+ if(!$line or (defined $obj->{LOGOUT} && $obj->{LOGOUT} == 1 )) {
+ undef $obj->{LOGOUT};
+ $watcher->w->cancel;
+ $handle->close();
+ undef $watcher;
+ return 1;
+ }
+ $line =~ s/[\r|\n]//sig
+ if(defined $line);
+
+ $obj->handleInput($watcher, $console, $line);
+ if(defined $obj->{LOGOUT} && $obj->{LOGOUT} == 1) {
+ undef $obj->{LOGOUT};
+ $watcher->w->cancel;
+ $handle->close();
+ undef $watcher;
+ return 1;
+ }
+
+ # Prompt
+ $client->print($prompt) if($client->opened);
+ },
+ );
+
+ # welcome
+ $client->print(sprintf(gettext("Welcome to xxv system version: %s.\r\nThis is session %s.\r\n"),$obj->{MOD}->{Version},$channel));
+
+ my $userMod = main::getModule('USER');
+ unless(exists $console->{USER} or $userMod->{active} ne 'y') {
+ # Login
+ my $data;
+ if($userMod->_checkIp($client)) {
+ $console->message(gettext("Welcome to xxv system."));
+ $data->{Name} = 'no';
+ $data->{Password} = 'no';
+ } else {
+ $data = $console->login(gettext("Welcome to xxv system. Please Login:"));
+ }
+ my $user = $userMod->check($client, $data->{Name}, $data->{Password});
+ if(exists $user->{Name}) {
+ $console->{USER} = $user;
+ } else {
+ $console->err(gettext("Sorry, but permission denied!"));
+ $client->close;
+ return 1;
+ }
+ }
+
+ $client->print($prompt);
+
+ },
+ ) if($obj->{active} eq 'y');
+
+ return 1;
+
+}
+
+# ------------------
+sub usage {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $modulename = shift || 0;
+ my $hint = shift || '';
+ my $user = shift || $console->{USER};
+
+ my $u = main::getModule('USER');
+ unless($user) {
+ my $loginObj = $obj;
+ $loginObj = main::getModule('HTTPD')
+ if ($console->{TYP} eq 'HTML') ;
+ $loginObj = main::getModule('WAPD')
+ if ($console->{TYP} eq 'WML') ;
+ $user = $loginObj->{USER};
+ }
+
+ my $ret;
+ push(@$ret, sprintf(gettext("%sThis is the xxv %s server.\nPlease use the following commands:\n"),
+ ($hint ? "$hint\n\n" : ''), $console->typ));
+
+ my $mods = main::getModules();
+ my @realModName;
+
+ # Search for command and display the Description
+ foreach my $modName (sort keys %{$mods}) {
+ my $modCfg = $mods->{$modName}->{MOD};
+ push(@realModName, $mods->{$modName}->{MOD}->{Name});
+ next if($modulename and uc($modulename) ne $modCfg->{Name});
+ foreach my $cmdName (sort keys %{$modCfg->{Commands}}) {
+ push(@$ret,
+ [
+ (split('::', $modName))[-1],
+ $modCfg->{Commands}->{$cmdName}->{short},
+ $cmdName,
+ $modCfg->{Commands}->{$cmdName}->{description},
+ ]
+ ) if(! $modCfg->{Commands}->{$cmdName}->{hidden} and ($u->{active} ne 'y') || $u->allowCommand($modCfg, $cmdName, $user, "1"));
+ }
+ }
+
+ $console->menu(
+ $ret,
+ {
+ periods => $mods->{'XXV::MODULES::EPG'}->{periods},
+ CHANNELS => $mods->{'XXV::MODULES::CHANNELS'}->ChannelArray('Name'),
+ CONFIGS => [ sort @realModName ],
+ },
+ );
+}
+
+# ------------------
+sub handleInput {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $line = shift || return;
+ my $user = shift || $console->{USER};
+
+ my ($ucmd, $udata) = ($1, $2) if($line =~ /(\S+)\s*(.*)/sig);
+
+ # Test the command on exists, permissions and so on
+ my $u = main::getModule('USER');
+ my ($cmdobj, $cmdname, $shorterr, $err) = $u->checkCommand($console, $ucmd);
+ $console->{call} = $cmdname;
+ if($cmdobj and not $shorterr) {
+ $cmdobj->{callback}($watcher, $console, $udata);
+ } elsif($shorterr eq 'noperm' or $shorterr eq 'noactive') {
+ return $console->err($err);
+ } else {
+ return $obj->usage($watcher, $console, undef, $err);
+ }
+}
+
+1;
diff --git a/lib/XXV/MODULES/TIMERS.pm b/lib/XXV/MODULES/TIMERS.pm
new file mode 100644
index 0000000..b20f67d
--- /dev/null
+++ b/lib/XXV/MODULES/TIMERS.pm
@@ -0,0 +1,1721 @@
+package XXV::MODULES::TIMERS;
+
+use strict;
+use Tools;
+use POSIX ":sys_wait_h", qw(strftime mktime);
+use Locale::gettext;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'TIMERS',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module parse the timers.conf and save this in the database.'),
+ Version => '0.92',
+ Date => '2007-01-28',
+ Author => 'xpix',
+ Status => sub{ $obj->status(@_) },
+ Preferences => {
+ interval => {
+ description => gettext('How often timers are to be updated (in seconds)'),
+ default => 30 * 60,
+ type => 'integer',
+ required => gettext("This is required!"),
+ },
+ prevminutes => {
+ description => gettext('Buffer time in minutes before the scheduled end of the recorded program.'),
+ default => 5,
+ type => 'integer',
+ },
+ afterminutes => {
+ description => gettext('Buffer time in minutes after the scheduled end of the recorded program.'),
+ default => 5,
+ type => 'integer',
+ },
+ Priority => {
+ description => gettext('Defining the priority of this timer and of recordings created by this timer.'),
+ default => 50,
+ type => 'integer',
+ },
+ Lifetime => {
+ description => gettext('The guaranteed lifetime (in days) of a recording created by this timer'),
+ default => 50,
+ type => 'integer',
+ },
+ DVBCards => {
+ description => gettext('How much DVB cards in your system?'),
+ default => 1,
+ type => 'integer',
+ },
+ deactive => {
+ description => gettext('Delete inactive timers after his end time?'),
+ default => 'n',
+ type => 'confirm',
+ },
+ usevpstime => {
+ description => gettext('Use VPS start time?'),
+ default => 'n',
+ type => 'confirm',
+ },
+ adjust => {
+ description => gettext('Timers adjust, if EPG entry were changed?'),
+ default => 'y',
+ type => 'confirm',
+ },
+ },
+ Commands => {
+ tlist => {
+ description => gettext("List timers 'tid'"),
+ short => 'tl',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'tlist',
+ },
+ tsearch => {
+ description => gettext("Search timers 'text'"),
+ short => 'ts',
+ callback => sub{ $obj->list(@_) },
+ DenyClass => 'tlist',
+ },
+ tupdate => {
+ description => gettext("Read timers and write into database"),
+ short => 'tu',
+ callback => sub{ $obj->readData(@_)},
+ Level => 'user',
+ DenyClass => 'tedit',
+ },
+ tnew => {
+ description => gettext("Create timer 'eid'"),
+ short => 'tn',
+ callback => sub{ $obj->newTimer(@_) },
+ Level => 'user',
+ DenyClass => 'tedit',
+ },
+ tedit => {
+ description => gettext("Edit timer 'tid'"),
+ short => 'te',
+ callback => sub{ $obj->editTimer(@_) },
+ Level => 'user',
+ DenyClass => 'tedit',
+ },
+ tdelete => {
+ description => gettext("Delete timer 'tid'"),
+ short => 'td',
+ callback => sub{ $obj->deleteTimer(@_) },
+ Level => 'user',
+ DenyClass => 'tedit',
+ },
+ ttoggle => {
+ description => gettext("Activate/Deactive timer 'tid'"),
+ short => 'tt',
+ callback => sub{ $obj->toggleTimer(@_) },
+ Level => 'user',
+ DenyClass => 'tedit',
+ },
+ tsuggest => {
+ hidden => 'yes',
+ callback => sub{ $obj->suggest(@_) },
+ DenyClass => 'tlist',
+ },
+ },
+ RegEvent => {
+ 'newTimerfromUser' => {
+ Descr => gettext('Create event entries, if a new timer created by user.'),
+
+ # You have this choices (harmless is default):
+ # 'harmless', 'interesting', 'veryinteresting', 'important', 'veryimportant'
+ Level => 'interesting',
+
+ # Search for a spezial Event.
+ # I.e.: Search for an LogEvent with match
+ # "Sub=>text" = subroutine =~ /text/
+ # "Msg=>text" = logmessage =~ /text/
+ # "Mod=>text" = modname =~ /text/
+ SearchForEvent => {
+ Msg => 'New timer',
+ },
+ # Search for a Match and extract the information
+ # of the id
+ # ...
+ Match => {
+ TimerId => qr/id\:\s+\"(\d+)\"/s,
+ },
+ Actions => [
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ return if($timer->{AutotimerId});
+ my $desc = getDataById($timer->{eventid}, 'EPG', 'eventid') if($timer->{eventid});
+ my $title = sprintf(gettext("New timer found: %s"),$timer->{File});
+ my $description = sprintf(gettext("At: %s to %s\nDescription: %s"),
+ $timer->{NextStartTime},
+ fmttime($timer->{Stop}),
+ $desc && $desc->{description} ? $desc->{description} : ''
+ );
+
+ main::getModule('REPORT')->news($title, $description, "display", $timer->{eventid}, $event->{Level});
+ }
+ |,
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ my $soap = main::getModule('SHARE');
+ my $level = 1;
+ if($timer->{AutotimerId}) {
+ $level = (($timer->{Priority} <= 50 or $timer->{Lifetime} < 33) ? 2 : 3);
+ } else {
+ $level = (($timer->{Priority} <= 50 or $timer->{Lifetime} < 33) ? 4 : 5);
+ }
+
+ if($timer->{eventid}) {
+ my $event = main::getModule('EPG')->getId($timer->{eventid}, 'UNIX_TIMESTAMP(starttime) + duration as STOPTIME');
+ $soap->setEventLevel($timer->{eventid}, $level, $event->{STOPTIME});
+ }
+ }|,
+ ],
+
+ },
+ 'deleteTimer' => {
+ Descr => gettext('Create event entries, if timer deleted by user.'),
+ Level => 'interesting',
+ SearchForEvent => {
+ Msg => 'delt',
+ },
+ Match => {
+ TimerId => qr/delt\s+(\d+)/s,
+ },
+ Actions => [
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ my $title = sprintf(gettext("Timer deleted: %s"),$timer->{File});
+ my $desc = getDataById($timer->{eventid}, 'EPG', 'eventid') if($timer->{eventid});
+ my $description = sprintf(gettext("At: %s to %s\nDescription: %s"),
+ $timer->{NextStartTime},
+ fmttime($timer->{Stop}),
+ $desc && $desc->{description} ? $desc->{description} : ''
+ );
+
+ main::getModule('REPORT')->news($title, $description, "display", $timer->{eventid}, $event->{Level});
+ }
+ |,
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ my $soap = main::getModule('SHARE');
+ my $level = 1;
+
+ if($timer->{eventid}) {
+ my $event = main::getModule('EPG')->getId($timer->{eventid}, 'UNIX_TIMESTAMP(starttime) + duration as STOPTIME');
+ $soap->setEventLevel($timer->{eventid}, $level, $event->{STOPTIME});
+ }
+ }|,
+ ],
+ },
+ 'toggleTimer' => {
+ Descr => gettext('Create event entries, if timer toggled by user.'),
+ Level => 'interesting',
+ SearchForEvent => {
+ Msg => 'modt',
+ },
+ Match => {
+ TimerId => qr/modt\s+(\d+)\s(on|off)/s,
+ Type => qr/modt\s+\d+\s+(on|off)/s,
+ },
+ Actions => [
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ my $title = sprintf(gettext("Timer toggled: %s to %s"),$timer->{File},$args->{Type});
+ my $description = sprintf(gettext("Description: %s\nAt: %s to %s"),
+ $timer->{Summary},
+ $timer->{NextStartTime},
+ fmttime($timer->{Stop})
+ );
+
+ main::getModule('REPORT')->news($title, $description, "display", $timer->{eventid}, $event->{Level});
+ }
+ |,
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $timer = getDataById($args->{TimerId}, 'TIMERS', 'Id');
+ my $soap = main::getModule('SHARE');
+ my $level = ($args->{Type} eq 'off' ? 1 : 2);
+ if($timer->{AutotimerId} and $args->{Type} eq 'on') {
+ $level = (($timer->{Priority} <= 50 or $timer->{Lifetime} < 33) ? 2 : 3);
+ } elsif($args->{Type} eq 'on') {
+ $level = (($timer->{Priority} <= 50 or $timer->{Lifetime} < 33) ? 4 : 5);
+ }
+
+ if($timer->{eventid}) {
+ my $event = main::getModule('EPG')->getId($timer->{eventid}, 'UNIX_TIMESTAMP(starttime) + duration as STOPTIME');
+ $soap->setEventLevel($timer->{eventid}, $level, $event->{STOPTIME});
+ }
+ }|,
+ ],
+ },
+ 'updateTimer' => {
+ Descr => gettext('Create event entries, if timer updated.'),
+ Level => 'harmless',
+ SearchForEvent => {
+ Msg => 'Reread',
+ },
+ Match => {
+ HighId => qr/Reread\s+(\d+)\s+timers/s,
+ },
+ Actions => [
+ q|sub{ my $args = shift;
+ my $event = shift;
+ my $soap = main::getModule('SHARE');
+ my $epg = main::getModule('EPG');
+ for (my $i = 1; $i<=$args->{HighId}; $i++) {
+ my $timer = getDataById($i, 'TIMERS', 'Id');
+
+ my $level = 1;
+ if($timer->{AutotimerId} and ($timer->{Status} & 1)) {
+ $level = (($timer->{Priority} <= 50 or $timer->{Lifetime} < 33) ? 2 : 3);
+ } elsif($timer->{Status} & 1) {
+ $level = (($timer->{Priority} <= 50 or $timer->{Lifetime} < 33) ? 4 : 5);
+ }
+
+ if($timer->{eventid}) {
+ my $event = $epg->getId($timer->{eventid}, 'UNIX_TIMESTAMP(starttime) + duration as STOPTIME');
+ $soap->setEventLevel($timer->{eventid}, $level, $event->{STOPTIME});
+ }
+ }
+ }|,
+ ],
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub status {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $lastReportTime = shift || 0;
+
+ my $total = 0;
+ {
+ my $sth = $obj->{dbh}->prepare("select count(*) as count from TIMERS");
+ if(!$sth->execute())
+ {
+ error sprintf("Can't execute query: %s.",$sth->errstr);
+ } else {
+ my $erg = $sth->fetchrow_hashref();
+ $total = $erg->{count} if($erg && $erg->{count});
+ }
+ }
+
+ return {
+ message => sprintf(gettext('%d Timers exists.'), $total),
+ };
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0, panic("Session to database is'nt connected")
+ unless($obj->{dbh});
+
+ # remove old table, if updated rows
+ tableUpdated($obj->{dbh},'TIMERS',19,1);
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+ $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS TIMERS (
+ Id int(11) unsigned NOT NULL,
+ Status char(1) default 1,
+ ChannelID varchar(100) NOT NULL default '',
+ Day varchar(20) default '-------',
+ Start int(11) unsigned,
+ Stop int(11) unsigned,
+ Priority tinyint(2),
+ Lifetime tinyint(2),
+ File text,
+ Summary text default '',
+ NextStartTime datetime,
+ NextStopTime datetime,
+ Collision varchar(100) default '0',
+ eventid bigint unsigned default '0',
+ eventstarttime datetime,
+ eventduration int unsigned default '0',
+ AutotimerId int(11) unsigned default '0',
+ addtime timestamp,
+ Checked char(1) default 0,
+ PRIMARY KEY (Id)
+ ) COMMENT = '$version'
+ |);
+
+ $obj->{newTimerFormat} = 0;
+ $obj->{after_updated} = [];
+
+ main::after(sub{
+ $obj->{svdrp} = main::getModule('SVDRP');
+ unless($obj->{svdrp}) {
+ panic ("Can't get modul SVDRP");
+ return 0;
+ }
+
+ $obj->readData();
+
+ # Interval to read timers and put to DB
+ Event->timer(
+ interval => $obj->{interval},
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ $obj->readData();
+ }
+ );
+ return 1;
+ }, "TIMERS: Store timers in database ...", 10);
+
+
+ return 1;
+}
+
+# ------------------
+sub saveTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error('No Data to Save!');
+ my $timerid = shift || 0;
+
+ my $status = ($data->{Activ} eq 'y' ? 1 : 0);
+ $status |= ($data->{VPS} eq 'y' ? 4 : 0);
+
+ $data->{File} =~ s/:/|/g;
+ $data->{File} =~ s/(\r|\n)//sig;
+
+ my $erg = $obj->{svdrp}->command(
+ sprintf("%s %s:%s:%s:%s:%s:%s:%s:%s:%s",
+ $timerid ? "modt $timerid" : "newt",
+ $status,
+ $data->{ChannelID},
+ $data->{Day},
+ $data->{Start},
+ $data->{Stop},
+ int($data->{Priority}),
+ int($data->{Lifetime}),
+ $data->{File},
+ ($data->{Summary} || '')
+ )
+ );
+
+ # Save shortly this timer in DB if this only a new timer (at)
+ # Very Important for Autotimer!
+ my $pos = $1 if($erg->[1] =~ /^250\s+(\d+)/);
+ if(! $timerid and $pos) {
+ $obj->insert([
+ $status,
+ $data->{ChannelID},
+ $data->{Day},
+ $data->{Start},
+ $data->{Stop},
+ int($data->{Priority}),
+ int($data->{Lifetime}),
+ $data->{File},
+ ($data->{Summary} || '')
+ ], $pos);
+ }
+
+ event('Save timer "%s" with TimerId: "%d"', $data->{File}, $pos);
+
+ return $erg;
+}
+
+# ------------------
+sub newTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $epgid = shift || 0;
+ my $epg = shift || 0;
+
+
+ if($epgid and not ref $epg) {
+ my $sth = $obj->{dbh}->prepare(
+qq|
+SELECT
+ eventid,
+ channel_id,
+ description as Summary,
+ CONCAT_WS('~', title, subtitle) as File,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) - ? ), '%d') as Day,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) - ? ), '%H%i') as Start,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) + duration + ? ), '%H%i') as Stop,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(vpstime)), '%H%i') as VpsStart,
+ DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(vpstime) + duration), '%H%i') as VpsStop
+FROM
+ EPG
+WHERE
+ eventid = ?
+|);
+ $sth->execute($obj->{prevminutes} * 60, $obj->{prevminutes} * 60, $obj->{afterminutes} * 60, $epgid)
+ or return $console->err(sprintf(gettext("Event ID '%s' does not exist in the database!"),$epgid));
+ $epg = $sth->fetchrow_hashref();
+ }
+ if(not ref $epg) {
+ my $t = time;
+ $epg = {
+ channel_id => '',
+ File => '',
+ Summary => '',
+ Day => $obj->{newTimerFormat}?my_strftime("%Y-%m-%d",$t):my_strftime("%d",$t),
+ Start => my_strftime("%H%M",$t),
+ Stop => my_strftime("%H%M",$t)
+ };
+ }
+
+ $epg->{Status} = '1'
+ if(not defined $epg->{Status});
+ $epg->{Priority} = $obj->{Priority}
+ if(not defined $epg->{Priority});
+ $epg->{Lifetime} = $obj->{Lifetime}
+ if(not defined $epg->{Lifetime});
+ if(main::getVdrVersion() >= 10344) {
+ $epg->{desc} = $epg->{Summary};
+ $epg->{Summary} = ""
+ }
+ if($epg->{VpsStart} && $obj->{usevpstime} eq 'y') {
+ $epg->{Status} |= 4;
+ $epg->{Start} = $epg->{VpsStart};
+ $epg->{Stop} = $epg->{VpsStop};
+ }
+
+ $obj->editTimer($watcher, $console, 0, $epg);
+}
+
+# ------------------
+sub editTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $timerid = shift || 0; # If timerid the edittimer
+ my $data = shift || 0; # Data for defaults
+
+ my $timerData;
+ if($timerid and not ref $data) {
+ my $sth = $obj->{dbh}->prepare(
+qq|
+SELECT
+ Id,
+ ChannelID as channel_id,
+ File,
+ Summary,
+ Start,
+ Stop,
+ Day,
+ Priority,
+ Lifetime,
+ Status
+FROM
+ TIMERS
+WHERE
+ Id = ?
+|);
+ $sth->execute($timerid)
+ or return $console->err(sprintf(gettext("Timer ID '%s' does not exist in the database!"),$timerid));
+ $timerData = $sth->fetchrow_hashref();
+ } elsif (ref $data eq 'HASH') {
+ $timerData = $data;
+ }
+
+ $timerData->{Summary} =~ s/(\r|\n)//sig
+ if(defined $timerData->{Summary});
+
+ my $mod = main::getModule('CHANNELS');
+ my $con = $console->typ eq "CONSOLE";
+
+ my $questions = [
+ 'Id' => {
+ typ => 'hidden',
+ def => $timerData->{Id} || 0,
+ },
+ 'Activ' => {
+ typ => 'confirm',
+ def => (defined $timerData->{Status} and ($timerData->{Status} & 1) ? 'y' : 'n'),
+ msg => gettext('Switch this timer on?'),
+ },
+ 'VPS' => {
+ typ => 'confirm',
+ def => (defined $timerData->{Status} and ($timerData->{Status} & 4) ? 'y' : 'n'),
+ msg => gettext('VPS for this timer on?'),
+ },
+ 'ChannelID' => {
+ typ => 'list',
+ def => $con ? $mod->ChannelToPos($timerData->{channel_id}) : $timerData->{channel_id},
+ choices => $con ? $mod->ChannelArray('Name') : $mod->ChannelIDArray('Name'),
+ msg => gettext('Which channel should recorded?'),
+ req => gettext("This is required!"),
+ check => sub{
+ my $value = shift || return;
+
+ if(my $name = $mod->ChannelToName($value)) {
+ $timerData->{Channel} = $value;
+ return $value;
+ } elsif(my $ch = $mod->PosToChannel($value) || $mod->NameToChannel($value) ) {
+ $timerData->{Channel} = $value;
+ return $ch;
+ } elsif( ! $mod->NameToChannel($value)) {
+ return undef, sprintf(gettext("This channel '%s' does not exist!"),$value);
+ } else {
+ return undef, gettext("This is required!");
+ }
+ },
+ },
+ 'Day' => {
+ typ => $con ? 'string' : 'date',
+ def => $timerData->{Day},
+ msg => gettext("Please enter a day (1 to 31) or the weekday in format 'MDMDFSS'."),
+ req => gettext("This is required!"),
+ check => sub{
+ my $value = shift || return;
+ if(($value =~ /^\d+$/ and int($value) <= 31 and int($value) > 0) # 13
+ or ($obj->{newTimerFormat} and $value =~ /^\d{4}\-\d{2}-\d{2}$/sig) # 2005-03-13
+ or $value =~ /^\S{7}\@\d{4}\-\d{2}-\d{2}$/sig # MTWTFSS@2005-03-13
+ or $value =~ /^\S{7}\@\d{2}$/sig # MTWTFSS@13
+ or $value =~ /^\S{7}$/) { # MTWTFSS
+ return $value;
+ } else {
+ return undef, gettext('No right day or corrupt format!');
+ }
+ },
+ },
+ 'Start' => {
+ typ => 'string',
+ def => sub{
+ return fmttime($timerData->{Start});
+ },
+ msg => gettext("Starttime in format 'HH:MM'"),
+ check => sub{
+ my $value = shift;
+ $value = fmttime($value) if($value =~ /^\d+$/sig);
+ return undef, gettext('No right time!') if($value !~ /^\d+:\d+$/sig);
+ my @v = split(':', $value);
+ $value = sprintf('%02d%02d',$v[0],$v[1]);
+ if(int($value) < 2400 and int($value) >= 0) {
+ return sprintf('%04d',$value);
+ } else {
+ return undef, gettext('No right time!');
+ }
+ },
+ },
+ 'Stop' => {
+ typ => 'string',
+ def => sub{
+ return fmttime($timerData->{Stop} || 0 );
+ },
+ msg => gettext("Endtime in format 'HH:MM'"),
+ check => sub{
+ my $value = shift;
+ $value = fmttime($value) if($value =~ /^\d+$/sig);
+ return undef, gettext('No right time!') if($value !~ /^\d+:\d+$/sig);
+ my @v = split(':', $value);
+ $value = sprintf('%02d%02d',$v[0],$v[1]);
+ if(int($value) < 2400 and int($value) >= 0) {
+ return sprintf('%04d',$value);
+ } else {
+ return undef, gettext('No right time!');
+ }
+ },
+ },
+ 'Priority' => {
+ typ => 'integer',
+ msg => sprintf(gettext('Priority (0 .. %d)'),$console->{USER}->{MaxPriority} ? $console->{USER}->{MaxPriority} : 99 ),
+ def => int($timerData->{Priority}),
+ check => sub{
+ my $value = shift || 0;
+ if($value =~ /^\d+$/sig and $value >= 0 and $value < 100) {
+ if($console->{USER}->{MaxPriority} and $value > $console->{USER}->{MaxPriority}) {
+ return undef, sprintf(gettext('Sorry, but maximum priority is limited on %d!'), $console->{USER}->{MaxPriority});
+ }
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'Lifetime' => {
+ typ => 'integer',
+ msg => sprintf(gettext('Lifetime (0 .. %d)'),$console->{USER}->{MaxLifeTime} ? $console->{USER}->{MaxLifeTime} : 99 ),
+ def => int($timerData->{Lifetime}),
+ check => sub{
+ my $value = shift || 0;
+ if($value =~ /^\d+$/sig and $value >= 0 and $value < 100) {
+ if($console->{USER}->{MaxLifeTime} and $value > $console->{USER}->{MaxLifeTime}) {
+ return undef, sprintf(gettext('Sorry, but maximum lifetime is limited on %d!'), $console->{USER}->{MaxLifeTime});
+ }
+ return int($value);
+ } else {
+ return undef, gettext('No right Value!');
+ }
+ },
+ },
+ 'File' => {
+ msg => gettext('Title of recording'),
+ def => $timerData->{File},
+ req => gettext("This is required!"),
+ },
+ ];
+
+ my $Summary = $timerData->{Summary};
+ $Summary =~s/\#~AT\[(\d+)\]//g;
+
+ if(main::getVdrVersion() >= 10344){
+ if($timerData->{Id} || $timerData->{desc}) {
+ my $desc = $timerData->{desc} || $obj->getEpgDesc($timerData->{Id});
+ if($desc) {
+ push(@$questions,
+ 'Description' => {
+ msg => gettext('Description'),
+ typ => 'string',
+ def => $desc,
+ readonly => 1
+ });
+ }
+ }
+
+ push(@$questions,
+ 'Summary' => {
+ typ => 'hidden',
+ def => $Summary,
+ });
+ } else {
+ push(@$questions,
+ 'Summary' => {
+ msg => gettext('Additional description'),
+ def => $Summary,
+ check => sub{
+ my $value = shift || return;
+ $value =~ s/(\r|\n)//sig;
+ return $value;
+ },
+ });
+ }
+ # Ask Questions
+ my $datasave = $console->question(($timerid ? gettext('Edit timer')
+ : gettext('New timer')), $questions, $data);
+
+ if(ref $datasave eq 'HASH') {
+ my $erg = $obj->saveTimer($datasave, $timerid);
+
+ my $error;
+ foreach my $zeile (@$erg) {
+ if($zeile =~ /^(\d{3})\s+(.+)/) {
+ $error = $2 if(int($1) >= 500);
+ }
+ }
+
+ unless($error) {
+ debug sprintf('%s timer with title "%s" is saved%s',
+ ($timerid ? 'Changed' : 'New'),
+ $data->{File},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+ $console->message($erg);
+ } else {
+ error sprintf('%s timer with title "%s" does\'nt saved : %s',
+ ($timerid ? 'Changed' : 'New'),
+ $data->{File},
+ $error
+ );
+ $console->err($erg);
+ }
+ $obj->readData($watcher,$console);
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+ }
+}
+
+# ------------------
+sub deleteTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+ my $timerid = shift || return $console->err(gettext("No Timer ID to delete! Please use tdelete 'tid'")); # If timerid the edittimer
+ my $answer = shift || 0;
+
+ my @timers = reverse sort{ $a <=> $b } split(/[^0-9]/, $timerid);
+
+ my $sql = sprintf('SELECT Id,File,ChannelID,NextStartTime,IF(NOW() between NextStartTime and NextStopTime,1,0) as Running FROM TIMERS where Id in (%s)', join(',' => ('?') x @timers));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@timers)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $data = $sth->fetchall_hashref('Id');
+
+ my $mod = main::getModule('CHANNELS') or return;
+ foreach my $tid (@timers) {
+ unless(exists $data->{$tid}) {
+ $console->err(sprintf(gettext("Timer with number '%s' does not exist in the database!"), $tid));
+ next;
+ }
+
+ if(ref $console and $console->{TYP} eq 'CONSOLE') {
+ $data->{$tid}->{ChannelID} = $mod->ChannelToName($data->{$tid}->{ChannelID});
+
+ $console->table($data->{$tid});
+ my $confirm = $console->confirm({
+ typ => 'confirm',
+ def => 'y',
+ msg => gettext('Are you sure to delete this timer?'),
+ }, $answer);
+ next if(!$answer eq 'y');
+ }
+
+ debug sprintf('Delete timer with title "%s"%s',
+ $data->{$tid}->{File},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $obj->{svdrp}->queue_cmds(sprintf("modt %d off", $tid))
+ if($data->{$tid}->{Running});
+ $obj->{svdrp}->queue_cmds(sprintf("delt %d", $tid));
+ }
+
+ if($obj->{svdrp}->queue_cmds('COUNT')) {
+ my $erg = $obj->{svdrp}->queue_cmds("CALL"); # Aufrufen der Kommandos
+ $console->msg($erg, $obj->{svdrp}->err)
+ if(ref $console);
+
+ sleep(1);
+
+ $obj->readData($watcher,$console);
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+ } else {
+ $console->err(gettext("No timer to delete!"));
+ }
+
+ return 1;
+}
+
+# ------------------
+sub toggleTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $timerid = shift || return $console->err(gettext("No Timer ID to toggle! Please use ttoggle 'id'")); # If timerid the edittimer
+
+ my @timers = reverse sort{ $a <=> $b } split(/[^0-9]/, $timerid);
+
+ my $sql = sprintf('SELECT Id,File,Status,NextStartTime, NextStopTime FROM TIMERS where Id in (%s)', join(',' => ('?') x @timers));
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@timers)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $data = $sth->fetchall_hashref('Id');
+ my $ref;
+
+ for my $timer (@timers) {
+
+ unless(exists $data->{$timer}) {
+ $console->err(sprintf(gettext("Timer with number '%s' does not exist in the database!"), $timer));
+ next;
+ }
+
+ # Build query for all timers with possible collisions
+ $ref .= " or '$data->{$timer}->{NextStartTime}' between NextStartTime and NextStopTime"
+ . " or '$data->{$timer}->{NextStopTime}' between NextStartTime and NextStopTime";
+
+
+ my $status = (($data->{$timer}->{Status} & 1) ? 'off' : 'on');
+
+ debug sprintf('Timer with title "%s" is %s%s',
+ $data->{$timer}->{File},
+ ($status eq 'on' ? 'activated' : 'deactivated'),
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $obj->{svdrp}->queue_cmds("modt $data->{$timer}->{Id} $status"); # Sammeln der Kommandos
+ }
+
+ if($obj->{svdrp}->queue_cmds('COUNT')) {
+
+ my $erg = $obj->{svdrp}->queue_cmds("CALL"); # Aufrufen der Kommandos
+ $console->msg($erg, $obj->{svdrp}->err)
+ if(ref $console and $console->typ ne 'AJAX');
+
+ $obj->readData($watcher, $console);
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+
+ if(ref $console and $console->typ eq 'AJAX') {
+ # { "data" : [ [ ID, ON, RUN, CONFLICT ], .... ] }
+ # { "data" : [ [ 5, 1, 0, 0 ], .... ] }
+ my $sql = sprintf('select Id, Status & 1 as Active, IF(NOW() between NextStartTime and NextStopTime,1,0) as Running, Collision from TIMERS where Id in (%s) %s',
+ join(',' => ('?') x @timers),$ref);
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute(@timers)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $erg = $sth->fetchall_arrayref();
+ $console->table($erg);
+ }
+
+ return 1;
+ } else {
+ $console->err(gettext('No timer to toggle!'));
+ return undef;
+ }
+}
+
+
+# ------------------
+sub insert {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return;
+ my $pos = shift || return;
+ my $checked = shift || 0;
+
+ # import only status which used from vdr and thereby exclude eventid from vdradmin
+ $data->[0] &= 15;
+
+ # change pos to channelid, because change to telnet reader
+ $data->[1] = $obj->{channels}->{$data->[1]}->{Id}
+ if(index($data->[1], '-') < 0);
+
+ # POS
+ unshift(@$data, $pos);
+ $data->[8] =~ s/\|/\:/g;
+
+ # NextTime
+ my $nexttime = $obj->getNextTime( $data->[3], $data->[4], $data->[5] )
+ or return error(sprintf("Can't get time form this data: %s", join(' ', @$data)));
+ push(@$data, $nexttime->{start}, $nexttime->{stop});
+
+ # insert placeholder
+ push(@$data, 0); # eventid
+ push(@$data, 0); # eventstarttime
+ push(@$data, 0); # eventduration
+
+ # AutotimerId
+ my $atxt = (split('~', $data->[9]))[-1];
+ my $aid = $1 if(defined $atxt and $atxt =~ /AT\[(\d+)\]/);
+ push(@$data, $aid || 0);
+
+ # checked
+ push(@$data, $checked);
+
+ # Search for event at EPG
+ my $e = $obj->_getNextEpgId( {
+ Id => $data->[0],
+ ChannelID => $data->[2],
+ File => $data->[8],
+ NextStartTime => $data->[10],
+ NextStopTime => $data->[11],
+ });
+ if($e and exists $e->{eventid}) {
+ $data->[12] = $e->{eventid};
+ $data->[13] = $e->{starttime};
+ $data->[14] = $e->{duration};
+ }
+
+ my $sth = $obj->{dbh}->prepare('REPLACE INTO TIMERS VALUES (?,?,?,?,?,?,?,?,?,?,FROM_UNIXTIME(?), FROM_UNIXTIME(?),0,?,?,?,?,NOW(),?)');
+ $sth->execute( @$data );
+}
+
+
+# Read from svdrp (better for future development)
+# ------------------
+sub readData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift;
+ my $console = shift;
+
+ # Search for old and deactivated Timers and delete this
+ $obj->getOldDeactivTimer()
+ if($obj->{deactive} eq 'y');
+
+ # Search for correct times
+ $obj->getCheckTimer()
+ if($obj->{adjust} eq 'y');
+
+ my $oldTimers = &getDataByTable('TIMERS');
+
+ $obj->{dbh}->do('DELETE FROM TIMERS');
+
+ # read from svdrp, because the
+ # vdr edit the timers.conf to lazy ;)
+ $obj->{channels} = main::getModule('CHANNELS')->ChannelHash('POS');
+ my $tlist = $obj->{svdrp}->command('lstt');
+
+ my $c = 0;
+ foreach my $line (@$tlist) {
+ next if(! $line or $line =~ /^22/);
+ $line =~ s/^\d+[- ]+\d+\s//sig;
+ $c++;
+ my @data = split(':', $line, 9);
+
+ $obj->insert(\@data, $c, 1)
+ if(scalar @data > 2);
+ }
+
+ # Search for overlapping Timers
+ my $overlapping = $obj->getOverlappingTimer();
+
+ # Get timers by Autotimer
+ my $aids = getDataByFields('AUTOTIMER', 'Id');
+ $obj->getTimersByAutotimer($aids);
+
+ # Get new timers by User
+ if($oldTimers) {
+ my $timers = $obj->getNewTimers($oldTimers);
+ foreach my $timerdata (@$timers) {
+ event('New timer "%s" with id: "%d"', $timerdata->{File}, $timerdata->{Id});
+ }
+ $obj->updated() if(scalar @$timers);
+ }
+
+ $obj->{REGISTER}++;
+ if(scalar keys %$oldTimers != $c or $obj->{REGISTER} == 2) {
+ # Event to signal we are finish to read
+ event(sprintf('Reread %d timers and written to DB!', $c));
+ }
+
+ $console->message(sprintf(gettext("Write %d timers in database."), $c), {overlapping => $overlapping})
+ if(ref $console and $console->typ ne 'AJAX');
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
+ if(ref $console and $console->typ eq 'HTML');
+
+ return 1;
+}
+
+# Routine um Callbacks zu registrieren und
+# diese nach dem Aktualisieren der Timer zu starten
+# ------------------
+sub updated {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cb = shift || 0;
+ my $log = shift || 0;
+
+ if($cb) {
+ push(@{$obj->{after_updated}}, [$cb, $log]);
+ } else {
+ foreach my $CB (@{$obj->{after_updated}}) {
+ next unless(ref $CB eq 'ARRAY');
+ lg $CB->[1]
+ if($CB->[1]);
+ &{$CB->[0]}()
+ if(ref $CB->[0] eq 'CODE');
+ }
+ }
+}
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $text = shift || '';
+
+ my $in = '';
+ if($text and $text =~ /^[0-9,_ ]+$/ ) {
+ my @timers = split(/[^0-9]/, $text);
+ $in = sprintf("and t.Id in ( %s )",join(',',@timers));
+ } elsif($text) {
+ $in = sprintf('and ( %s )', buildsearch("t.File,t.Summary",$text));
+ }
+ my %f = (
+ 'Id' => umlaute(gettext('Sv')),
+ 'Status' => umlaute(gettext('Status')),
+ 'Prg' => umlaute(gettext('Prg')),
+ 'Channel' => umlaute(gettext('Channel')),
+ 'Start' => umlaute(gettext('Start')),
+ 'Stop' => umlaute(gettext('Stop')),
+ 'File' => umlaute(gettext('File')),
+ 'Priority' => umlaute(gettext('Priority')),
+ );
+
+ my $sql = qq|
+SELECT
+ t.Id as $f{'Id'},
+ t.Status as $f{'Status'},
+ c.Name as $f{'Channel'},
+ c.Pos as __Pos,
+ t.Day as $f{'Prg'},
+ DATE_FORMAT(t.NextStartTime, '%H:%i') as $f{'Start'},
+ DATE_FORMAT(t.NextStopTime, '%H:%i') as $f{'Stop'},
+ t.File as $f{'File'},
+ t.Priority as $f{'Priority'},
+ UNIX_TIMESTAMP(t.NextStartTime) as __Day,
+ t.Collision as __Collision,
+ t.eventid as __eventid,
+ t.AutotimerId as __AutotimerId,
+ UNIX_TIMESTAMP(t.NextStopTime) - UNIX_TIMESTAMP(t.NextStartTime) as __Duration,
+ e.description as __description
+FROM
+ TIMERS as t,
+ CHANNELS as c,
+ EPG as e
+WHERE
+ t.ChannelID = c.Id
+ and (t.eventid = e.eventid)
+ $in
+
+UNION
+
+SELECT
+ t.Id as $f{'Id'},
+ t.Status as $f{'Status'},
+ c.Name as $f{'Channel'},
+ c.Pos as __Pos,
+ t.Day as $f{'Prg'},
+ DATE_FORMAT(t.NextStartTime, '%H:%i') as $f{'Start'},
+ DATE_FORMAT(t.NextStopTime, '%H:%i') as $f{'Stop'},
+ t.File as $f{'File'},
+ t.Priority as $f{'Priority'},
+ UNIX_TIMESTAMP(t.NextStartTime) as __Day,
+ t.Collision as __Collision,
+ t.eventid as __eventid,
+ t.AutotimerId as __AutotimerId,
+ UNIX_TIMESTAMP(t.NextStopTime) - UNIX_TIMESTAMP(t.NextStartTime) as __Duration,
+ "" as __description
+FROM
+ TIMERS as t,
+ CHANNELS as c
+WHERE
+ t.ChannelID = c.Id
+ and (t.eventid = 0)
+ $in
+
+ORDER BY
+ __Day
+|;
+
+ my $fields = fields($obj->{dbh}, $sql);
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+ $console->table($erg, {
+ runningTimer => $obj->getRunningTimer,
+ cards => $obj->{DVBCards},
+ capacity => main::getModule('RECORDS')->{CapacityFree},
+ });
+}
+
+# ------------------
+sub getTimerById {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $tid = shift || return error ('No TimerId!' );
+
+ my $sql = qq|
+SELECT
+ t.Id,
+ t.Status,
+ c.Name as Channel,
+ c.Pos as __Pos,
+ t.Day as Prg,
+ t.Start,
+ t.Stop,
+ t.File,
+ t.Priority,
+ UNIX_TIMESTAMP(t.NextStartTime) as Day,
+ t.Collision as Collision,
+ t.eventid as eventid,
+ t.AutotimerId as AutotimerId
+FROM
+ TIMERS as t,
+ CHANNELS as c
+WHERE
+ t.ChannelID = c.Id
+ and t.Id = ?
+|;
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($tid)
+ or return error(sprintf("Timer ID '%s' does not exist in the database!",$tid));
+ return $sth->fetchrow_hashref();
+}
+
+
+# ------------------
+sub getRunningTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $rowname = shift || 'Id';
+ my $sql = "select $rowname from TIMERS where NOW() between NextStartTime and NextStopTime AND (Status & 1)";
+ my $erg = $obj->{dbh}->selectall_hashref($sql, $rowname);
+ return $erg;
+}
+
+# ------------------
+sub getNewTimers {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $oldTimers = shift || return;
+
+ my $ret = [];
+ foreach my $timerid (keys %$oldTimers) {
+ if(! $oldTimers->{$timerid}->{Checked}) {
+ push(@$ret, $oldTimers->{$timerid});
+ }
+ }
+ return $ret;
+}
+
+# ------------------
+sub getOldDeactivTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $sql = "select Id from TIMERS where not (Status & 1) and UNIX_TIMESTAMP(NextStopTime) > UNIX_TIMESTAMP() + (60*60*24*28)";
+ my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');
+
+ foreach my $t (reverse sort {$a <=> $b} keys %$erg) {
+ $obj->{svdrp}->queue_cmds("delt $t");
+ }
+ $obj->{svdrp}->queue_cmds("CALL")
+ if($obj->{svdrp}->queue_cmds("COUNT"));
+ return $erg;
+}
+
+# ------------------
+sub getCheckTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $sql = qq|
+SELECT t.Id as Id, t.Status as Status,t.ChannelID as ChannelID,
+ t.Priority as Priority, t.Lifetime as Lifetime,
+ t.File as File, t.Summary as Summary,
+ t.Start as TimerStart,t.Stop as TimerStop,
+
+ UNIX_TIMESTAMP(e.starttime) as starttime,
+ UNIX_TIMESTAMP(e.starttime) + e.duration as stoptime,
+ UNIX_TIMESTAMP(e.vpstime) as vpsstart,
+ UNIX_TIMESTAMP(e.vpstime) + e.duration as vpsstop,
+
+ ABS(UNIX_TIMESTAMP(t.eventstarttime) - UNIX_TIMESTAMP(NextStartTime)) as lead,
+ ABS(UNIX_TIMESTAMP(NextStopTime)-(UNIX_TIMESTAMP(t.eventstarttime) + t.eventduration)) as lag
+
+ FROM TIMERS as t, EPG as e
+ WHERE (Status & 1)
+ AND e.eventid > 0
+ AND t.eventid = e.eventid
+ AND ((e.starttime != t.eventstarttime) OR (e.duration != t.eventduration))
+ AND SUBSTRING_INDEX( t.File , '~', 1 ) LIKE CONCAT('%', e.title ,'%')
+|;
+ my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');
+
+ foreach my $t (keys %$erg) {
+ my %tt;
+
+# dumper($erg->{$t});
+
+ # Adjust start and stop times
+ my $start;
+ my $stop;
+
+ if(($erg->{$t}->{Status} & 4) # Use VPS Time if used
+ and $erg->{$t}->{vpsstart}
+ and $erg->{$t}->{vpsstop}) {
+ $start = $erg->{$t}->{vpsstart};
+ $stop = $erg->{$t}->{vpsstop};
+ } else {
+ $start = $erg->{$t}->{starttime} - $erg->{$t}->{lead};
+ $stop = $erg->{$t}->{stoptime} + $erg->{$t}->{lag};
+ }
+
+ # Format parameterhash for saveTimer
+ my $tt = {
+ Activ => (($erg->{$t}->{Status} & 1) ? 'y' : 'n'),
+ VPS => (($erg->{$t}->{Status} & 4) ? 'y' : 'n'),
+ ChannelID => $erg->{$t}->{ChannelID},
+ File => $erg->{$t}->{File},
+ Summary => $erg->{$t}->{Summary},
+ Day => $obj->{newTimerFormat}?my_strftime("%Y-%m-%d",$start):my_strftime("%d",$start),
+ Start => my_strftime("%H%M",$start),
+ Stop => my_strftime("%H%M",$stop),
+ Priority => $erg->{$t}->{Priority},
+ Lifetime => $erg->{$t}->{Lifetime}
+ };
+
+ my $timer = $erg->{$t}->{Id};
+
+ debug sprintf("Adjust timer %d (%s) at %s : from %s - %s to %s - %s",
+ $timer,
+ $tt->{File},
+ $tt->{Day},
+ fmttime($erg->{$t}->{TimerStart}), fmttime($erg->{$t}->{TimerStop}),
+ fmttime($tt->{Start}),fmttime($tt->{Stop}));
+
+ $obj->saveTimer($tt, $timer);
+ }
+ return $erg;
+}
+
+# ------------------
+sub getEpgIds {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $sql = "select Id, Status & 1 as Status, eventid from TIMERS where eventid > 0";
+ my $erg = $obj->{dbh}->selectall_hashref($sql, 'eventid');
+ return $erg;
+}
+
+# ------------------
+sub getEpgDesc {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $tid = shift || return error ('No TimerId!' );
+
+ my $sql = qq|
+select
+ description from TIMERS as t, EPG as e
+where
+ e.eventid > 0 and
+ t.eventid = e.eventid and
+ t.id = ?
+|;
+
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($tid)
+ or return error(sprintf("Timer ID '%s' does not exist in the database!",$tid));
+ my $erg = $sth->fetchrow_hashref();
+ return $erg ? $erg->{description} : '';
+}
+
+# ------------------
+sub getOverlappingTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $sql = qq|
+select
+ TIMERS.Id,
+ TIMERS.Priority,
+ TIMERS.NextStartTime,
+ TIMERS.NextStopTime,
+ CHANNELS.TID as transponderid,
+ LEFT(CHANNELS.Source,1) as source
+from TIMERS,
+ CHANNELS
+where TIMERS.ChannelID = CHANNELS.Id
+|;
+ my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');
+ my $return;
+
+ my $sth = $obj->{dbh}->prepare("UPDATE TIMERS SET Collision = ? WHERE Id = ?");
+ foreach my $tid (keys %$erg) {
+ my $result = $obj->checkOverlapping($erg->{$tid});
+ if(ref $result eq 'ARRAY' and scalar @$result) {
+ my $col = join(',',@$result);
+ $sth->execute($col,$tid);
+ $return->{"timer_$tid"} = $col;
+ }
+
+
+ }
+ return $return;
+}
+
+# ------------------
+sub checkOverlapping {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+
+ my $NextStartTime = $data->{NextStartTime};
+ my $NextStopTime = $data->{NextStopTime};
+ my $transponder = $data->{transponderid};
+ my $source = $data->{source};
+ my $Priority = $data->{Priority} || $obj->{Priority};
+ my $tid = $data->{Id} || 0;
+
+ my $sql = qq|
+SELECT
+ t.Id,
+ t.Priority,
+ c.TID
+FROM
+ TIMERS as t, CHANNELS as c
+WHERE
+ ((? between t.NextStartTime AND t.NextStopTime)
+ OR (? between t.NextStartTime AND t.NextStopTime)
+ OR (t.NextStartTime between ? AND ?)
+ OR (t.NextStopTime between ? AND ?))
+ AND t.Id != ?
+ AND (t.Status & 1)
+ AND t.ChannelID = c.Id
+ AND c.TID != ?
+ AND LEFT(c.Source,1) = ?
+ORDER BY
+ t.Priority desc
+|;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute($NextStartTime,$NextStopTime,
+ $NextStartTime,$NextStopTime,
+ $NextStartTime,$NextStopTime,
+ $tid,$transponder,$source)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $result = $sth->fetchall_arrayref();
+
+ if(scalar @{$result}) {
+ my $coltext = [];
+ foreach my $probant (@{$result}) {
+
+ if(defined $probant->[0]) {
+
+ # current timer has higher Priority
+ last
+ if($Priority > $probant->[1]);
+
+
+ # Store conflict line at line
+ my $col = sprintf('%d:%d',
+ $probant->[0],
+ $probant->[1]);
+
+ # insert double transponder, on same line
+ my $n = 0;
+ foreach my $trans (@{$result}) {
+
+ if(defined $trans->[0]
+ && $probant->[0] != $trans->[0]
+ && $probant->[2] == $trans->[2]) {
+
+ $col .= sprintf('|%d:%d',
+ $trans->[0],
+ $trans->[1]);
+
+ undef @{$result}[$n]->[0];
+ }
+ ++$n;
+ }
+ # Add line
+ push(@$coltext,$col);
+ }
+ }
+ if(scalar(@$coltext) > $obj->{DVBCards} - 1) {
+ return $coltext;
+ }
+ }
+}
+
+# ------------------
+sub getNextTimer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $erg = $obj->{svdrp}->command('NEXT abs');
+ my @eerg = grep(/^250/, @$erg);
+ if(scalar @eerg and my ($errcode, $nextTimer, $zeit) = split(/\s+/, $eerg[0])) {
+ return if(
+ ! $nextTimer
+ or $zeit < time
+ or (ref $obj->{NextTimerEvent} and $obj->{NextTimerEvent}->at == $zeit)
+ );
+
+ my $timer = $obj->getTimerById($nextTimer);
+
+ $obj->{NextTimerEvent} = Event->timer(
+ at => $zeit,
+ data => $timer,
+ hard => 1,
+ repeat => 0,
+ prio => 2, # -1 very hard ... 6 very low
+ cb => sub{
+ my $event = shift;
+ my $watcher = $event->w;
+ my $data = $watcher->data;
+
+ my $reportmod = main::getModule('REPORT');
+ $reportmod->news(
+ sprintf(gettext("Timer %d with title '%s' is start to recording!"), $data->{Id}, $data->{File}),
+ sprintf(gettext("on channel: %s until %s"), $data->{Channel}, fmttime($data->{Stop})),
+ 'tedit',
+ $data->{Id},
+ 'harmless'
+ );
+ $watcher->cancel;
+ },
+ );
+ }
+}
+
+# Find EPG to selected timer
+# ------------------
+sub _getNextEpgId {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $timer = shift || return error ('No Hash!' );
+
+ my $e;
+ my @file = split('~', $timer->{File});
+
+ if(scalar @file >= 2) { # title and subtitle defined
+ my $sth = $obj->{dbh}->prepare(qq|
+ SELECT eventid,starttime,duration from EPG
+ WHERE
+ channel_id = ?
+ AND ((UNIX_TIMESTAMP(starttime) + (duration/2)) between ? and ? )
+ AND (title like ? or title like ? )
+ ORDER BY ABS(( ? )-UNIX_TIMESTAMP(starttime)) LIMIT 1
+ |);
+ if(!$sth->execute($timer->{ChannelID},
+ $timer->{NextStartTime},
+ $timer->{NextStopTime},
+ '%'.$file[-2].'%',
+ '%'.$file[-1].'%',
+ $timer->{NextStartTime})) {
+ lg sprintf("Can't find epg event for timer with id %d - %s", $timer->{Id} , $timer->{File} );
+ return 0;
+ }
+ $e = $sth->fetchrow_hashref();
+
+ } else {
+ my $sth = $obj->{dbh}->prepare(qq|
+ SELECT eventid,starttime,duration from EPG
+ WHERE
+ channel_id = ?
+ AND ((UNIX_TIMESTAMP(starttime) + (duration/2)) between ? and ? )
+ AND (title like ? )
+ ORDER BY ABS(( ? )-UNIX_TIMESTAMP(starttime)) LIMIT 1
+ |);
+ if(!$sth->execute($timer->{ChannelID},
+ $timer->{NextStartTime},
+ $timer->{NextStopTime},
+ '%'.$timer->{File}.'%',
+ $timer->{NextStartTime})) {
+ lg sprintf("Can't find epg event for timer with id %d - %s", $timer->{Id} , $timer->{File} );
+ return 0;
+ }
+ $e = $sth->fetchrow_hashref();
+ }
+
+
+ lg sprintf("Can't find epg event for timer with id %d - %s", $timer->{Id} , $timer->{File} )
+ if(not exists $e->{eventid});
+ return $e;
+}
+
+# The following subroutines is stolen from vdradmind and vdradmin-0.97-am
+# Thanks on Cooper and Thomas for this great work!
+# $obj->getNextTime('MDMDFSS', 1300, 1200)
+# ------------------
+sub getNextTime {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $dor = shift || return error ('No Day!' );
+ my $start = shift || return error ('No Starttime!' );
+ my $stop = shift || return error ('No Stoptime!' );
+
+ $start = sprintf('%04d', $start);
+ $stop = sprintf('%04d', $stop);
+
+ my ($startsse, $stopsse);
+ if(length($dor) == 7) { # repeating timer => MTWTFSS
+ $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"));
+ $stopsse += 86400 if($stopsse < $startsse);
+
+ my $weekday = ((localtime(time))[6] + 6) % 7;
+ my $perrec = join("", substr($dor, $weekday), substr($dor, 0, $weekday));
+ $perrec =~ m/^-+/g;
+
+ my $off = (pos $perrec || 0) * 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 || 0) + 1) * 86400;
+ }
+ $startsse += $off;
+ $stopsse += $off;
+ } elsif(length($dor) == 18) { # first-day timer => MTWTFSS@2005-03-13
+ $dor =~ /.{7}\@(\d{4})-(\d{2})-(\d{2})/;
+ $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
+ if ($dor =~ /(\d{4})-(\d{2})-(\d{2})/) { # vdr >= 1.3.23 => 2005-03-13
+ $startsse = my_mktime(substr($start, 2, 2),
+ substr($start, 0, 2), $3, ($2 - 1), $1);
+
+ $stopsse = my_mktime(substr($stop, 2, 2),
+ substr($stop, 0, 2), $stop > $start ? $3 : $3 + 1, ($2 - 1), $1);
+ $obj->{newTimerFormat} = 1;
+ }
+ else { # vdr < 1.3.23 => 13
+ $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)));
+ }
+ }
+ }
+
+ my $ret = {
+ start => $startsse,
+ stop => $stopsse,
+ };
+ return $ret;
+}
+
+# ------------------
+# Name: getTimersByAutotimer
+# Descr: Routine group Autotimer to Timers.
+# Usage: $hash = $obj->getTimersByAutotimer([$aid, $aid, $aid, ...]);
+# ------------------
+sub getTimersByAutotimer {
+ my $obj = shift || return error ('No Object!' );
+ my $aids = shift || return $obj->{AIDS};
+
+ $obj->{AIDS} = {};
+ for my $aid (@$aids) {
+ $obj->{AIDS}->{$aid} = {
+ allTimer => [],
+ activeTimer => [],
+ deactiveTimer => [],
+ };
+ my $erg = getDataBySearch('TIMERS', sprintf('AutotimerId = %d', $aid));
+ map {
+ my $type = ($_->[1] ? 'activeTimer' : 'deactiveTimer');
+ push(@{$obj->{AIDS}->{$aid}->{$type}}, $_->[0]);
+ push(@{$obj->{AIDS}->{$aid}->{allTimer}}, $_->[0]);
+ } @$erg;
+ }
+ return $obj->{AIDS};
+}
+
+# ------------------
+# Name: getRootDirs
+# Descr: Get first root dir's.
+# Usage: $hash = $obj->getRootDirs([$count]);
+# ------------------
+sub getRootDirs {
+ my $obj = shift || return error ('No Object!' );
+ my $count = shift || 1;
+ my $sql = "select distinct SUBSTRING_INDEX(File,'~',$count) from TIMERS;";
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ my @ret;
+ for(@$erg) {
+ push(@ret, $_->[0]);
+ }
+ return \@ret;
+}
+
+sub my_mktime {
+ my $sec = 0;
+ my $min = shift;
+ my $hour = shift;
+ my $mday = shift;
+ my $mon = shift;
+ my $year = shift() - 1900;
+
+ return mktime($sec, $min, $hour, $mday, $mon, $year, 0, 0, -1);
+}
+
+sub my_strftime {
+ my $format = shift;
+ my $time = shift || time;
+ return(strftime($format, localtime($time)));
+}
+
+# ------------------
+sub suggest { # ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+ my $params = shift;
+
+ if($search) {
+ my $sql = qq|
+ SELECT
+ File
+ FROM
+ TIMERS
+ WHERE
+ ( File LIKE ? )
+ GROUP BY
+ File
+ ORDER BY
+ File
+ LIMIT 25
+ |;
+ my $sth = $obj->{dbh}->prepare($sql);
+ $sth->execute('%'.$search.'%')
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ my $result = $sth->fetchall_arrayref();
+ $console->table($result)
+ if(ref $console && $result);
+ }
+}
+
+1;
+
+
+1;
diff --git a/lib/XXV/MODULES/USER.pm b/lib/XXV/MODULES/USER.pm
new file mode 100644
index 0000000..852cfd9
--- /dev/null
+++ b/lib/XXV/MODULES/USER.pm
@@ -0,0 +1,919 @@
+package XXV::MODULES::USER;
+
+use strict;
+
+use Tools;
+use Locale::gettext;
+use File::Path;
+
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'USER',
+ Prereq => {
+ 'Net::IP::Match::Regexp qw( create_iprange_regexp match_ip )'
+ => 'Efficiently match IPv4 addresses against IPv4 ranges via regexp ',
+ },
+ Description =>
+gettext("This module managed a Useradministration Interface.
+for use you can set a Level to the hole Modul with
+a parameter 'Level' in the main root or you can set
+the same parameter in a function."),
+ Version => '0.94',
+ Date => '2007-06-24',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Switch the Userauthentification on or off'),
+ default => 'y',
+ type => 'confirm',
+ },
+ withAuth => {
+ description => gettext('IP addresses with user authentification'),
+ default => '',
+ type => 'string',
+ check => sub{
+ my $value = shift || return;
+ my @ips = split(/\s*,\s*/, $value);
+ for (@ips) {
+ return undef, sprintf(gettext('Your IP number (%s) is wrong! You need a IP with range (xxx.xxx.xxx.xxx/xx)'), $_)
+ unless ($_ =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d+/);
+ }
+ return $value;
+ },
+ },
+ noAuth => {
+ description => gettext('IP addresses without user authentification'),
+ default => '',
+ type => 'string',
+ check => sub{
+ my $value = shift || return;
+ my @ips = split(/\s*,\s*/, $value);
+ for (@ips) {
+ return undef, sprintf(gettext('Your IP number (%s) is wrong! You need a IP with range (xxx.xxx.xxx.xxx/xx)'), $_)
+ unless ($_ =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d+/);
+ }
+ return $value;
+ },
+ },
+ tempimages => {
+ description => gettext('common directory for temporary images'),
+ default => '/var/cache/xxv/temp',
+ type => 'dir',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ unew => {
+ description => gettext('Create a new account for user'),
+ short => 'un',
+ callback => sub{ $obj->create(@_) },
+ Level => 'admin',
+ },
+ udelete => {
+ description => gettext("Delete a account of user 'uid'"),
+ short => 'ud',
+ callback => sub{ $obj->delete(@_) },
+ Level => 'admin',
+ },
+ uedit => {
+ description => gettext("Edit a account of user 'uid'"),
+ short => 'ue',
+ callback => sub{ $obj->edit(@_) },
+ Level => 'admin',
+ },
+ uprefs => {
+ description => gettext("Change the own preferences"),
+ short => 'up',
+ callback => sub{ $obj->userprefs(@_) },
+ Level => 'user',
+ },
+ ulist => {
+ description => gettext("List the accounts of users"),
+ short => 'ul',
+ callback => sub{ $obj->list(@_) },
+ Level => 'admin',
+ },
+ logout => {
+ description => gettext("Logout from the current Session"),
+ short => 'exit',
+ callback => sub{
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ if($obj->{active} eq 'y') {
+ $console->message(gettext("Session closed."));
+ $console->redirect({url => '?', parent => 'top', wait => 2})
+ if($console->typ eq 'HTML');
+
+ my $ConsoleMod;
+ my $delayed = 0;
+ if($console->typ eq 'HTML' || $console->typ eq 'AJAX') {
+ $ConsoleMod = main::getModule('HTTPD');
+ $delayed = 1;
+ } elsif ($console->typ eq 'WML') {
+ $ConsoleMod = main::getModule('WAPD');
+ $delayed = 1;
+ } elsif ($console->typ eq 'CONSOLE') {
+ $ConsoleMod = main::getModule('TELNET');
+ };
+
+ if($delayed) {
+ # Close session delayed, give browser my time load depends files like style.css
+ Event->timer(
+ after => 1,
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ $obj->logout;
+ delete $console->{USER} if($console->{USER});
+ $ConsoleMod->{LOGOUT} = 1 if($ConsoleMod);
+ },
+ );
+ } else {
+ $obj->logout;
+ delete $console->{USER} if($console->{USER});
+ $ConsoleMod->{LOGOUT} = 1 if($ConsoleMod);
+ }
+ }
+ },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return 0, panic("Session to database is'nt connected")
+ unless($obj->{dbh});
+
+ # don't remove old table, if updated rows => warn only
+ tableUpdated($obj->{dbh},'USER',9,0);
+
+ # Look for table or create this table
+ my $version = main::getVersion;
+ my $erg = $obj->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS USER (
+ Id int(11) unsigned auto_increment NOT NULL,
+ Name varchar(100) NOT NULL default '',
+ Password varchar(100) NOT NULL,
+ Level set('admin', 'user', 'guest' ) NOT NULL,
+ Prefs varchar(100) default '',
+ UserPrefs varchar(100) default '',
+ Deny set('tlist', 'alist', 'rlist', 'mlist', 'tedit', 'aedit', 'redit', 'remote', 'stream', 'cedit', 'media'),
+ MaxLifeTime tinyint(2) default '0',
+ MaxPriority tinyint(2) default '0',
+ PRIMARY KEY (Id)
+ ) COMMENT = '$version'
+ |);
+
+ # The Table is empty? Make a default User ...
+ unless($obj->{dbh}->selectrow_arrayref('select count(*) from USER')->[0]) {
+ $obj->_insert({
+ Name => 'xxv',
+ Password => 'xxv',
+ Level => 'admin',
+ });
+ }
+}
+
+
+# ------------------
+# Name: create
+# Descr: Save a new User in the Usertable.
+# Usage: my $ok = $obj->create($watcher, $console, 0, {name => 'user', ...});
+# ------------------
+sub create {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $data = shift || 0;
+
+ $obj->edit($watcher, $console, $id, $data);
+
+}
+
+# ------------------
+sub userprefs {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || $obj->{USER}->{Id};
+ my $data = shift || 0;
+
+ my $user;
+ if($id and not ref $data) {
+ my $sth = $obj->{dbh}->prepare('select * from USER where Id = ?');
+ $sth->execute($id)
+ or return $console->err(sprintf(gettext("Account for user with ID '%s' does not exist in the database!"),$id));
+ $user = $sth->fetchrow_hashref();
+ }
+
+ my $questions = [
+ 'Id' => {
+ typ => 'hidden',
+ def => $user->{Id} || 0,
+ },
+ 'Password' => {
+ typ => 'password',
+ msg => gettext("Password for this account"),
+ req => gettext('This is required!'),
+ def => '',
+ check => sub{
+ my $value = shift || return;
+ # If no password given the
+ # take the old password as default
+ if($console->typ eq 'HTML') {
+ if($value->[0] and $value->[0] ne $value->[1]) {
+ return undef, gettext("Field with 1st and 2nd password must be equal to confirm!");
+ } else {
+ return $value->[0];
+ }
+ }
+ else {
+ return $value;
+ }
+ },
+ },
+ 'UserPrefs' => {
+ def => $user->{UserPrefs} || '',
+ msg => gettext("Personality preferences for this User: ModName::Param=value, "),
+ typ => 'string',
+ check => sub{
+ my $value = shift || return;
+ foreach my $pref (split(',', $value)) {
+ my ($modname, $parameter, $value) = $pref =~ /(\S+)::(\S+)\=(.+)/sg;
+ if(my $mod = main::getModule($modname)) {
+ unless(exists $mod->{$parameter}) {
+ return undef, sprintf(gettext("The Parameter '%s' in Module '%s' doesn't exist!"),$parameter, $mod);
+ }
+ }
+ }
+ return $value;
+ },
+ },
+ ];
+
+ # Ask Questions
+ $data = $console->question(sprintf(gettext('Edit preferences of user: %s'), $obj->{USER}->{Name}), $questions, $data);
+
+ if(ref $data eq 'HASH') {
+ $obj->_insert($data);
+
+ $obj->refreshUserSettings($data->{UserPrefs}, $user->{UserPrefs});
+
+ $console->message(gettext('Account for user saved!'));
+ if($console->typ eq 'HTML') {
+ $console->redirect({url => '?', parent => 'top', wait => 2});
+ $console->message(gettext('Please wait ... refresh the interface!'));
+ }
+ }
+ return 1;
+}
+
+
+# ------------------
+# Name: edit
+# Descr: Edit an existing User in the Usertable.
+# Usage: my $ok = $obj->edit($watcher, $console, $id, [$data]);
+# ------------------
+sub edit {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || 0;
+ my $data = shift || 0;
+
+ my $user;
+ if($id and not ref $data) {
+ my $sth = $obj->{dbh}->prepare('select * from USER where Id = ?');
+ $sth->execute($id)
+ or return $console->err(sprintf(gettext("Account for user with ID '%s' does not exist in the database!"),$id));
+ $user = $sth->fetchrow_hashref();
+
+ # question erwartet ein Array
+ my @deny = split(/\s*,\s*/, $user->{Deny});
+ $user->{Deny} = \@deny;
+ }
+
+ my %l = (
+ 'admin' => gettext('Administrator'),
+ 'user' => gettext('User'),
+ 'guest' => gettext('Guest')
+ );
+
+ my $questions = [
+ 'Id' => {
+ typ => 'hidden',
+ def => $user->{Id} || 0,
+ },
+ 'Name' => {
+ msg => gettext("Name from this account"),
+ req => gettext('This is required!'),
+ def => $user->{Name} || '',
+ },
+ 'Password' => {
+ typ => 'password',
+ msg => gettext("Password for this account"),
+ req => gettext('This is required!'),
+ def => '',
+ check => sub{
+ my $value = shift || return;
+ # If no password given the
+ # take the old password as default
+ if($console->typ eq 'HTML') {
+ if($value->[0] and $value->[0] ne $value->[1]) {
+ return undef, gettext("Field with 1st and 2nd password must be equal to confirm!");
+ } else {
+ return $value->[0];
+ }
+ }
+ else {
+ return $value;
+ }
+ },
+ },
+ 'Level' => {
+ def => sub {
+ my $value = $user->{Level} || 'guest';
+ return $l{$value};
+ },
+ msg => gettext("Level for this account"),
+ typ => 'radio',
+ req => gettext('This is required!'),
+ choices => [$l{'admin'},$l{'user'},$l{'guest'}],
+ check => sub{
+ my $value = shift || return;
+ my $data = shift || return error('No Data in CB');
+ unless(grep($_ eq $value, @{$data->{choices}})) {
+ my $ch = join(' ', @{$data->{choices}});
+ return undef, sprintf(gettext("You can choose: %s!"),$ch);
+ }
+ foreach my $k (keys %l) {
+ return $k
+ if($value eq $l{$k});
+ }
+ my $ch = join(' ', @{$data->{choices}});
+ return undef, sprintf(gettext("You can choose: %s!"),$ch);
+ },
+ },
+ 'Deny' => {
+ msg => gettext('Deny class of commands'),
+ typ => 'checkbox',
+ choices => ['tlist', 'alist', 'rlist', 'mlist', 'tedit', 'aedit', 'redit', 'remote', 'stream', 'cedit', 'media'],
+ def => $user->{Deny} || '',
+ check => sub{
+ my $value = shift || '';
+ my $data = shift || return error('No Data in CB');
+ my @vals = (ref $value eq 'ARRAY') ? @$value : split(/\s*,\s*/, $value);
+
+ foreach my $v (@vals) {
+ unless(grep($_ eq $v, @{$data->{choices}})) {
+ my $ch = join(' ', @{$data->{choices}});
+ return undef, sprintf(gettext("You can choose: %s!"),$ch);
+ }
+ }
+ return join(',', @vals);
+ },
+ },
+ 'Prefs' => {
+ def => $user->{Prefs} || '',
+ msg => gettext("Preferences for this User: ModName::Param=value, "),
+ typ => 'string',
+ check => sub{
+ my $value = shift || return;
+ foreach my $pref (split(',', $value)) {
+ my ($modname, $parameter, $value) = $pref =~ /(\S+)::(\S+)\=(.+)/sg;
+ if(my $mod = main::getModule($modname)) {
+ unless(exists $mod->{$parameter}) {
+ return undef, sprintf(gettext("The Parameter '%s' in Module '%s' doesn't exist!"),$parameter, $mod);
+ }
+ }
+ }
+ return $value;
+ },
+ },
+ 'MaxLifeTime' => {
+ msg => gettext("Maximally permitted value for lifetime on timers"),
+ def => $user->{MaxLifeTime} || '0',
+ type => 'integer',
+ check => sub{
+ my $value = shift || return 0;
+ unless(int($value) and int($value) > 0 and int($value) < 100) {
+ return undef, gettext("This value is not a integer or not between 0 and 100");
+ }
+ return $value;
+ },
+ },
+ 'MaxPriority' => {
+ msg => gettext("Maximally permitted value for priority on timers"),
+ def => $user->{MaxPriority} || '0',
+ type => 'integer',
+ check => sub{
+ my $value = shift || return 0;
+ unless(int($value) and int($value) > 0 and int($value) < 100) {
+ return undef, gettext("This value is not a integer or not between 0 and 100");
+ }
+ return $value;
+ },
+ },
+ ];
+
+ # Ask Questions
+ $data = $console->question(($id ? gettext('Edit account of user')
+ : gettext('Create a new account for user')), $questions, $data);
+
+ if(ref $data eq 'HASH') {
+ $obj->_insert($data);
+
+ debug sprintf('%s account with name "%s" is saved%s',
+ ($id ? 'New' : 'Changed'),
+ $data->{Name},
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $console->message(gettext('Account for user saved!'));
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+ }
+ return 1;
+}
+
+# ------------------
+# Name: delete
+# Descr: Delete an existing User in the Usertable with Id.
+# Usage: my $ok = $obj->delete($watcher, $console, $id);
+# ------------------
+sub delete {
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $id = shift || return $console->err(gettext("No ID for Account of user to delete! Please use udelete 'uid'"));
+
+ my $sth = $obj->{dbh}->prepare('delete from USER where Id = ?');
+ $sth->execute($id)
+ or return $console->err(sprintf(gettext("Account for user with ID '%s' does not exist in the database!"),$id));
+ $console->message(sprintf gettext("Account of user %s is deleted."), $id);
+
+ debug sprintf('Delete user account "%s"%s',
+ $id,
+ ( $console->{USER} && $console->{USER}->{Name} ? sprintf(' from user: %s', $console->{USER}->{Name}) : "" )
+ );
+
+ $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
+ if($console->typ eq 'HTML');
+
+}
+
+
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my %f = (
+ 'Id' => umlaute(gettext('Service')),
+ 'Name' => umlaute(gettext('Name')),
+ 'Level' => umlaute(gettext('Level')),
+ 'Prefs' => umlaute(gettext('Preferences')),
+ 'UserPrefs' => umlaute(gettext('UserPreferences')),
+ );
+
+ my $sql = qq|
+select
+ Id as $f{Id},
+ Name as $f{Name},
+ Level as $f{Level},
+ Prefs as $f{Prefs},
+ UserPrefs as $f{UserPrefs}
+from
+ USER
+ |;
+ my $fields = fields($obj->{dbh}, $sql);
+
+ my $erg = $obj->{dbh}->selectall_arrayref($sql);
+ unshift(@$erg, $fields);
+
+ $console->table($erg);
+}
+
+# ------------------
+# Name: logout
+# Descr: The routine for logout the user, this will clean the user temp files
+# and make a rollback to the standard user settings.
+# Usage: my $ok = $obj->logout();
+# ------------------
+sub logout {
+ my $obj = shift || return error ('No Object!' );
+
+ lg sprintf('Logout called%s',
+ $obj->{USER}->{Name} ? sprintf(" by user %s", $obj->{USER}->{Name}) : ""
+ );
+
+ # get the default user settings
+ $obj->setUserSettings($obj->{USER}->{UserPrefs}, 'rollback')
+ if($obj->{USER}->{UserPrefs});
+
+ # get the default settings
+ $obj->setUserSettings($obj->{USER}->{Prefs}, 'rollback')
+ if($obj->{USER}->{Prefs});
+
+ main::toCleanUp($obj->{USER}->{Name});
+ delete $obj->{USER};
+ return 1;
+}
+
+# ------------------
+sub _checkIp {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $handle = shift || return;
+
+ my $ip = getip($handle);
+
+ if($obj->{withAuth}) {
+ my $regexp = create_iprange_regexp(split(/\s*,\s*/, $obj->{withAuth}));
+ if (match_ip($ip, $regexp)) {
+ return 0;
+ }
+ }
+
+ if($obj->{noAuth}) {
+ my $regexp = create_iprange_regexp(split(/\s*,\s*/, $obj->{noAuth}));
+ if (match_ip($ip, $regexp)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+# ------------------
+# Name: check
+# Descr: The loginroutine to check the User Name, Password
+# or the ClientIPAdress.
+# This will return a Userhash with the DB-Entrys.
+# Usage: my $userHash = $obj->check($handle);
+# ------------------
+sub check {
+ my $obj = shift || return error ('No Object!' );
+ my $handle = shift || return;
+
+ if($obj->_checkIp($handle)) {
+ $obj->{USER}->{Name} = undef;
+ $obj->{USER}->{Level} = 'admin';
+ } else {
+ my $name = shift || return;
+ my $password = shift || return;
+
+
+ my $oldprefs = $obj->{USER}->{UserPrefs};
+
+ my $newUser = 0;
+ if((!$obj->{USER}) or (!scalar keys %{$obj->{USER}}) or $name ne $obj->{USER}->{Name}) {
+ lg sprintf('User %s try to login!', $name );
+ $newUser = $name;
+ $obj->logout()
+ if($obj->{USER} and (scalar keys %{$obj->{USER}}));
+ }
+
+ # check User
+ my $sth = $obj->{dbh}->prepare('select * from USER where Name = ? and Password = md5( ? )');
+ $sth->execute($name, $password)
+ or return error sprintf("Can't execute query: %s.",$sth->errstr);
+ $obj->{USER} = $sth->fetchrow_hashref();
+
+ # Set the user settings from user
+ $obj->refreshUserSettings($obj->{USER}->{UserPrefs}, $oldprefs);
+
+ # Set the user settings from admin
+ $obj->setUserSettings($obj->{USER}->{Prefs}, 'set')
+ if($obj->{USER}->{Prefs} and $newUser);
+ }
+
+ if(my $level = $obj->getLevel($obj->{USER}->{Level})) {
+ $obj->{USER}->{value} = $level if($level);
+ }
+
+ return $obj->{USER};
+}
+
+# ------------------
+sub refreshUserSettings {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $newprefs = shift || '';
+ my $oldprefs = shift || '';
+
+ return 1 if($newprefs eq $oldprefs);
+
+ $obj->setUserSettings($oldprefs, 'rollback')
+ if($oldprefs);
+
+ $obj->setUserSettings($newprefs, 'set')
+ if($newprefs);
+
+ my $mod = main::getModule('CONFIG');
+ $mod->reconfigure();
+
+}
+
+
+# ------------------
+sub setUserSettings {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $prefs = shift || return error ('No Settings??');
+ my $mode = shift || 'set';
+
+ foreach my $pref (split(',', $prefs)) {
+ my ($modname, $parameter, $value) = $pref =~ /(\S+)::(\S+)\=(.*)/sg;
+ if($modname and my $mod = main::getModule($modname) and my $cfg = main::getModule('CONFIG')->{config}) {
+ if(exists $mod->{$parameter}) {
+ if($mode eq 'set') {
+ $cfg->{$modname}->{$parameter} = $value;
+ } else {
+ $cfg->{$modname}->{$parameter} = $mod->{$parameter};
+ }
+ } else {
+ error("The Parameter '$parameter' in Module '$mod' is doesn't exist!");
+ }
+ }
+ }
+}
+
+# ------------------
+sub allowCommand {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $modCfg = shift || return error('No Moduleinformation');
+ my $cmdName = shift || return error('No Command name');
+ my $user = shift || return error('No User');
+ my $DontdumpViolation = shift || '';
+
+ if(
+ (exists $modCfg->{Level} and $user->{value} < $obj->getLevel($modCfg->{Level}))
+ or
+ (exists $modCfg->{Commands}->{$cmdName}->{Level} and $user->{value} < $obj->getLevel($modCfg->{Commands}->{$cmdName}->{Level}))
+ or
+ (exists $user->{Deny} and exists $modCfg->{Commands}->{$cmdName}->{DenyClass} and $user->{Deny} =~ /$modCfg->{Commands}->{$cmdName}->{DenyClass}/)
+ ) {
+ error(sprintf('User %s with Level %s has try to call command %s without permissions!',
+ $user->{Name}, $user->{Level}, $cmdName))
+ if($DontdumpViolation eq '');
+ return 0;
+ }
+ return 1;
+}
+
+# ------------------
+# Name: checkCommand
+# Descr: A routine to check the commands, translate the shortcuts.
+# This will return the cmdobj and cmdname if this command ok.
+# $shorterr is set in following Errorcases:
+# 'noactive' = Plugin is not set active
+# 'noperm' = Permission denied for the called User
+# 'noexists' = Command does not exist!
+# $error is the full Errortext to diaply im Userinterface.
+# Usage: my ($cmdobj, $cmdname, $shorterr, $error) = $obj->checkCommand($console, $command);
+# Test:
+sub t_checkCommand {
+ my ($cmdobj, $cmdname, $shorterr, $error, $t)
+ = $_[0]->checkCommand($_[1], 'tl');
+ $t = 1 if(ref $cmdobj and $cmdname eq 'tlist');
+ ($cmdobj, $cmdname, $shorterr, $error)
+ = $_[0]->checkCommand($_[1], 'lalalalal');
+ $t = 1 if(not ref $cmdobj and not $cmdname and $shorterr and $error);
+ return $t;
+}
+# ------------------
+sub checkCommand {
+ my $obj = shift || return error ('No Object!' );
+ my $console = shift || return error ('No Console' );
+ my $ucmd = shift || return error ('No Command' );
+ my $DontdumpViolation = shift || '';
+
+ my $mods = main::getModules();
+ my $err = 0;
+ my $shorterr = 0;
+ my $cmdobj = 0;
+ my $cmdname = 0;
+ my $cfg = main::getModule('CONFIG')->{config};
+ my $ok = 0;
+
+ # Checks the Commands Syntax (double shortcmds?)
+ $obj->checkCmdSyntax()
+ unless(defined $obj->{Check});
+
+ foreach my $modName (keys %{$mods}) {
+ my $modCfg = $mods->{$modName}->{MOD};
+ foreach my $cmdName (sort keys %{$modCfg->{Commands}}) {
+ if(lc($ucmd) eq $cmdName or (exists $modCfg->{Commands}->{$cmdName}->{short} and lc($ucmd) eq $modCfg->{Commands}->{$cmdName}->{short})) {
+ $ok++;
+ $cmdobj = $modCfg->{Commands}->{$cmdName};
+ $cmdname = $cmdName;
+ # Check on active Modul
+ if(exists $mods->{$modName}->{active} and $cfg->{$modCfg->{Name}}->{active} eq 'n') {
+ $err = sprintf(gettext("Sorry but the module %s is inactive! Switch this active in %s:Preferences:active = y"),
+ $modCfg->{Name}, $modCfg->{Name});
+ $shorterr = 'noactive';
+ }
+
+ if($obj->{active} eq 'y') {
+ # Check Userlevel and Permissions
+ unless($obj->allowCommand($modCfg, $cmdName, $console->{USER},$DontdumpViolation)) {
+ $err = gettext('You have no permissions on this command!');
+ $shorterr = 'noperm';
+ }
+ }
+ }
+ }
+ }
+ unless($ok) {
+ $err = sprintf(gettext("I don't understand the command '%s' \n"), $ucmd);
+ $shorterr = 'noexists';
+ }
+
+ if($shorterr) {
+ return (undef, 'nothing', $shorterr, $err)
+ } else {
+ return ($cmdobj, $cmdname, undef, undef)
+ }
+}
+
+# ------------------
+# Name: checkCmdSyntax
+# Descr: Check the Syntax of Commands and for double Names in different Modules
+# Usage: my $ok = $obj->checkCmdSyntax(tlist);
+# Test:
+sub t_checkCmdSyntax {
+ return $_[0]->checkCmdSyntax('tlist');
+}
+# ------------------
+sub checkCmdSyntax {
+ my $obj = shift || return error ('No Object!' );
+ my $mods = main::getModules();
+
+ my $shorts = {};
+ foreach my $modName (keys %{$mods}) {
+ my $modCfg = $mods->{$modName}->{MOD};
+ foreach my $cmdName (sort keys %{$modCfg->{Commands}}) {
+ my $short = $modCfg->{Commands}->{$cmdName}->{short} || $cmdName;
+ if(exists $shorts->{$short} ) {
+ return error("[ERROR] In %s::%s double short name %s, also in %s!",
+ $modName, $cmdName, $short, $shorts->{$short});
+ } else {
+ $shorts->{$short} = $modName.'::'.$cmdName;
+ }
+ }
+ }
+ $obj->{Check} = 1;
+ 1;
+}
+
+# ------------------
+# Name: getLevel
+# Descr: Translate the Levelname to an numeric level
+# Usage: my $score = $obj->getLevel(levelname);
+# Test:
+sub t_getLevel {
+ return $_[0]->getLevel('user') == 5;
+}
+# ------------------
+sub getLevel {
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return;
+
+ # Level Table
+ $obj->{LEV} = {
+ admin => 10,
+ user => 5,
+ guest => 1,
+ } unless(exists $obj->{LEV});
+
+ if($obj->{LEV}->{$name}) {
+ return $obj->{LEV}->{$name};
+ } else {
+ return error("This Levelname '$name' does not exist");
+ }
+
+}
+
+# ------------------
+sub _insert {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return;
+
+ if(ref $data eq 'HASH') {
+ my ($names, $vals, $kenn);
+ map {
+
+ if($_ eq 'Password') {
+ if($data->{Password}) {
+ push(@$names, $_);
+ push(@$vals, $data->{$_});
+ push(@$kenn, 'MD5(?)');
+ }
+ } else {
+ push(@$names, $_);
+ push(@$vals, $data->{$_});
+ push(@$kenn, '?');
+ }
+
+ } sort keys %$data;
+
+ my $sql;
+ if($data->{Id}) {
+ my $temp = [];
+ my $c = 0;
+ foreach (@$names) {
+ push(@$temp, sprintf("%s=%s", $names->[$c], $kenn->[$c]));
+ $c++;
+ }
+ $sql = sprintf("UPDATE USER SET %s WHERE Id = %lu",
+ join(', ', @$temp),
+ $data->{Id},
+ );
+ } else {
+ $sql = sprintf("REPLACE INTO USER (%s) VALUES (%s)",
+ join(', ', @$names),
+ join(', ', @$kenn),
+ );
+ }
+ my $sth = $obj->{dbh}->prepare( $sql );
+ $sth->execute( @$vals );
+ } else {
+ my $sth = $obj->{dbh}->prepare('REPLACE INTO USER VALUES (?,?,?,?)');
+ $sth->execute( @$data );
+ }
+}
+
+# ------------------
+# Name: userTmp
+# Descr: Return a temp directory only for logged user and delete this by exit xxv.
+# Usage: my $tmpdir = $obj->userTmp([username]);
+# ------------------
+sub userTmp {
+ my $obj = shift || return error ('No Object!' );
+ my $user = ($obj->{active} eq 'y' ? ( shift || ($obj->{USER}->{Name}?$obj->{USER}->{Name}:"nobody") ) : "nobody" );
+
+ # /var/cache/xxv/temp/xpix/$PID
+ my $dir = sprintf('%s/%s/%d', $obj->{tempimages} , $user, $$);
+
+ unless(-d $dir) {
+ mkpath($dir) or error "Can't mkpath $dir : $!";
+ }
+
+ # Nach Logout oder beenden von xxv das temp löschen
+ main::toCleanUp($user, sub{ deleteDir($dir) }, 'logout')
+ unless(main::toCleanUp($user, undef, 'exists')); # ein CB registrieren
+
+ return $dir;
+}
+
+1;
diff --git a/lib/XXV/MODULES/VTX.pm b/lib/XXV/MODULES/VTX.pm
new file mode 100644
index 0000000..6062f65
--- /dev/null
+++ b/lib/XXV/MODULES/VTX.pm
@@ -0,0 +1,1396 @@
+package XXV::MODULES::VTX;
+
+use strict;
+
+use File::Find;
+use FileHandle;
+use Locale::gettext;
+
+################################################################################
+# This module method must exist for XXV
+sub module {
+ my $self = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'VTX',
+ Prereq => {
+ # 'Perl::Module' => 'Description',
+ },
+ Description => gettext('This module display cached teletext pages from osdteletext-plugin.'),
+ Version => '0.93',
+ Date => '2007-03-04',
+ Author => 'Andreas Brachold <vdr04 at deltab.de>',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ },
+ dir => {
+ description => gettext('Directory where the teletext files are be located'),
+ default => '/vtx',
+ type => 'dir',
+ required => gettext('This is required!'),
+ },
+ cache => {
+ description => gettext("Used cache system.\nChoose 'legacy' for the traditional one-file-per-page system.\nDefault is 'packed' for the one-file-for-a-few-pages system.\nVDR-osdteletext-Plugin\n'legacy' <= osdteletext-0.3.2 or 'packed' >= osdteletext-0.4.0"),
+ default => 'packed',
+ type => 'radio',
+ required => gettext('This is required!'),
+ choices => ['legacy','packed']
+ },
+ },
+ Commands => {
+ vtxpage => {
+ description => gettext("Display the teletext page 'pagenumber'"),
+ short => 'vt',
+ callback => sub{ $self->page(@_) },
+ },
+ vtxchannel => {
+ description => gettext("Channel for teletext actions 'cid'"),
+ short => 'vc',
+ callback => sub{ $self->channel(@_) },
+ },
+ vtxsearch => {
+ description => gettext("Search for text inside teletext pages 'text'"),
+ short => 'vs',
+ callback => sub{ $self->search(@_) },
+ },
+ },
+ };
+ return $args;
+}
+
+################################################################################
+# Ctor
+sub new {
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ return $self;
+}
+
+
+################################################################################
+# Find first usable channel
+sub findfirst {
+
+ my $self = shift || return error ('No Object!');
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+
+ my $basedir = $self->{dir}
+ || return $self->pagedump($console,gettext("directory is for modul vtx not registered!"),"");
+
+ my $mod = main::getModule ('CHANNELS');
+ my $channels =[];
+
+ my $cache = $self->{cache} ||'packed';
+ if ($cache ne 'packed') {
+ foreach my $ch (@{$mod->ChannelArray ('Name')}) {
+ if (-d $basedir.'/'.$ch->[1]) {
+ return $self->channel ($watcher, $console,$ch->[1]);
+ }
+ }
+ } else {
+ foreach my $ch (@{$mod->ChannelArray ('Id')}) {
+ if (-d $basedir.'/'.$ch->[0]) {
+ return $self->channel ($watcher,$console,$ch->[1]);
+ }
+ }
+ }
+}
+
+################################################################################
+# Callback "Channel choice"
+sub channel
+{
+ my $self = shift || return error ('No Object!');
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $channel = shift || return $self->findfirst ($watcher, $console);
+
+ my $basedir = $self->{dir} || return error ('No Base Directory defined !');
+ my $cache = $self->{cache} || 'packed';
+
+ my $mod = main::getModule ('CHANNELS');
+
+ my $chandir = "";
+ my $channelname = "";
+
+ # Get ChannelID and channel's Name
+ foreach my $ch (@{$mod->ChannelArray ('Name, Id')}) {
+ if ($ch->[2] == $channel) {
+ $channelname = $ch->[0];
+ if ($cache eq 'packed') {
+ $chandir = $ch->[1];
+ } else {
+ $chandir = $channel;
+ }
+ last;
+ }
+ }
+
+
+ if ($channelname ne ""
+ and $chandir ne ""
+ and -d "$basedir/$chandir") {
+
+ $self->{CHANNEL}= $channel;
+ $self->{CHANNELDIR}= $chandir;
+ {
+ $self->{INDEX} = [];
+ my @index;
+ if ($cache eq 'packed') {
+ find(
+ sub{
+ if($File::Find::name =~ /\d{3}s.vtx$/sig) {
+ push(@index,GetPackedToc($File::Find::name));
+ }
+ },"$basedir/$chandir");
+ } else {
+ find(
+ sub{
+ if($File::Find::name =~ /\d{3}_\d{2}.vtx$/sig) {
+ my ($page, $subpage)
+ = $File::Find::name =~ /^.*(\d{3})_(\d{2}).*/si;
+ if($page and $subpage) {
+ my $found = 0;
+ foreach my $p (@index) {
+ if($p->[0] == $page) {
+ $found = 1;
+ push(@{$p->[1]},$subpage)
+ if($subpage != 0);
+ last;
+ }
+ }
+ if ($found == 0) {
+ push(@index,[$page, [$subpage] ]);
+ }
+ }
+ }
+ },"$basedir/$chandir");
+ }
+ if (scalar @index == 0) {
+ $self->pagedump($console,sprintf(gettext("No data found for \'%s\'!"),$channelname),"");
+ return;
+ }
+ # Seitenindex sortieren
+ @{$self->{INDEX}} = sort { $a->[0] <=> $b->[0] } @index;
+ # Subseitenindex sortieren
+ foreach my $p (@{$self->{INDEX}}) {
+ if (scalar @{$p->[1]} > 1) {
+ my @tmp = sort { $a <=> $b } @{$p->[1]};
+ @{$p->[1]} = @tmp;
+ }
+ }
+ }
+
+# Dump PageIndex
+# foreach my $p (@{$self->{INDEX}}) {
+# my $dump = "Pages $p->[0]";
+# foreach my $s (@{$p->[1]}) {
+# $dump .= ", $s";
+# }
+# warn($dump);
+# }
+
+ $console->message(sprintf(gettext("channel \'%s\' for modul vtx registered."),$channelname))
+ if ($console->{TYP} ne 'HTML') ;
+ } else {
+ $self->pagedump($console,sprintf(gettext("No data found for \'%s\'!"),$channelname),"");
+ return;
+ }
+ my $fpage = @{$self->{INDEX}}[0];# First Page on Index
+ return $self->page ($watcher, $console,sprintf ("%03d_%02d", $fpage->[0],$fpage->[1]->[0]));
+}
+
+################################################################################
+# Callback "Teletextpage choice"
+sub page {
+ my $self = shift || return error ('No Object!');
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $page = shift || "";
+ my $channel = $self->{CHANNEL} || return $self->findfirst ($watcher, $console);
+ my $basedir = $self->{dir} || return error ('No Base Directory defined !');
+ my $chandir = $self->{CHANNELDIR} || return error ('No CHANNEL');
+ my $cache = $self->{cache} || 'packed';
+
+ my @pp = split ('_', $page);
+ if (scalar @pp == 0) {
+ # First Page on Index
+ my $fpage = @{$self->{INDEX}}[0];
+ $pp[0] = sprintf("%3d",$fpage->[0]);
+ $pp[1] = sprintf("%2d",$fpage->[1]->[0]);
+ }
+ elsif (scalar @pp == 1) {
+ # First Subpage on Index
+ $pp[1] = "00";
+ foreach my $fpage (@{$self->{INDEX}}) {
+ if($fpage->[0] == $pp[0]) {
+ $pp[1] = sprintf("%2d",$fpage->[1]->[0]);
+ last;
+ }
+ }
+ }
+ my $bHTML = ($console->{TYP} ne 'HTML')?0:1;
+ my $result = $self->realpage($console, $pp[0], $pp[1],$bHTML);
+
+ return 0 if($result eq "");
+ return $self->pagedump($console,$result,$chandir);
+}
+
+################################################################################
+# Generate Message
+sub pagedump {
+ my $self = shift || return error ('No Object!');
+ my $console = shift || return error ('No Console');
+ my $result = shift;
+ my $chandir = shift;
+
+ if ($console->{TYP} ne 'HTML') {
+ return $console->message ($result);
+ } else {
+
+ my $charray =[];
+ my $chsel = $self->{CHANNELDIR};
+ my $cache = $self->{cache};
+ my $basedir = $self->{dir};
+ my $mod = main::getModule ('CHANNELS');
+
+ my @chan = (@{$mod->ChannelArray ('Name, Id')});
+ if ($cache ne 'packed') {
+ foreach my $ch (@chan) {
+ push (@$charray, [$ch->[0], $ch->[2]])
+ if (-d $basedir.'/'.$ch->[2]) ; # Lookup /vtx/25/
+ }
+ } else {
+ foreach my $ch (@chan){
+ if (-d $basedir.'/'.$ch->[1]) { # Lookup /vtx/S19.2E-1-1101-28108/
+ push (@$charray, [$ch->[0], $ch->[2]]);
+ $chsel = $ch->[2]
+ if ($ch->[1] eq $chandir) ;
+ }
+ }
+ }
+
+ my @lines = $self->InsertPageLink($result);
+ $self->NavigatePages();
+ my $tmpldata =
+ {
+ channel => $chsel,
+ channels => $charray,
+ page => $self->{mainpage},
+ subpage => $self->{subpage},
+ toppage => $self->{toppage},
+ page_prev => $self->{page_prev},
+ page_next => $self->{page_next},
+ subpage_prev => $self->{subpage_prev},
+ subpage_next => $self->{subpage_next}
+ };
+
+ $console->{dontparsedData} = 1;
+ return $console->vtx(\@lines, $tmpldata);
+ }
+ return 1;
+}
+
+################################################################################
+# Insert for HTML Pages, Link for other Pages
+sub InsertPageLink {
+
+ my $self = shift;
+ my $result = shift;
+ my @lines;
+
+ # Replace XXX => <a href="?cmd=vt&amp;data=XXX">XXX</a>
+ my $ua = "<a class='vtx' href='?cmd=vt&amp;data=";
+ my $ub = "'>";
+ my $uc = "</a>";
+
+ foreach my $line (split('\n',$result)) {
+ my ($page1,$page2) = $line =~ /\D+([1-8]\d{2})\D+([1-8]\d{2})\D+/s;
+ if($page1 and $page2) {
+ foreach my $p (@{$self->{INDEX}}) {
+ if($p->[0] == $page1) {
+ $line =~ s/$page1/$ua.$page1.$ub.$page1.$uc/eg;
+ } elsif($p->[0] == $page2) {
+ $line =~ s/$page2/$ua.$page2.$ub.$page2.$uc/eg;
+ last;
+ }
+ }
+ } else {
+ my ($page1) = $line =~ /\D+([1-8]\d{2})\D+/s;
+ if($page1) {
+ foreach my $p (@{$self->{INDEX}}) {
+ if($p->[0] == $page1) {
+ $line =~ s/$page1/$ua.$page1.$ub.$page1.$uc/eg;
+ last;
+ }
+ }
+ }
+ }
+
+ # Make anchor for external URLs
+ $line =~ s/((www)\.[a-z0-9\.\/\-]+)/<a target=\"blank\" class=\"vtx\" href=\"http:\/\/$1\">$1<\/a>/gi;
+ push (@lines, $line);
+ }
+ return @lines;
+}
+
+################################################################################
+# Find next and prior Pages, used one HTML View
+sub NavigatePages {
+ my $self = shift;
+
+ my $mFound = 0;
+ my $sFound = 0;
+ $self->{toppage} = 0;
+ $self->{page_prev} = 0;
+ $self->{page_next} = 0;
+ $self->{subpage_prev} = 0;
+ $self->{subpage_next} = 0;
+
+ $self->{toppage} = $self->{INDEX}->[0][0] if ($self->{INDEX} && scalar ($self->{INDEX}));
+
+# Outer Mainpages-Loop##########################################################
+ foreach my $p (@{$self->{INDEX}}) {
+ if($mFound == 1) {
+ $self->{page_next} = $p->[0];
+ last;
+ }
+ if($p->[0] && $p->[0] == $self->{mainpage}) {
+ $mFound = 1;
+ if ($p->[1] && scalar @{$p->[1]} > 1) {
+# Inner Subpages-Loop###########################################################
+ foreach my $s (@{$p->[1]}) {
+ if($sFound == 1) {
+ $self->{subpage_next} = sprintf ("%03d_%02d", $self->{mainpage},$s);
+ last;
+ }
+ if($s == $self->{subpage}) {
+ $sFound = 1;
+ }
+ if($sFound == 0) {
+ $self->{subpage_prev} = sprintf ("%03d_%02d", $self->{mainpage},$s);
+ }
+ }
+ if($sFound == 0) {
+ $self->{subpage_prev} = 0;
+ }
+# Inner Subpages-Loop###########################################################
+ }
+ }
+ if($mFound == 0) {
+ $self->{page_prev} = $p->[0];
+ }
+ }
+ if($mFound == 0) {
+ $self->{page_prev} = 0;
+ }
+# Outer Mainpages-Loop##########################################################
+}
+
+################################################################################
+# Our internal real page deliverer
+sub realpage {
+ my $self = shift || return error ('No Object!');
+ my $console = shift || return error ('No Console!');
+ my $mainpage= shift || return error ('No Page!');
+ my $subpage = shift || return error ('No Subpage!');
+ my $bHTML = shift;
+
+ my $basedir = $self->{dir} || return error ('No directory is defined!');
+ my $chandir = $self->{CHANNELDIR} || return error ('No CHANNEL');
+ my $cache = $self->{cache} || 'packed';
+################################################################################
+# get filename
+ my $filename;
+ if ($cache eq 'packed') {
+ # Build name /vtx/S19.2E-1-1101-28108/100s.vtx
+ my $group = (int ($mainpage / 10)) *10;
+ $filename = sprintf ("%s/%s/%03ds.vtx", $basedir, $chandir, $group);
+ } else {
+ # Build name /vtx/15/100_01.vtx
+ $filename = sprintf ("%s/%s/%03d_%02d.vtx", $basedir, $chandir, $mainpage, $subpage);
+ }
+################################################################################
+# Now open and read this file
+ my $fh = FileHandle->new;
+ if(!$fh->open($filename)) {
+ $self->pagedump($console,gettext("The page could not be found!"),"");
+ return "";
+ }
+
+ my $result = $self->readpage($console, $fh, $mainpage, $subpage, $bHTML);
+ $fh->close();
+ return $result;
+}
+
+################################################################################
+# Translation table for ASCII
+# Source - Bytelayout - vdr-plugin osdteletext-0.4.1/txtfont.c
+# Codingrule iso-8859-15
+my @tableascii = (
+ ' ', # 0x20
+ '!', # 0x21
+ '"', # 0x22
+ '#', # 0x23
+ '$', # 0x24
+ '%', # 0x25
+ '&', # 0x26
+ '\'', # 0x27
+ '(', # 0x28
+ ')', # 0x29
+ '*', # 0x2A
+ '+', # 0x2B
+ ',', # 0x2C
+ '-', # 0x2D
+ '.', # 0x2E
+ '/', # 0x2E
+ '0', # 0x30
+ '1', # 0x31
+ '2', # 0x32
+ '3', # 0x33
+ '4', # 0x34
+ '5', # 0x35
+ '6', # 0x36
+ '7', # 0x37
+ '8', # 0x38
+ '9', # 0x39
+ ':', # 0x3A
+ ';', # 0x3B
+ '<', # 0x3C
+ '=', # 0x3D
+ '>', # 0x3E
+ '?', # 0x3F
+ '§', # 0x40
+ 'A', # 0x41
+ 'B', # 0x42
+ 'C', # 0x43
+ 'D', # 0x44
+ 'E', # 0x45
+ 'F', # 0x46
+ 'G', # 0x47
+ 'H', # 0x48
+ 'I', # 0x49
+ 'J', # 0x4A
+ 'K', # 0x4B
+ 'L', # 0x4C
+ 'M', # 0x4D
+ 'N', # 0x4E
+ 'O', # 0x4F
+ 'P', # 0x50
+ 'Q', # 0x51
+ 'R', # 0x52
+ 'S', # 0x53
+ 'T', # 0x54
+ 'U', # 0x55
+ 'V', # 0x56
+ 'W', # 0x57
+ 'X', # 0x58
+ 'Y', # 0x59
+ 'Z', # 0x5A
+ 'Ä', # 0x5B
+ 'Ö', # 0x5C
+ 'Ü', # 0x5D
+ '^', # 0x5E
+ '_', # 0x5F
+ '°', # 0x60
+ 'a', # 0x61
+ 'b', # 0x62
+ 'c', # 0x63
+ 'd', # 0x64
+ 'e', # 0x65
+ 'f', # 0x66
+ 'g', # 0x67
+ 'h', # 0x68
+ 'i', # 0x69
+ 'j', # 0x6A
+ 'k', # 0x6B
+ 'l', # 0x6C
+ 'm', # 0x6D
+ 'n', # 0x6E
+ 'o', # 0x6F
+ 'p', # 0x70
+ 'q', # 0x71
+ 'r', # 0x72
+ 's', # 0x73
+ 't', # 0x74
+ 'u', # 0x75
+ 'v', # 0x76
+ 'w', # 0x77
+ 'x', # 0x78
+ 'y', # 0x79
+ 'z', # 0x7A
+ 'ä', # 0x7B
+ 'ö', # 0x7C
+ 'ü', # 0x7D
+ 'ß', #/0x7E
+ ' ', # Block 0x7F
+ '@', # 0x80
+ ' ', # 0x81
+ ' ', # 0x82
+ '£', # 0x83
+ '$', # 0x84
+ ' ', # 0x85
+ ' ', # 0x86
+ ' ', # 0x87
+ ' ', # 0x88
+ ' ', # 0x89
+ ' ', # 0x8A
+ ' ', # 0x8B
+ ' ', # 0x8C
+ ' ', # 0x8D
+ ' ', # 0x8E
+ '#', # 0x8F
+ 'É', # 0x90
+ 'é', # 0x91
+ 'ä', # 0x92
+ '#', # 0x93
+ ' ', # 0x94
+ ' ', # 0x95
+ ' ', # 0x96
+ ' ', # 0x97
+ 'ö', # 0x98
+ 'å', # 0x99
+ 'ü', # 0x9A
+ 'Ä', # 0x9B
+ 'Ö', # 0x9C
+ 'Å', # 0x9D
+ 'Ü', # 0x9E
+ '_', # 0x9F
+ ' ', # 0x20a 0xA0
+ ' ', # 0x21a 0xA1
+ ' ', # 0x22a 0xA2
+ ' ', # 0x23a 0xA3
+ ' ', # 0x24a 0xA4
+ ' ', # 0x25a 0xA5
+ ' ', # 0x26a 0xA6
+ ' ', # 0x27a 0xA7
+ ' ', # 0x28a 0xA8
+ ' ', # 0x29a 0xA9
+ ' ', # 0x2Aa 0xAA
+ ' ', # 0x2Ba 0xAB
+ ' ', # 0x2Ca 0xAC
+ ' ', # 0x2Da 0xAD
+ ' ', # 0x2Ea 0xAE
+ ' ', # 0x2Fa 0xAF
+ ' ', # 0x30a 0xB0
+ ' ', # 0x31a 0xB1
+ ' ', # 0x32a 0xB2
+ ' ', # 0x33a 0xB3
+ ' ', # 0x34a 0xB4
+ ' ', # 0x35a 0xB5
+ ' ', # 0x36a 0xB6
+ ' ', # 0x37a 0xB7
+ ' ', # 0x38a 0xB8
+ ' ', # 0x39a 0xB9
+ ' ', # 0x3Aa 0xBA
+ ' ', # 0x3Ba 0xBB
+ ' ', # 0x3Ca 0xBC
+ ' ', # 0x3Da 0xBD
+ ' ', # 0x3Ea 0xBE
+ ' ', # 0x3Fa 0xBF
+ 'é', # 0xC0
+ 'ù', # 0xC1
+ 'à', # 0xC2
+ '£', # 0xC3
+ '$', # 0xC4
+ 'ã', # 0xC5
+ 'õ', # 0xC6
+ ' ', # 0xC7
+ 'ò', # 0xC8
+ 'è', # 0xC9
+ 'ì', # 0xCA
+ '°', # 0xCB
+ 'ç', # 0xCC
+ ' ', # 0xCD
+ ' ', # 0xCE
+ '#', # 0xCF
+ 'à', # 0xD0
+ 'è', # 0xD1
+ 'â', # 0xD2
+ 'é', # 0xD3
+ 'ï', # 0xD4
+ 'Ã', # 0xD5
+ 'Õ', # 0xD6
+ 'Ç', # 0xD7
+ 'ô', # 0xD8
+ 'û', # 0xD9
+ 'ç', # 0xDA
+ 'ë', # 0xDB
+ 'ê', # 0xDC
+ 'ù', # 0xDD
+ 'î', # 0xDE
+ '#', # 0xDF
+ '¡', # 0xE0
+ '¿', # 0xE1
+ 'ü', # 0xE2
+ 'ç', # 0xE3
+ '$', # 0xE4
+ ' ', # 0xE5
+ ' ', # 0xE6
+ ' ', # 0xE7
+ 'ñ', # 0xE8
+ 'è', # 0xE9
+ 'à', # 0xEA
+ 'á', # 0xEB
+ 'é', # 0xEC
+ 'í', # 0xED
+ 'ó', # 0xEE
+ 'ú', # 0xEF
+ 'Á', # 0xF0
+ 'À', # 0xF1
+ 'È', # 0xF2
+ 'Í', # 0xF3
+ 'Ï', # 0xF4
+ 'Ó', # 0xF5
+ 'Ò', # 0xF6
+ 'Ú', # 0xF7
+ 'æ', # 0xF8
+ 'Æ', # 0xF9
+ 'ð', # 0xFA
+ ' ', # 0xFB
+ 'ø', # 0xFC
+ 'Ø', # 0xFD
+ ' ', # 0xFE
+ ' ', # 0xFF
+ ' ', # 0x60a
+ ' ', # 0x61a
+ ' ', # 0x62a
+ ' ', # 0x63a
+ ' ', # 0x64a
+ ' ', # 0x65a
+ ' ', # 0x66a
+ ' ', # 0x67a
+ ' ', # 0x68a
+ ' ', # 0x69a
+ ' ', # 0x6Aa
+ ' ', # 0x6Ba
+ ' ', # 0x6Ca
+ ' ', # 0x6Da
+ ' ', # 0x6Ea
+ ' ', # 0x6Fa
+ ' ', # 0x70a
+ ' ', # 0x71a
+ ' ', # 0x72a
+ ' ', # 0x73a
+ ' ', # 0x74a
+ ' ', # 0x75a
+ ' ', # 0x76a
+ ' ', # 0x77a
+ ' ', # 0x78a
+ ' ', # 0x79a
+ ' ', # 0x7Aa
+ ' ', # 0x7Ba
+ ' ', # 0x7Ca
+ ' ', # 0x7Da
+ ' ', # 0x7Ea
+ ' ' # 0x7Fa
+);
+
+################################################################################
+# Translation table for HTML
+my @tablehtml = (
+ ' ', # 0x20
+ '!', # 0x21
+ '"', # 0x22
+ '#', # 0x23
+ '$', # 0x24
+ '%', # 0x25
+ '&amp;', # 0x26
+ '\'', # 0x27
+ '(', # 0x28
+ ')', # 0x29
+ '*', # 0x2A
+ '+', # 0x2B
+ ',', # 0x2C
+ '-', # 0x2D
+ '.', # 0x2E
+ '/', # 0x2E
+ '0', # 0x30
+ '1', # 0x31
+ '2', # 0x32
+ '3', # 0x33
+ '4', # 0x34
+ '5', # 0x35
+ '6', # 0x36
+ '7', # 0x37
+ '8', # 0x38
+ '9', # 0x39
+ ':', # 0x3A
+ ';', # 0x3B
+ '&lt;', # 0x3C
+ '=', # 0x3D
+ '&gt;', # 0x3E
+ '?', # 0x3F
+ '&sect;', # 0x40
+ 'A', # 0x41
+ 'B', # 0x42
+ 'C', # 0x43
+ 'D', # 0x44
+ 'E', # 0x45
+ 'F', # 0x46
+ 'G', # 0x47
+ 'H', # 0x48
+ 'I', # 0x49
+ 'J', # 0x4A
+ 'K', # 0x4B
+ 'L', # 0x4C
+ 'M', # 0x4D
+ 'N', # 0x4E
+ 'O', # 0x4F
+ 'P', # 0x50
+ 'Q', # 0x51
+ 'R', # 0x52
+ 'S', # 0x53
+ 'T', # 0x54
+ 'U', # 0x55
+ 'V', # 0x56
+ 'W', # 0x57
+ 'X', # 0x58
+ 'Y', # 0x59
+ 'Z', # 0x5A
+ '&Auml;', # 0x5B
+ '&Ouml;', # 0x5C
+ '&Uuml;', # 0x5D
+ '^', # 0x5E
+ '_', # 0x5F
+ '&deg;', # 0x60
+ 'a', # 0x61
+ 'b', # 0x62
+ 'c', # 0x63
+ 'd', # 0x64
+ 'e', # 0x65
+ 'f', # 0x66
+ 'g', # 0x67
+ 'h', # 0x68
+ 'i', # 0x69
+ 'j', # 0x6A
+ 'k', # 0x6B
+ 'l', # 0x6C
+ 'm', # 0x6D
+ 'n', # 0x6E
+ 'o', # 0x6F
+ 'p', # 0x70
+ 'q', # 0x71
+ 'r', # 0x72
+ 's', # 0x73
+ 't', # 0x74
+ 'u', # 0x75
+ 'v', # 0x76
+ 'w', # 0x77
+ 'x', # 0x78
+ 'y', # 0x79
+ 'z', # 0x7A
+ '&auml;', # 0x7B
+ '&ouml;', # 0x7C
+ '&uuml;', # 0x7D
+ '&szlig;', # 0x7E
+ 'image-7F', # Block 0x7F
+ '@', # 0x80
+ '&ndash;', # 0x81
+ '&frac14;', # 0x82 1/4
+ '&pound;', # 0x83
+ '$', # 0x84
+ ' ', # 0x85 Taste Teletext (a)
+ ' ', # 0x86 Taste Small
+ ' ', # 0x87 Taste Hide
+ ' ', # 0x88 ||
+ '&frac34;', # 0x89 3/4
+ '&divide;', # 0x8A
+ '&larr;', # 0x8B <-
+ '&frac12;', # 0x8C 1/2
+ '&rarr;', # 0x8D ->
+ '&uarr;', # 0x8E
+ '#', # 0x8F
+ '&Eacute;', # 0x90
+ '&eacute;', # 0x91
+ '&auml;', # 0x92
+ '#', # 0x93
+ '&curren;', # 0x94
+ ' ', # 0x95 Taste Teletext (b)
+ ' ', # 0x96 Taste
+ ' ', # 0x97 Taste Big
+ '&ouml;', # 0x98
+ '&aring;', # 0x99
+ '&uuml;', # 0x9A
+ '&Auml;', # 0x9B
+ '&Ouml;', # 0x9C
+ '&Aring;', # 0x9D
+ '&Uuml;', # 0x9E
+ '_', # 0x9F
+ 'image-20', # 0x20a 0xA0 # image-20 == whitespace
+ 'image-21', # 0x21a 0xA1
+ 'image-22', # 0x22a 0xA2
+ 'image-23', # 0x23a 0xA3
+ 'image-24', # 0x24a 0xA4
+ 'image-25', # 0x25a 0xA5
+ 'image-26', # 0x26a 0xA6
+ 'image-27', # 0x27a 0xA7
+ 'image-28', # 0x28a 0xA8
+ 'image-29', # 0x29a 0xA9
+ 'image-2A', # 0x2Aa 0xAA
+ 'image-2B', # 0x2Ba 0xAB
+ 'image-2C', # 0x2Ca 0xAC
+ 'image-2D', # 0x2Da 0xAD
+ 'image-2E', # 0x2Ea 0xAE
+ 'image-2F', # 0x2Fa 0xAF
+ 'image-30', # 0x30a 0xB0
+ 'image-31', # 0x31a 0xB1
+ 'image-32', # 0x32a 0xB2
+ 'image-33', # 0x33a 0xB3
+ 'image-34', # 0x34a 0xB4
+ 'image-35', # 0x35a 0xB5
+ 'image-36', # 0x36a 0xB6
+ 'image-37', # 0x37a 0xB7
+ 'image-38', # 0x38a 0xB8
+ 'image-39', # 0x39a 0xB9
+ 'image-3A', # 0x3Aa 0xBA
+ 'image-3B', # 0x3Ba 0xBB
+ 'image-3C', # 0x3Ca 0xBC
+ 'image-3D', # 0x3Da 0xBD
+ 'image-3E', # 0x3Ea 0xBE
+ 'image-3F', # 0x3Fa 0xBF
+ '&eacute;', # 0xC0
+ '&ugrave;', # 0xC1
+ '&agrave;', # 0xC2
+ '&pound;', # 0xC3
+ '$', # 0xC4
+ '&atilde;', # 0xC5
+ '&otilde;', # 0xC6
+ '&bull;', # 0xC7
+ '&ograve;', # 0xC8
+ '&egrave;', # 0xC9
+ '&igrave;', # 0xCA
+ '&deg;', # 0xCB
+ '&ccedil;', # 0xCC
+ '&rarr;', # 0xCD
+ '&uarr;', # 0xCE
+ '#', # 0xCF
+ '&agrave;', # 0xD0
+ '&egrave;', # 0xD1
+ '&acirc;', # 0xD2
+ '&eacute;', # 0xD3
+ '&iuml;', # 0xD4
+ '&Atilde;', # 0xD5
+ '&Otilde;', # 0xD6
+ '&Ccedil;', # 0xD7
+ '&ocirc;', # 0xD8
+ '&ucirc;', # 0xD9
+ '&ccedil;', # 0xDA
+ '&euml;', # 0xDB
+ '&ecirc;', # 0xDC
+ '&ugrave;', # 0xDD
+ '&icirc;', # 0xDE
+ '#', # 0xDF
+ '&iexcl;', # 0xE0
+ '&iquest;', # 0xE1
+ '&uuml;', # 0xE2
+ '&ccedil;', # 0xE3
+ '$', # 0xE4
+ ' ', # 0xE5 a mit unterstrich
+ ' ', # 0xE6 o mit unterstrich
+ '&Ntilde;', # 0xE7
+ '&ntilde;', # 0xE8
+ '&egrave;', # 0xE9
+ '&agrave;', # 0xEA
+ '&aacute;', # 0xEB
+ '&eacute;', # 0xEC
+ '&iacute;', # 0xED
+ '&oacute;', # 0xEE
+ '&uacute;', # 0xEF
+ '&Aacute;', # 0xF0
+ '&Agrave;', # 0xF1
+ '&Egrave;', # 0xF2
+ '&Iacute;', # 0xF3
+ '&Iuml;', # 0xF4
+ '&Oacute;', # 0xF5
+ '&Ograve;', # 0xF6
+ '&Uacute;', # 0xF7
+ '&aelig;', # 0xF8
+ '&AElig;', # 0xF9
+ '&eth;', # 0xFA
+ '&ETH;', # 0xFB
+ '&oslash;', # 0xFC
+ '&Oslash;', # 0xFD
+ '&thorn;', # 0xFE
+ '&THORN;', # 0xFF
+ 'image-60', # 0x60a
+ 'image-61', # 0x61a
+ 'image-62', # 0x62a
+ 'image-63', # 0x63a
+ 'image-64', # 0x64a
+ 'image-65', # 0x65a
+ 'image-66', # 0x66a
+ 'image-67', # 0x67a
+ 'image-68', # 0x68a
+ 'image-69', # 0x69a
+ 'image-6A', # 0x6Aa
+ 'image-6B', # 0x6Ba
+ 'image-6C', # 0x6Ca
+ 'image-6D', # 0x6Da
+ 'image-6E', # 0x6Ea
+ 'image-6F', # 0x6Fa
+ 'image-70', # 0x70a
+ 'image-71', # 0x71a
+ 'image-72', # 0x72a
+ 'image-73', # 0x73a
+ 'image-74', # 0x74a
+ 'image-75', # 0x75a
+ 'image-76', # 0x76a
+ 'image-77', # 0x77a
+ 'image-78', # 0x78a
+ 'image-79', # 0x79a
+ 'image-7A', # 0x7Aa
+ 'image-7B', # 0x7Ba
+ 'image-7C', # 0x7Ca
+ 'image-7D', # 0x7Da
+ 'image-7E', # 0x7Ea
+ 'image-7F' # 0x7Fa
+);
+
+################################################################################
+# Color table
+my @colors = (
+ "black", "red", "green", "yellow",
+ "blue", "magenta", "cyan", "white"
+);
+
+################################################################################
+# Translation unpacked bytes to text
+sub translate {
+
+ my $self=shift;
+ my $bHTML=shift;
+ my $c=shift;
+ my $graph=shift;
+ my $double=shift;
+ my $sepgraph=shift;
+ my $fg=shift;
+ my $bg=shift;
+ $c = int($c);
+ if ($graph == 1) {
+ if (($c>=0x20) and ($c<=0x3F)) { $c += 0x80; }
+ elsif (($c>=0x60) and ($c<=0x7F)) { $c += 0xA0; }
+ }
+ $c -= 0x20;
+ if($bHTML == 1) {
+ my $result;
+
+ if ($fg != $self->{ofg} or $bg != $self->{obg}) {
+ if ($self->{ofg} != -1 or $self->{obg} != -1) {
+ $result .= "</font>";
+ }
+ $result .= sprintf("<font style=\"color:%s;background-color:%s;\">",$colors[$fg],$colors[$bg]);
+
+ $self->{ofg} = $fg;
+ $self->{obg} = $bg;
+ }
+ if($c < 0 or $c > 256) {
+ $result .= '&nbsp;';
+ } else {
+ my $h .= $tablehtml[$c];
+ $h =~ s/ /"&nbsp;"/eg;
+ $result .= $h;
+ if ($graph == 1 || $c == 0x5f) #Block 0x5f = 0x7f - 0x20
+ {
+ my $pre = "<img class=\"vtx\" src=\"vtximages/";
+ my $color = $colors[$fg];
+ my $post = ".gif\" alt=\"\" title=\"\" />&nbsp;";
+ # set <img class="vtx" class="vtx" src="vtximages/black21.gif" alt="" title="">
+ # vtx-image are locate inside skin folder
+ $result =~ s/(image)\-(.+)/$pre.$color.$2.$post/eg;
+ }
+ }
+ return $result;
+ } else {
+ return ' ' if($c < 0 or $c > 256);
+ return $tableascii[$c];
+ }
+}
+################################################################################
+# close text line
+sub endline {
+ my $self=shift;
+ my $bHTML=shift;
+ my $result = "";
+ $result .= "</font><br />" if($bHTML);
+ $result .= "\n";
+ return $result;
+}
+
+################################################################################
+# Read page which open from filehandle
+sub readpage {
+ my $self=shift;
+ my $console=shift;
+ my $fh=shift;
+ my $mainpage=shift;
+ my $subpage=shift;
+ my $bHTML = shift;
+ my $cache = $self->{cache} || 'packed';
+
+# Seek inside packed file
+ if ($cache eq 'packed') {
+ # Parse TOC
+ #
+ # 8x[MAIN,SUB a 2x4byte],
+ # 8x[PAGE a 972byte],
+ # 8x[MAIN,SUB a 2x4byte],
+ # 8x[PAGE a 972byte]
+ #
+ my $tocbuf;
+ my $notfound = 1;
+ while($notfound == 1) {
+ if($fh->read($tocbuf, 4*2*8) ne 64) {
+ $self->pagedump($console,gettext("The page could not readed!"),"");
+ return "";
+ }
+ my @toc = unpack( "i*", $tocbuf);
+ my $n = 0;
+ for (;$n < 8 and $notfound == 1; ++$n ) {
+ my $mpage = int(sprintf ("%X",@toc[$n*2]));
+ my $spage = int(sprintf ("%X",@toc[($n*2)+1]));
+ # Check for last toc entry 0/0
+ if($mpage == 0 and $spage == 0) {
+ $self->pagedump($console,gettext("The page could not be found!"),"");
+ return "";
+ }
+ # Look for toc entry same wanted page
+ if($mpage == $mainpage) {
+ if(($spage == $subpage )
+ or ($subpage <= 1 and $spage <= 1)) {
+
+ $self->{mainpage} = $mpage;
+ $self->{subpage} = $spage;
+
+ $notfound = 0;
+ }
+ }
+ }
+ --$n if($notfound == 0);
+ # Skip unwanted Pages
+ if(0 == $fh->seek((972*$n), 1)) {
+ $self->pagedump($console,gettext("The page could not readed!"),"");
+ return "";
+ }
+ }
+ } else {
+ $self->{mainpage} = $mainpage;
+ $self->{subpage} = $subpage;
+ }
+
+# Read page now
+ my $packed;
+ if($fh->read($packed, 972) ne 972) {
+ $self->pagedump($console,gettext("The page could not readed!"),"");
+ return "";
+ }
+ my $result = "";
+ $result .= "<p class=\"vtx\">\n" if($bHTML);
+
+ my @buf = unpack( "C*", $packed);
+
+ my $n = 9 + 1 + 2; #Index, skip irgendwas davor, Language, irgendwas wieder
+ my $flash=0;
+ my $double=0;
+ my $hidden=0; #hidden = verdeckt!!!
+ my $sepgraph=0;
+ my $hold=0;
+ my $graph=0;
+ my $skipnextline=0;
+ my $lc=0x20;
+
+ my $fg = 7;
+ my $bg = 0;
+ for (my $y=0;$y<24;$y++) {
+
+ $flash=0;
+ $double=0;
+ $hidden=0; #hidden = verdeckt!!!
+ $sepgraph=0;
+ $hold=0;
+ $graph=0;
+ $skipnextline=0;
+ $lc=0x20;
+
+ $fg = 7;
+ $bg = 0;
+ $self->{ofg} = -1;
+ $self->{obg} = -1;
+
+ for (my $x=0;$x<40;++$x,++$n)
+ {
+# $result .= sprintf("<!-- %2x -->",$buf[$n])
+# if($bHTML);
+
+ my $c=int($buf[$n] & 0x7F); #Parity Bit ist uninteressant!
+
+ if (($y==0)&&($x<8)) { # Die Daten sind uninteressant zur Anzeige!
+ $c = 0x20;
+ }
+
+ if( $c >= 0x00 and $c <= 0x07 ) {
+ $lc=0x20
+ if($graph);
+ $hidden= 0;
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+ $graph= 0;
+ $fg = int($c);
+
+ } elsif( $c == 0x08 ) { # Blinken einschalten (flashing)
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+ $flash= 1;
+
+ } elsif( $c == 0x09 ) { # Blinken ausschalten (steady)
+ $flash= 0;
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+ } elsif( $c == 0x0A ) { # end box (nicht benutzt)
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x0B ) { # start box (nicht benutzt)
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x0C ) { # normal high
+ $double= 0;
+ $lc=0x20;
+ $result .= $self->translate($bHTML, 0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x0D ) { # double high
+# for (my $frei=1;$frei<40;$frei++)
+# $result .= $self->translate($bHTML, $frei,$y+1,0x20,$graph,$double,$sepgraph,$fg,$bg);
+# $result .= $self->endline($bHTML);
+
+ $result .= $self->translate($bHTML, 0x20,$graph,$double,$sepgraph,$fg,$bg);
+ $double= 1;
+# $skipnextline= 1;
+
+ } elsif( $c >= 0x0E and $c <= 0x0F ) { # keine Funktion
+
+ } elsif( $c >= 0x10 and $c <= 0x17 ) { #
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+ $hidden= 0;
+ $graph= 1;
+ $fg = $c-0x10;
+
+ } elsif( $c == 0x18 ) { # verborgen
+ $hidden= 1;
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x19 ) { # contigouous graphics
+ $sepgraph= 0;
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x1A ) { # separated grphics
+ $sepgraph= 1;
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x1B ) { # ESC
+
+ } elsif( $c == 0x1C ) { # black background
+ $bg = (0);
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x1D ) { # new background
+ my $tmp = $fg; # ExchangeColor
+ $fg = $bg;
+ $bg = $tmp;
+
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x1E ) { # hold graphics
+ $hold= 1;
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+
+ } elsif( $c == 0x1F ) { # release graphics
+ $result .= $self->translate($bHTML, ($hold == 1)?$lc:0x20,$graph,$double,$sepgraph,$fg,$bg);
+ $hold= 0;
+
+ } else { #
+ if ($graph == 1) {
+ $lc = $c;
+ }
+ $result .= $self->translate($bHTML, $c,$graph,$double,$sepgraph,$fg,$bg);
+ }
+ }
+ $result .= $self->endline($bHTML);
+ if ($skipnextline==1) {
+ $y++;
+ }
+ }
+ $result .= "</p>\n" if($bHTML);
+ return $result;
+}
+
+################################################################################
+# Read TOC from packed file for index
+sub GetPackedToc {
+
+ my $filename = shift;
+ my @index;
+ my $fh = FileHandle->new;
+ if(!$fh->open($filename)) {
+ error ("The page could not be found! : $filename");
+ } else {
+ # Parse TOC
+ #
+ # 8x[MAIN,SUB a 2x4byte],
+ # 8x[PAGE a 972byte],
+ # 8x[MAIN,SUB a 2x4byte],
+ # 8x[PAGE a 972byte]
+ #
+ my $tocbuf;
+ my $bEnd = 0;
+ while(!$fh->eof() and $bEnd == 0) {
+ if($fh->read($tocbuf, 4*2*8) ne 64) {
+ $bEnd = 1;
+ last;
+ }
+ my @toc = unpack( "i*", $tocbuf);
+ my $n = 0;
+ for (;$n < 8; ++$n ) {
+ my $m = (sprintf ("%X",@toc[$n*2]));
+
+ next # Skip nonregular pages like 80F
+ if($m =~ /\D/sig);
+
+ my $mpage = int($m);
+ my $spage = int(sprintf ("%X",@toc[($n*2)+1]));
+
+ # Check for last toc entry 0/0
+ if($mpage == 0 and $spage == 0) {
+ $bEnd = 1;
+ last;
+ }
+ my $found = 0;
+ foreach my $p (@index) {
+ if($p->[0] == $mpage) {
+ $found = 1;
+ push(@{$p->[1]},$spage)
+ if($spage != 0);
+ last;
+ }
+ }
+ if ($found == 0) {
+ push(@index,[$mpage, [$spage] ]);
+ }
+ }
+ # Skip Pages
+ if(0 == $fh->seek((972*8), 1)) {
+ $bEnd = 1;
+ last;
+ }
+ }
+
+ $fh->close();
+ }
+ return @index;
+}
+
+################################################################################
+# HighLight searched text
+sub HighLight {
+
+ my $self = shift;
+ my $result = shift;
+ my $search = shift;
+ my $lines;
+
+ my $ua = "<font style=\"color:black;background-color:lime;\">";
+ my $ub = "</font>";
+
+ foreach my $line (split('\n',$result)) {
+ $line =~ s/$search/$ua$search$ub/g;
+ $lines .= $line;
+ }
+ return $lines;
+}
+
+################################################################################
+# Callback "Teletext search"
+sub search {
+ my $self = shift || return error ('No Object!');
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $search = shift;
+
+ my $channel = $self->{CHANNEL};
+ my $chandir = $self->{CHANNELDIR};
+ if($channel eq "" or $chandir eq "") {
+ $self->pagedump($console,gettext("No channel defined!"),"");
+ }
+
+ chomp($search);
+ unless($search) {
+ $self->pagedump($console,gettext("No data to search given!"),$chandir);
+ }
+
+ my $oldpage = $self->{mainpage};
+ my $oldsubpage = $self->{subpage};
+
+ my @foundlist;
+ my $searchlimit = 25;
+ foreach my $p (@{$self->{INDEX}}) {
+ foreach my $s (@{$p->[1]}) {
+ my $mp = sprintf("%3d",$p->[0]);
+ my $sp = sprintf("%2d",$s);
+
+ my $lookup = $self->realpage($console, $mp, $sp, 0);
+
+ my @found = grep(/$search/,$lookup);
+ if(scalar @found > 0) {
+ push(@foundlist,[$mp, $sp]);
+ $searchlimit--;
+ last if($searchlimit <= 0);
+ }
+ }
+ last if($searchlimit <= 0);
+ }
+
+ if(scalar @foundlist < 1) {
+ $self->{mainpage} = $oldpage;
+ $self->{subpage} = $oldsubpage;
+ $self->pagedump($console,sprintf(gettext("No page with \'%s\' found!"),$search),$chandir);
+ return 0;
+ }
+
+ my $bHTML = ($console->{TYP} ne 'HTML')?0:1;
+ foreach my $pp (@foundlist) {
+
+ $self->{mainpage} = $pp->[0];
+ $self->{subpage} = $pp->[1];
+
+ my $result = $self->realpage($console, $pp->[0], $pp->[1],$bHTML);
+
+ if($bHTML) {
+ $result = $self->HighLight($result,$search);
+ }
+
+ $self->pagedump($console,$result,$chandir)
+ if($result ne "");
+ }
+ return 1;
+}
+
+1;
diff --git a/lib/XXV/MODULES/WAPD.pm b/lib/XXV/MODULES/WAPD.pm
new file mode 100644
index 0000000..8114108
--- /dev/null
+++ b/lib/XXV/MODULES/WAPD.pm
@@ -0,0 +1,354 @@
+package XXV::MODULES::WAPD;
+
+use Locale::gettext;
+use XXV::OUTPUT::Wml;
+use File::Basename;
+use File::Find;
+use Tools;
+
+use strict;
+
+my $mime = {
+ wml => "text/vnd.wap.wml", # WML-Dateien (WAP)
+ wmlc => "application/vnd.wap.wmlc", # WMLC-Dateien (WAP)
+ wmls => "text/vnd.wap.wmlscript", # WML-Scriptdateien (WAP)
+ wmlsc => "application/vnd.wap.wmlscriptc", # WML-Script-C-dateien (WAP)
+ wbm => "image/vnd.wap.wbmp", # Bitmap-Dateien (WAP)
+ wbmp => "image/vnd.wap.wbmp" # Bitmap-Dateien (WAP)
+};
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'WAPD',
+ Prereq => {
+ 'IO::Socket::INET' => 'Object interface for AF_INET domain sockets ',
+ 'MIME::Base64' => 'Encoding and decoding of base64 strings',
+ 'CGI qw/:push -nph -no_xhtml -compile/'
+ => 'Simple Common Gateway Interface Class',
+ },
+ Description => gettext('This module is a multisession WAPD server.'),
+ Version => '0.45',
+ Date => '16.09.2005',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ Clients => {
+ description => gettext('Maximum number from simultaneous connections to the same time'),
+ default => 5,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Port => {
+ description => gettext('Number of port to listen for wap clients'),
+ default => 8085,
+ type => 'integer',
+ required => gettext('This is required!'),
+ },
+ Interface => {
+ description => gettext('Local interface to bind service'),
+ default => '0.0.0.0',
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ WMLRoot => {
+ description => gettext('Used Skin'),
+ default => 'wml',
+ type => 'list',
+ required => gettext('This is required!'),
+ choices => $obj->findskins,
+ },
+# StartPage => {
+# description => gettext('First page, which is to be seen when logon'),
+# default => 'now',
+# type => 'list',
+# required => gettext('This is required!'),
+# choices => [
+# [ gettext('Running now'), 'now'],
+# [ gettext('Program guide'), 'program'],
+# [ gettext('Autotimer'), 'alist'],
+# [ gettext('Timers'), 'tlist'],
+# [ gettext('Recordings'), 'rlist'],
+# [ gettext('Music'), 'mlist'],
+# [ gettext('Remote'), 'remote'],
+# [ gettext('Teletext'), 'vtxpage'],
+# [ gettext('Status'), 'sa'],
+# ],
+# },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
+ $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ # The Initprocess
+ $self->init or return error('Problem to initialize module');
+
+ return $self;
+}
+
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ # globals
+ my $channels;
+
+ # make socket
+ my $socket = IO::Socket::INET->new(
+ Listen => $obj->{Clients},
+ LocalPort => $obj->{Port},
+ LocalAddr => $obj->{Interface},
+ Reuse => 1
+ ) or return error("Can't create Socket: $!");
+
+ # install an initial watcher
+ Event->io(
+ fd => $socket,
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ # accept client
+ my $client=$socket->accept;
+ panic "Can't connect wapd to new client." and return unless $client;
+ $client->autoflush;
+
+ # make "channel" number
+ my $channel=++$channels;
+
+ # install a communicator
+ Event->io(
+ fd => $client,
+ poll => 'r',
+ prio => -1, # -1 very hard ... 6 very low
+ cb => sub {
+ my $watcher = shift;
+
+ # read new line and report it
+ my $handle=$watcher->w->fd;
+
+ my $data = $obj->parseRequest($handle,(defined $obj->{LOGOUT} && $obj->{LOGOUT} == 1 ));
+ unless($data) {
+ undef $obj->{LOGOUT};
+ $watcher->w->cancel;
+ $handle->close();
+ undef $watcher;
+ return 1;
+ }
+
+ undef $obj->{LOGOUT}
+ if(exists $obj->{LOGOUT});
+
+ my $WMLRootDir = sprintf('%s/%s', $obj->{paths}->{HTMLDIR}, $obj->{WMLRoot});
+ my $cgi = CGI->new( $data->{Query} );
+
+ my $console = XXV::OUTPUT::Wml->new(
+ -handle => $handle,
+ -dbh => $obj->{dbh},
+ -wmldir => $WMLRootDir,
+ -cgi => $cgi,
+ -mime => $mime,
+ -browser=> $data,
+ -paths => $obj->{paths},
+# -start => $obj->{StartPage},
+ );
+
+ my $userMod = main::getModule('USER');
+ if(ref $userMod and $userMod->{active} eq 'y') {
+ $console->{USER} = $userMod->check($handle, $data->{username}, $data->{password});
+ $console->login(gettext('You have no permissions to this system!'))
+ unless(exists $console->{USER}->{Level});
+ }
+
+ if(ref $userMod and
+ ($userMod->{active} ne 'y'
+ or exists $console->{USER}->{Level})) {
+
+ $console->{call} = 'nothing';
+ if(($data->{Request} eq '/' or $data->{Request} =~ /\.WML$/) and not $data->{Query}) {
+ # Send the first page (wapd.tmpl)
+ my $page = $data->{Request};
+ if($page eq '/') {
+ if(-r sprintf('%s/wapd.tmpl', $WMLRootDir)) {
+ $console->{call} = 'wapd';
+ my $output = $console->parseTemplate('wapd','wapd');
+ $console->out( $output );
+ } else {
+ $console->datei(sprintf('%s/index.WML', $WMLRootDir));
+ }
+ } else {
+ $console->datei(sprintf('%s/%s', $WMLRootDir, $page));
+ }
+ } elsif(my $typ = $mime->{lc((split('\.', $data->{Request}))[-1])}) {
+ # Send multimedia files (this must registered in $mime!)
+ $console->image(sprintf('%s%s', $WMLRootDir, $data->{Request}), $typ);
+ } elsif( $cgi->param('binary') ) {
+ # Send multimedia files (if param binary)
+ $obj->handleInput($watcher, $console, $cgi);
+ } else {
+ $console->start();
+ $obj->handleInput($watcher, $console, $cgi);
+ $console->footer();
+ }
+ }
+ $watcher->w->cancel;
+ undef $watcher;
+ $handle->close();
+ },
+ );
+
+ },
+ ) if($obj->{active} eq 'y');
+
+ return 1;
+
+}
+
+# ------------------
+sub parseRequest {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $hdl = shift || return error ('No Handle!' );
+ my $logout = shift || 0;
+
+ my ($Req, $size) = getFromSocket($hdl);
+
+ if($Req->[0] =~ /^GET (\/[\w\.\/-\:\%]*)([\?[\w=&\.\+\%-\:\!]*]*)[\#\d ]+HTTP\/1.\d$/) {
+ my $data = {};
+ ($data->{Request}, $data->{Query}) = ($1, $2 ? substr($2, 1, length($2)) : undef);
+
+ # parse header
+ foreach my $line (@$Req) {
+ if($line =~ /Referer: (.*)/) {
+ $data->{Referer} = $1;
+ }
+ if($line =~ /Host: (.*)/) {
+ $data->{HOST} = $1;
+ }
+ if($line =~ /Authorization: basic (.*)/i and not $logout) {
+ ($data->{username}, $data->{password}) = split(":", MIME::Base64::decode_base64($1), 2);
+ }
+ if($line =~ /User-Agent: (.*)/i) {
+ $data->{http_useragent} = $1;
+ }
+ }
+ $data->{Request} =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+
+ return $data;
+ } else {
+ error sprintf("Unknown Request: <%s>\n", join("\n", @$Req));
+ return;
+ }
+
+}
+
+# ------------------
+sub handleInput {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $watcher = shift || return error ('No Watcher!');
+ my $console = shift || return error ('No Console');
+ my $cgi = shift || return error ('No CGI Object');
+
+ my $ucmd = $cgi->param('cmd') || '<undef>';
+ my $udata = $cgi->param('data') || '';
+
+ # Set the referer, if come a form with a error
+ # then patch the referer
+ $console->{browser}->{Referer} = $cgi->param('referer')
+ if($cgi->param('referer'));
+
+ # Test on result set (user has save) and
+ # get the DataVars in a special Hash
+ my $result;
+ foreach my $name ($cgi->param) {
+ if(my ($n) = $name =~ /^__(.+)/sig) {
+ my @vals = $cgi->param($name);
+ if(scalar @vals > 1) {
+ @{$result->{$n}} = @vals;
+ } else {
+ $result->{$n} = shift @vals;
+ }
+ }
+ }
+
+ # Test the command on exists, permissions and so on
+ my $u = main::getModule('USER');
+ my ($cmdobj, $cmdname, $shorterr, $err) = $u->checkCommand($console, $ucmd);
+ $console->{call} = $cmdname;
+ if($cmdobj and not $shorterr) {
+ $cmdobj->{callback}($watcher, $console, $udata, $result );
+ } elsif($shorterr eq 'noperm' or $shorterr eq 'noactive') {
+ return $console->status403($err);
+ } else {
+ return $obj->usage($watcher, $console, undef, $err);
+ }
+}
+
+
+# ------------------
+sub usage {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return main::getModule('TELNET')->usage(@_);
+}
+
+# ------------------
+sub findskins
+# ------------------
+{
+ my $obj = shift || return error ('No Object!' );
+ my $found;
+ find({ wanted => sub{
+ if(-d $File::Find::name and -e $File::Find::name.'/wapd.tmpl' ) {
+ my $l = basename($File::Find::name);
+ push(@{$found},[$l,$l]);
+ }
+ },
+ follow => 1,
+ follow_skip => 2,
+ },
+ $obj->{paths}->{HTMLDIR}
+ );
+ error "Can't find useful WML Skin at : $obj->{paths}->{HTMLDIR}"
+ if(scalar $found == 0);
+ return $found;
+}
+
+1;
diff --git a/lib/XXV/OUTPUT/Ajax.pm b/lib/XXV/OUTPUT/Ajax.pm
new file mode 100644
index 0000000..0151b5f
--- /dev/null
+++ b/lib/XXV/OUTPUT/Ajax.pm
@@ -0,0 +1,231 @@
+package XXV::OUTPUT::Ajax;
+
+use strict;
+
+#use Template;
+use vars qw($AUTOLOAD);
+use Locale::gettext;
+use Tools;
+
+$SIG{CHLD} = 'IGNORE';
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'Ajax',
+ Prereq => {
+ 'XML::Simple' => 'Easy API to maintain XML (esp config files)',
+ 'JSON' => 'Parse and convert to JSON (JavaScript Object Notation)',
+ },
+ Description => gettext('This receive and send Ajax messages.'),
+ Version => '0.01',
+ Date => '27.10.2004',
+ Author => 'xpix',
+ };
+ return $args;
+}
+
+# ------------------
+sub AUTOLOAD {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || {};
+ my $params = shift || 0;
+
+ my $name = (split('::', $AUTOLOAD))[-1];
+ return if($name eq 'DESTROY');
+
+ $obj->{nopack} = 1;
+ $obj->out( $data, $params, $name );
+
+ $obj->{call} = '';
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{handle} = $attr{'-handle'}
+ || return error('No handle defined!');
+
+ $self->{cgi} = $attr{'-cgi'}
+ || return error('No TemplateDir given!');
+
+ $self->{browser} = $attr{'-browser'}
+ || return error('No Mimehash given!');
+
+ $self->{xml} = XML::Simple->new()
+ || return error('XML failed!');
+
+ $self->{outtype} = $attr{'-output'}
+ || return error('No output type given!');
+
+ $self->{types} = {
+ 'xml' => 'application/xml',
+ 'json' => 'text/html',
+ 'html' => 'text/html',
+ 'javascript' => 'text/javascript',
+ };
+
+ # New JSON Object if required
+ if($self->{outtype} eq 'json') {
+ $self->{json} = JSON->new()
+ unless(ref $self->{json});
+ }
+
+ $self->{TYP} = 'AJAX';
+
+ $self->{CMDSTAT} = undef;
+
+ return $self;
+}
+
+# ------------------
+sub out {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || 0;
+ my $para = shift || 0;
+ my $name = shift || 'noName';
+ my $type = shift || $obj->{types}->{$obj->{outtype}} || 'text/plain';
+ my %args = @_;
+
+ $obj->{nopack} = 1;
+ unless(defined $obj->{header}) {
+ # HTTP Header
+ $obj->{output_header} = $obj->header($type, \%args);
+ }
+
+ $obj->{sendbytes}+= length($data);
+
+ if($obj->{outtype} eq 'json') {
+ $obj->{output}->{data} = $data;
+ } else {
+ $obj->{output}->{DATA} = $data;
+ $obj->{output}->{$name}->{data} = $data;
+ $obj->{output}->{$name}->{params} = $para
+ if($para);
+ }
+}
+
+# ------------------
+sub printout {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $nopack = shift || $obj->{nopack} || 0;
+
+
+ my $content .= ($obj->{outtype} eq 'xml'
+ ? $obj->{xml}->XMLout($obj->{output})
+ :
+ ( $obj->{outtype} eq 'json'
+ ? $obj->{json}->objToJson ($obj->{output}, {pretty => 1, indent => 2})
+ : $obj->{output}->{DATA})
+ );
+ # Kompress
+ $content = Compress::Zlib::memGzip($content)
+ if(! $nopack and $obj->{Zlib} and $obj->{browser}->{accept_gzip});
+
+ $obj->{handle}->print($obj->{output_header}, $content);
+
+ undef $obj->{output};
+ undef $obj->{output_header};
+ undef $obj->{nopack};
+}
+
+
+# ------------------
+sub header {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $typ = shift || return error ('No Type!' );
+ my $arg = shift || {};
+
+ $arg->{'Content-encoding'} = 'gzip'
+ if($obj->{browser}->{accept_gzip} && ((!defined $obj->{nopack}) || $obj->{nopack} == 0) );
+
+ $arg->{'Cache-Control'} = 'no-cache, must-revalidate' if(!defined $arg->{'Cache-Control'});
+ $arg->{'Pragma'} = 'no-cache' if(!defined $arg->{'Pragma'});
+
+ $obj->{header} = 200;
+ return $obj->{cgi}->header(
+ -type => $typ,
+ -status => "200 OK",
+ -expires => "now",
+ %{$arg},
+ );
+}
+
+# ------------------
+sub headerNoAuth {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $typ = shift || 'text/html';
+
+ $obj->{header} = 401;
+ return $obj->{cgi}->header(
+ -type => $typ,
+ -status => "401 Authorization Required\nWWW-Authenticate: Basic realm=\"xxvd\""
+ );
+}
+
+# ------------------
+sub msg {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || 0;
+ my $err = shift || 0;
+
+
+ my $msg;
+ if(! $err and $data) {
+ $msg = $data;
+ } else {
+ $msg = sprintf('ERROR:%s (%s)', $data);
+ }
+
+ $obj->out( $msg, 0, 'msg' );
+
+ $obj->{call} = '';
+}
+
+# ------------------
+sub typ {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{TYP};
+}
+
+# ------------------
+sub setCall {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Name!' );
+
+ $obj->{call} = $name;
+ return $obj->{call};
+}
+
+# ------------------
+sub browser {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{browser};
+}
+
+1;
diff --git a/lib/XXV/OUTPUT/Console.pm b/lib/XXV/OUTPUT/Console.pm
new file mode 100644
index 0000000..b1b6d80
--- /dev/null
+++ b/lib/XXV/OUTPUT/Console.pm
@@ -0,0 +1,741 @@
+package XXV::OUTPUT::Console;
+
+BEGIN{
+ $ENV{PERL_RL} = 'Perl'
+};
+
+use Locale::gettext;
+use Term::ReadLine;
+
+use strict;
+
+use Tools;
+use Pod::Text;
+use vars qw($AUTOLOAD);
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'Telnet',
+ Prereq => {
+ 'XML::Simple' => 'Easy API to maintain XML (esp config files)',
+ 'Text::ASCIITable' => 'Create a nice formatted table using ASCII characters.',
+ 'Term::ReadLine::Perl' => 'a quick implementation of the minimal interface to Readline',
+ },
+ Description => gettext('This receive and send ASCII messages'),
+ Version => '0.01',
+ Date => '27.10.2004',
+ Author => 'xpix',
+ };
+ return $args;
+}
+
+# ------------------
+sub AUTOLOAD {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || {};
+ my $params = shift || 0;
+
+ my $name = (split('::', $AUTOLOAD))[-1];
+ return if($name eq 'DESTROY');
+
+ $obj->message(gettext("Sorry, but this command is not available in this Interface!"));
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{handle} = $attr{'-handle'}
+ || return error('No handle defined!');
+
+ $self->{dbh} = $attr{'-dbh'}
+ || return error('No DBH defined!');
+
+ $self->{paths} = $attr{'-paths'}
+ || return error('No Paths given!');
+
+ $self->{term} = Term::ReadLine->new('xxv', $self->{handle}, $self->{handle})
+ || return error('No Term given!');
+
+ $self->{TYP} = 'CONSOLE';
+
+ $self->{maxwidth} = 20;
+
+ $self->{TableDefaults} = {
+ allowANSI => 1,
+ allowHTML => 1,
+ drawRowLine => 1,
+ reportErrors=> 1,
+ cb_count => sub{ $self->_myallowansi_cb(@_) },
+ };
+
+ return $self;
+}
+
+# ------------------
+sub printout {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ $data =~ s/[^\r]\n/\r\n/sig;
+
+ my $h = $obj->{handle};
+ print $h $data."\r\n";
+}
+
+# ------------------
+sub message {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ my $t = Text::ASCIITable->new();
+ $t->setOptions( $obj->{TableDefaults} );
+
+ $t->setCols(gettext('Message'));
+ if(ref $data eq 'ARRAY') {
+ map { $t->addRow($_) } @$data;
+ } else {
+ $t->addRow($data);
+ }
+ $obj->printout($t->draw());
+}
+
+# ------------------
+sub push {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ $obj->printout($data);
+}
+# ------------------
+sub msg {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+ my $err = shift || return $obj->message($data);
+
+ $obj->err($data) if($err);
+}
+
+# ------------------
+sub err {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ my $t = Text::ASCIITable->new;
+ $t->setOptions( $obj->{TableDefaults} );
+
+ $t->setCols(gettext('ERROR'));
+ if(ref $data eq 'ARRAY') {
+ map { $t->addRow($_) } @$data;
+ } else {
+ $t->addRow($data);
+ }
+ $obj->printout($t->draw());
+}
+
+# ------------------
+sub menu {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ my $t = Text::ASCIITable->new;
+ $t->setOptions( $obj->{TableDefaults} );
+ $t->setCols(gettext('Module'),
+ gettext('Short cut'),
+ gettext('Name'),
+ gettext('Description'));
+
+ foreach my $line (@$data) {
+ if(ref $line eq 'ARRAY') {
+ $t->addRow(@$line);
+ } else {
+ $obj->printout($line);
+ }
+ }
+
+ $obj->printout($t->draw());
+}
+
+# ------------------
+sub littlemenu {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ my $text = $data->{title}."\n";
+ $text .= gettext("Please use command and one of the following sectors:\n");
+ $text .= join(', ', sort keys %{$data->{links}});
+
+ $obj->message($text);
+}
+
+# ------------------
+sub login {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ my $questions = [
+ 'Name' => {
+ typ => 'string',
+ msg => gettext("Username?"),
+ },
+ 'Password' => {
+ typ => 'string',
+ msg => gettext("Password?"),
+ },
+ ];
+
+ my $answer = $obj->question($data."\r\n", $questions);
+ return $answer;
+}
+
+
+# ------------------
+sub table {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+ my $param = shift || {};
+ my $noPrint = shift || 0;
+
+ my $out = '';
+ my $UserMaxWidth = (delete $param->{maxwidth} || $obj->{maxwidth});
+
+ my $fields = (ref $data eq 'ARRAY') ?
+ shift @$data :
+ [
+ gettext('Name'),
+ gettext('Value')
+ ];
+
+ my $t = Text::ASCIITable->new;
+ $t->setOptions( $obj->{TableDefaults} );
+ $t->setOptions($param) if($param);
+
+ my ($displayFields, $displayData) = $obj->_parseData($fields, $data);
+ $t->setCols(@$displayFields);
+ map { $t->setColWidth($_, $UserMaxWidth) } @$displayFields;
+
+ if(ref $displayData eq 'ARRAY') {
+ foreach my $line (@$displayData) {
+ if(ref $line eq 'ARRAY') {
+ $t->addRow(@$line);
+ } else {
+ $out .= $line;
+ }
+ }
+ } else {
+ foreach my $name (sort keys %$data) {
+ my $dspl = '';
+ if(ref $data->{$name} eq 'HASH') {
+ foreach (sort keys %{$data->{$name}}) {
+ if(ref $data->{$name}->{$_}) {
+ $dspl .= $obj->table($data->{$name}->{$_}, $param, 'noPrint');
+ } else {
+ $dspl .= sprintf("%s: %s\n", $_, $data->{$name}->{$_});
+ }
+ }
+ } else {
+ $dspl = $data->{$name};
+ }
+ $t->addRow($name, $dspl);
+ }
+ }
+
+ $out .= $t->draw();
+
+ unless($noPrint) {
+ $obj->printout($out);
+ } else {
+ return $out;
+ }
+}
+
+# ------------------
+sub question {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $title = shift || 0;
+ my $questions = shift || return error ('No Data!' );
+ my $erg = shift || {};
+
+ my $quest;
+
+ # Check Data
+ if(ref $erg eq 'HASH' and ref $questions eq 'ARRAY' and exists $erg->{action}) {
+ my $error;
+ @$quest = @$questions;
+ while (my ($name, $data) = splice(@$quest, 0, 2)) {
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $erg->{$name});
+
+ # Check Callback
+ if(exists $data->{check} and ref $data->{check} eq 'CODE' and not $error) {
+ ($erg->{$name}, $error) = $data->{check}($erg->{$name}, $data, $erg);
+ }
+
+ # Check on directory
+ if($data->{typ} eq 'dir' and $data->{required} and not -d $erg->{$name}) {
+ ($erg->{$name}, $error) = (undef, sprintf(gettext("Directory '%s' is doesn't exist!"), $erg->{$name}));
+ }
+
+ # Check on file
+ if($data->{typ} eq 'file' and $data->{required} and not -e $erg->{$name}) {
+ ($erg->{$name}, $error) = (undef, sprintf(gettext("File '%s' is doesn't exist!"), $erg->{$name}));
+ }
+
+ if($error) {
+ $obj->err(sprintf(gettext("Error at field '%s' (%s) : %s"), $data->{msg}, $name, $error));
+ last;
+ }
+ }
+ unless($error) {
+ delete $erg->{action};
+ return $erg;
+ }
+ }
+
+ $obj->printout("$title\n") if($title);
+ if(ref $questions eq 'ARRAY') {
+ while (my ($name, $data) = splice(@$questions, 0, 2)) {
+ my $type = delete $data->{typ};
+ $type ||= 'string';
+ $erg->{$name} = $obj->$type($data);
+ }
+ } else {
+ my $type = delete $questions->{typ};
+ $type ||= 'string';
+ $erg = $obj->$type($questions);
+ }
+ return $erg;
+}
+
+# ------------------
+sub list {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $t = $obj->{term};
+ my $error;
+ my $def;
+
+ if(defined $data->{def}) {
+ if(ref $data->{def} eq 'CODE') {
+ $def = $data->{def}();
+ } elsif(ref $data->{def} eq 'ARRAY') {
+ $def = join(', ', @{$data->{def}});
+ } else {
+ $def = $data->{def};
+ }}
+
+ my $message = (defined $def) ? sprintf('%s [%s]: ', $data->{msg}, $def) : $data->{msg}.': ';
+
+ while (defined (my $answer = $t->readline($message))) {
+ $answer =~ s/[\r|\n]//sig;
+
+ # Default value ...
+ $answer = $data->{def}
+ if(defined $data->{def} and not $answer);
+
+ # List ...
+ $error = sprintf(gettext("\nPlease use one of this list items:\n %s"), join(",\n", @{$data->{choices}}))
+ unless(grep($_ eq $answer, @{$data->{choices}}));
+
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $answer);
+
+ # Check Callback ...
+ ($answer, $error) = $data->{check}($answer)
+ if(defined $data->{check} and ref $data->{check} eq 'CODE');
+
+ # Display Error Message ....
+ if($error) {
+ $message = (defined $data->{def}) ? sprintf('%s [%s]', $error, $def) : $error.': ';
+ undef $error;
+ } else {
+ return $answer;
+ }
+ }
+}
+
+
+# ------------------
+sub string {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $t = $obj->{term};
+ my $error;
+
+ my $def;
+ if(defined $data->{def}) {
+ if(ref $data->{def} eq 'CODE') {
+ $def = $data->{def}();
+ } else {
+ $def = $data->{def};
+ }}
+
+ my $message = (defined $def) ? sprintf('%s [%s]: ', $data->{msg}, $def) : $data->{msg}.': ';
+ while (defined (my $answer = $t->readline($message))) {
+ $answer =~ s/[\r|\n]//sig;
+ # Default value ...
+ $answer = $def
+ if($def and not $answer);
+
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $answer);
+
+ # Check Callback ...
+ ($answer, $error) = $data->{check}($answer)
+ if(defined $data->{check} and ref $data->{check} eq 'CODE');
+
+ # Display Error Message ....
+ if($error) {
+ $message = ($def) ? sprintf('%s [%s]', $error, $def) : $error.': ';
+ undef $error;
+ } else {
+ return $answer;
+ }
+ }
+}
+
+# ------------------
+sub file {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->string(@_);
+}
+
+# ------------------
+sub dir {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->string(@_);
+}
+
+# ------------------
+sub password {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->string(@_);
+}
+
+# ------------------
+sub date {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->string(@_);
+}
+
+# ------------------
+sub integer {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $t = $obj->{term};
+ my $error;
+
+ my $message = (defined $data->{def} ) ? sprintf('%s [%s]: ', $data->{msg}, $data->{def}) : $data->{msg}.': ';
+ while (defined (my $answer = $t->readline($message))) {
+ $answer =~ s/[\r|\n]//sig;
+
+ # Default value ...
+ $answer = $data->{def}
+ if($data->{def} and ! $answer);
+
+ # Interger?
+ $error = sprintf(gettext("'%s' is not an integer!"),$answer)
+ if($answer and not int($answer));
+
+ # Required?
+ $error = $data->{req}
+ if(defined $data->{req} and not $answer);
+
+ # Check Callback
+ ($answer, $error) = $data->{check}($answer)
+ if(defined $data->{check} and ref $data->{check} eq 'CODE');
+
+ if($error) {
+ $message = (defined $data->{def}) ? sprintf('%s [%s]', $error, $data->{def}) : $error.': ';
+ undef $error;
+ } else {
+ return $answer;
+ }
+ }
+}
+
+# ------------------
+sub confirm {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $t = $obj->{term};
+ my $error;
+
+ my $message = (defined $data->{def} ) ? sprintf('%s [%s]: ', $data->{msg}, $data->{def}) : $data->{msg}.': ';
+ while (defined (my $answer = $t->readline($message))) {
+ $answer =~ s/[\r|\n]//sig;
+
+ # Default value ...
+ $answer = $data->{def}
+ if($data->{def} and ! $answer);
+
+ # Only yes or no ...
+ $error = gettext("Please answer with 'y'es or 'n'o: ")
+ if($answer !~ /^[y|n]$/);
+
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $answer);
+
+ # Check Callback ...
+ ($answer, $error) = $data->{check}($answer)
+ if(defined $data->{check} and ref $data->{check} eq 'CODE');
+
+ # Display Error Message ....
+ if($error) {
+ $message = (defined $data->{def}) ? sprintf('%s [%s]', $error, $data->{def}) : $error.': ';
+ undef $error;
+ } else {
+ return $answer;
+ }
+ }
+}
+
+# ------------------
+sub hidden {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+
+ return error('No <def> in hidden Variable!')
+ unless(defined $data->{def});
+ return $data->{def};
+}
+
+# ------------------
+sub image {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data to display');
+
+ return $obj->message(gettext("Sorry, but i cannot display an image on this Interface."));
+}
+
+# ------------------
+sub checkbox {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $t = $obj->{term};
+ my $error;
+
+ my $message = (defined $data->{def}) ? sprintf('%s [%s]: ', $data->{msg}, join(', ', @{$data->{def}})) : $data->{msg}.': ';
+ while (defined (my $answer = $t->readline($message))) {
+ $answer =~ s/[\r|\n]//sig;
+ # Default value ...
+ $answer = $data->{def}
+ if(defined $data->{def} and not $answer);
+
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $answer);
+
+ # Check Callback ...
+ ($answer, $error) = $data->{check}($answer, $data)
+ if(defined $data->{check} and ref $data->{check} eq 'CODE');
+
+ # Display Error Message ....
+ if($error) {
+ $message = (defined $data->{def}) ? sprintf('%s [%s]', $error, join(', ', @{$data->{def}})) : $error.': ';
+ undef $error;
+ } else {
+ return $answer;
+ }
+ }
+}
+
+# ------------------
+sub radio {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || return error ('No Data!' );
+ my $t = $obj->{term};
+ my $error;
+
+ my $message = (defined $data->{def}) ? sprintf('%s [%s]: ', $data->{msg}, $data->{def}) : $data->{msg}.': ';
+ while (defined (my $answer = $t->readline($message))) {
+ $answer =~ s/[\r|\n]//sig;
+ # Default value ...
+ $answer = $data->{def}
+ if(defined $data->{def} and not $answer);
+
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $answer);
+
+ # only one of all ...
+ $error = sprintf(gettext("You can only use one of this items: %s"), join(',', @{$data->{choices}}))
+ unless(grep( $answer eq $_, @{$data->{choices}}));
+
+
+ # Check Callback ...
+ ($answer, $error) = $data->{check}($answer, $data)
+ if(defined $data->{check} and ref $data->{check} eq 'CODE');
+
+ # Display Error Message ....
+ if($error) {
+ $message = (defined $data->{def}) ? sprintf('%s [%s]', $error, $data->{def}) : $error.': ';
+ undef $error;
+ } else {
+ return $answer;
+ }
+ }
+}
+
+# ------------------
+sub pod {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $modname = uc(shift) || return error ('No Modname!' );
+ $modname = ucfirst($modname) if($modname eq 'GENERAL');
+
+ my $podfile = sprintf('%s/%s.pod', $obj->{paths}->{PODPATH}, $modname);
+ return $obj->err(gettext('Module %s not found!'), $modname)
+ unless(-r $podfile);
+ my $tmpdir = main::getModule('USER')->userTmp;
+ my $outfile = sprintf('%s/%s_%d.pod', $tmpdir, $modname, time);
+
+ my $parser = Pod::Text->new (sentence => 0, width => 78);
+
+ # Read POD from file.pod and write to file.txt.
+ $parser->parse_from_file (
+ $podfile,
+ $outfile
+ );
+ return error('Problem to convert pod2txt')
+ unless(-r $outfile);
+ my $txt = load_file($outfile);
+
+ $obj->message($txt);
+}
+
+# ------------------
+sub txtfile {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $filename = shift || return error ('No TxtFile to diplay!' );
+ my $param = shift || {};
+
+ my $txtfile = sprintf('%s/%s.txt', $obj->{paths}->{DOCPATH}, $filename);
+ my $gzfile = sprintf('%s/%s.txt.gz', $obj->{paths}->{DOCPATH}, $filename);
+
+ $txtfile = main::getModule('HTTPD')->unzip($gzfile)
+ if(! -r $txtfile and -e $gzfile and -r $gzfile);
+
+ my $txt = load_file($txtfile);
+ return $obj->message($txt, {tags => {first => "File: $filename.txt"}});
+}
+
+# ------------------
+sub remote {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $text = qq!
+.-----------------------.
+| 1 | 2 | 3 |
+|-----------------------|
+| 4 | 5 | 6 |
+|-----------------------|
+| 7 | 8 | 9 |
+|-----------------------|
+| Menu | 0 | Back |
+|-----------------------|
+| | Up | |
+|-----------------------|
+| Left | Ok | Right |
+|-----------------------|
+| | Down | Blue |
+|-----------------------|
+| Red | Green | Yellow|
+|-----------------------|
+| Vol:+/- | Chan: <> |
+|-----------------------|
+| << | >> |
+|-----------------------|
+!;
+ $obj->printout($text);
+}
+
+# ------------------
+sub _myallowansi_cb {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $_ = shift;
+ s/\33\[(\d+(;\d+)?)?[musfwhojBCDHRJK]//g;
+ return length($_);
+}
+
+# ------------------
+sub _parseData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $fields = shift || return error ('No Fields!' );
+ my $data = shift || return error ('No Data!' );
+
+ my $displayFields = [];
+ @$displayFields = grep(!/^__/, @$fields);
+
+ if(ref $data eq 'ARRAY') {
+ foreach my $d (@$data) {
+ my $c = -1; my @newData;
+ foreach my $r (@$d) {
+ $c++;
+ CORE::push(@newData, $r)
+ if($fields->[$c] !~ /^__/);
+ }
+ @$d = @newData;
+ }
+ return ($displayFields, $data);
+ } else {
+ return ($fields, $data);
+ }
+}
+
+
+# ------------------
+sub typ {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ return $obj->{TYP};
+}
+
+1;
diff --git a/lib/XXV/OUTPUT/Dump.pm b/lib/XXV/OUTPUT/Dump.pm
new file mode 100644
index 0000000..02ea847
--- /dev/null
+++ b/lib/XXV/OUTPUT/Dump.pm
@@ -0,0 +1,62 @@
+package XXV::OUTPUT::Dump;
+
+use strict;
+
+use vars qw($AUTOLOAD);
+use Tools;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'Dump',
+ Prereq => {
+ },
+ Description => gettext('This receive and send Dump messages.'),
+ Version => '0.01',
+ Date => '27.10.2004',
+ Author => 'xpix',
+ };
+ return $args;
+}
+# ------------------
+sub AUTOLOAD {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return if($AUTOLOAD =~ /DESTROY$/);
+dumper(\@_);
+ return @_;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{TYP} = 'INTERFACE';
+
+ return $self;
+}
+
+# ------------------
+sub typ {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{TYP};
+}
+
+1;
diff --git a/lib/XXV/OUTPUT/HTML/PUSH.pm b/lib/XXV/OUTPUT/HTML/PUSH.pm
new file mode 100644
index 0000000..7c7d7df
--- /dev/null
+++ b/lib/XXV/OUTPUT/HTML/PUSH.pm
@@ -0,0 +1,95 @@
+package XXV::OUTPUT::HTML::PUSH;
+
+use strict;
+
+use Tools;
+
+$| = 1;
+
+=head1 NAME
+
+XXV::OUTPUT::HTML::PUSH - A Push for http system
+
+=head1 SYNOPSIS
+
+ use XXV::OUTPUT::HTML::PUSH;
+
+ my $pusher = XXV::OUTPUT::HTML::PUSH->new(
+ -cgi => $obj->{cgi}, # The CGI Object from Lincoln Stein
+ -handle => $obj->{handle}, # The handle to printout the http Stuff
+ );
+
+ $pusher->start(); # Start the Push Process
+
+ while($c > 10) {
+ $pusher->print($c++); # Print out the message
+ }
+
+ $pusher->stop(); # Stop the Push
+
+
+=cut
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ $self->{handle} = $attr{'-handle'}
+ || return error('No handle defined!');
+
+ $self->{cgi} = $attr{'-cgi'}
+ || return error('No CGI Object defined!');
+
+ return $self;
+}
+
+# ------------------
+sub start {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $out = shift || 0;
+ $obj->{handle}->print($obj->{cgi}->multipart_init(-boundary=>'----here we go!'));
+ $obj->print($out) if($out);
+}
+
+# ------------------
+sub print {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $msg = shift || return;
+ my $type = shift || 'text/html';
+
+ $obj->{handle}->print($obj->{cgi}->multipart_start(-type=>$type));
+ $obj->{handle}->print($msg."\n");
+ $obj->{handle}->print($obj->{cgi}->multipart_end);
+}
+
+# ------------------
+sub follow_print {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $msg = shift || return;
+ my $type = shift || 'text/html';
+
+ unless($obj->{header}) {
+ $obj->{handle}->print($obj->{cgi}->multipart_start(-type=>$type));
+ $obj->{header} = 1;
+ }
+ $obj->{handle}->print($msg."\n");
+}
+
+# ------------------
+sub stop {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{handle}->print($obj->{cgi}->multipart_end);
+ $obj->{handle}->print($obj->{cgi}->header(
+ -type => 'text/html',
+ -status => "200 OK",
+ ));
+}
+
+1;
diff --git a/lib/XXV/OUTPUT/HTML/WAIT.pm b/lib/XXV/OUTPUT/HTML/WAIT.pm
new file mode 100644
index 0000000..8ebd430
--- /dev/null
+++ b/lib/XXV/OUTPUT/HTML/WAIT.pm
@@ -0,0 +1,169 @@
+package XXV::OUTPUT::HTML::WAIT;
+
+use strict;
+
+use Tools;
+use XXV::OUTPUT::HTML::PUSH;
+
+=head1 NAME
+
+XXV::OUTPUT::HTML::WAIT - A Processbar for XXV system
+
+=head1 SYNOPSIS
+
+ use XXV::OUTPUT::HTML::WAIT;
+
+ my $waiter = XXV::OUTPUT::HTML::WAIT->new(
+ -cgi => $obj->{cgi}, # The CGI Object from Lincoln Stein
+ -handle => $obj->{handle}, # The handle to printout the http Stuff
+ -callback => sub{ # Callback for html output.
+ # In this case parse the html template wait.tmpl
+ my ($min, $max, $cur, $steps) = @_;
+ my $out = $obj->parseTemplate(
+ 'wait',
+ {
+ msg => $msg,
+ minimum => $min,
+ current => $cur,
+ maximum => $max,
+ steps => $steps
+ },
+ );
+ return $out;
+ },
+ );
+
+ $waiter->min(0); # Min Value for process Bar
+ $waiter->max(10); # Max Value for process Bar
+ $waiter->screen('yes'); # Every call of next will redraw the process bar
+
+ while($c > 10) {
+ $waiter->next($c++); # Next Event with current value
+ }
+ $waiter->end;
+
+=cut
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ $self->{handle} = $attr{'-handle'}
+ || return error('No handle defined!');
+
+ $self->{cgi} = $attr{'-cgi'}
+ || return error('No CGI Object defined!');
+
+ $self->{callback} = $attr{'-callback'}
+ || return error('No Callback to print out!');
+
+ $self->{steps} = $attr{'-steps'} || 10;
+
+ $self->{pusher} = XXV::OUTPUT::HTML::PUSH->new(
+ -cgi => $self->{cgi}, # The CGI Object from Lincoln Stein
+ -handle => $self->{handle}, # The handle to printout the http Stuff
+ );
+
+ $self->init();
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ $obj->{STEP} = 0;
+ $obj->{pusher}->start();
+ undef $obj->{FirstRefresh};
+}
+
+# ------------------
+sub next {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cur = shift || $obj->{MAX};
+ my $end = shift || 0;
+ my $msg = shift || 0;
+
+
+ # Don't show really every step, even is screen defined,
+ # avoid high traffic and long duration of waiter progress
+ my $t = time;
+ return
+ if(defined $obj->{SCREEN} && $obj->{SCREEN} eq 'no'
+ && $end == 0
+ && $obj->{LastRefreshTime} && $obj->{LastRefreshTime} > ($t - 1));
+
+ # remember time from from first call
+ $obj->{FirstRefresh} = $t
+ if(not $obj->{FirstRefresh});
+
+ # calc end time of execution
+ my $rest = $end ? 0 : $obj->{MAX} - $cur;
+ my $deltaT = $t - $obj->{FirstRefresh};
+ my $etaT = ($cur > 0) ? ($deltaT / $cur * $rest) : 0;
+ # Format end time of execution from seconds to human readable format
+ my $eta = sprintf("%02d:%02d:%02d",$etaT / 3600 % 24 ,($etaT / 60) % 60, $etaT % 60 );
+
+ $obj->{LastRefreshTime} = $t;
+
+
+ # 2.2 = 22 / 10
+ my $step = $obj->{MAX} / $obj->{steps};
+ $obj->{STEP} += $step;
+
+ if ($end or $cur > $obj->{MAX}) {
+ $obj->{pusher}->stop();
+ my $out = $obj->{endcallback}($obj->{MIN}, $obj->{MAX}, $cur, $obj->{steps}, $msg, $eta)
+ if(ref $obj->{endcallback} eq 'CODE');
+ } else {
+ my $out = $obj->{callback}($obj->{MIN}, $obj->{MAX}, $cur, $obj->{steps}, $msg, $eta);
+ $obj->{pusher}->print($out);
+ }
+}
+
+# ------------------
+sub end {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $msg = shift || 0;
+
+ return $obj->next(undef, $obj->{MAX}, 1, $msg);
+}
+
+
+# ------------------
+sub endcallback {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{endcallback} = shift || return $obj->{endcallback};
+}
+
+# ------------------
+sub max {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{MAX} = shift || return $obj->{MAX};
+}
+
+# ------------------
+sub min {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{MIN} = shift || return $obj->{MIN};
+}
+
+# ------------------
+sub screen {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{SCREEN} = shift || return $obj->{SCREEN};
+}
+
+
+1;
diff --git a/lib/XXV/OUTPUT/Html.pm b/lib/XXV/OUTPUT/Html.pm
new file mode 100644
index 0000000..72251ba
--- /dev/null
+++ b/lib/XXV/OUTPUT/Html.pm
@@ -0,0 +1,851 @@
+package XXV::OUTPUT::Html;
+
+use strict;
+
+#use Template;
+use vars qw($AUTOLOAD);
+use Locale::gettext;
+use Tools;
+use XXV::OUTPUT::HTML::WAIT;
+use File::Path;
+use File::Basename;
+use Pod::Html;
+use Fcntl;
+#use Thread;
+
+$SIG{CHLD} = 'IGNORE';
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'Html',
+ Prereq => {
+ 'HTML::TextToHTML' => 'convert plain text file to HTML. ',
+ },
+ Description => gettext('This receive and send HTML messages.'),
+ Version => '0.92',
+ Date => '2007-01-21',
+ Author => 'xpix',
+ };
+ return $args;
+}
+
+# ------------------
+sub AUTOLOAD {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || {};
+ my $params = shift || 0;
+
+ my $name = (split('::', $AUTOLOAD))[-1];
+ return if($name eq 'DESTROY');
+
+ my $output = $obj->parseTemplate($name, $data, $params);
+
+ $obj->out( $output );
+
+ $obj->{call} = '';
+}
+
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{handle} = $attr{'-handle'}
+ || return error('No handle defined!');
+
+ $self->{paths} = $attr{'-paths'}
+ || return error('No Paths defined!');
+
+ $self->{dbh} = $attr{'-dbh'}
+ || return error('No DBH defined!');
+
+ $self->{htmdir} = $attr{'-htmdir'}
+ || return error('No htmdir given!');
+
+ $self->{htmdef} = $attr{'-htmdef'}
+ || return error('No htmdef given!');
+
+ $self->{cgi} = $attr{'-cgi'}
+ || return error('No TemplateDir given!');
+
+ $self->{mime} = $attr{'-mime'}
+ || return error('No Mimehash given!');
+
+ $self->{browser} = $attr{'-browser'}
+ || return error('No Mimehash given!');
+
+ $self->{start} = $attr{'-start'}
+ || return error('No StartPage given!');
+
+ $self->{debug} = $attr{'-debug'}
+ || 0;
+
+ $self->{TYP} = 'HTML';
+
+ # Forward name of Server for CGI::server_software
+ $ENV{'SERVER_SOFTWARE'} = sprintf("xxvd %s",main::getVersion());
+ $ENV{'SERVER_PROTOCOL'} = 'HTTP/1.1';
+
+ # create Template object
+ $self->{tt} = Template->new(
+ START_TAG => '\<\?\%', # Tagstyle
+ END_TAG => '\%\?\>', # Tagstyle
+ INCLUDE_PATH => [$self->{htmdir},$self->{htmdef}] , # or list ref
+ INTERPOLATE => 1, # expand "$var" in plain text
+ PRE_CHOMP => 1, # cleanup whitespace
+ EVAL_PERL => 1, # evaluate Perl code blocks
+ );
+
+ eval "use Compress::Zlib";
+ $self->{Zlib} = ($@ ? 0 : 1);
+
+
+ # create TextToHTML object
+ $self->{txt2html} = HTML::TextToHTML->new(
+ preformat_whitespace_min => 4,
+ );
+
+ &bench('CLEAR');
+
+ return $self;
+}
+
+# ------------------
+sub parseTemplate {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Name!' );
+ my $data = shift || return error ('No Data!' );
+ my $params = shift || {};
+
+ my $output;
+ unless(defined $obj->{header}) {
+ $output .= $obj->parseTemplateFile("start", $data, $params);
+ }
+ $output .= $obj->parseTemplateFile($name, $data, $params,((exists $obj->{call}) ? $obj->{call} : 'nothing'));
+ return $output;
+}
+
+# ------------------
+sub index {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{nopack} = 1;
+ $obj->{call} = 'index';
+ my $params = {};
+ $params->{start} = $obj->{start};
+ $obj->out( $obj->parseTemplateFile("index", {}, $params, $obj->{call}));
+}
+
+
+# ------------------
+sub parseTemplateFile {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Name!' );
+ my $data = shift || return error ('No Data!' );
+ my $params = shift || return error ('No params!' );
+ my $call = shift || 'nothing';
+
+ $obj->parseData($data)
+ if($name ne 'start' && $name ne 'footer'
+ && !$obj->{dontparsedData} );
+
+ my $t = $obj->{tt};
+ my $u = main::getModule('USER');
+
+ # you can use two templates, first is a user defined template
+ # and second the standard template
+ # i.e. call the htmlhelp command the htmlhelp.tmpl
+ # SpecialTemplate: ./htmlRoot/usage.tmpl
+ # StandardTemplate: ./htmlRoot/widgets/menu.tmpl
+ my $widget_first = sprintf('%s.tmpl', $call);
+ my $widget_second = sprintf('widgets/%s.tmpl', $name);
+ my $widget = (-e sprintf('%s/%s', $obj->{htmdir}, $widget_first) ? $widget_first : $widget_second);
+
+ my $user = ($u->{active} eq 'y' && $obj->{USER}->{Name} ? $obj->{USER}->{Name} : "nobody" );
+ my $output;
+ my $vars = {
+ cgi => $obj->{cgi},
+ call => $name,
+ data => $data,
+ type => ref $data,
+ info => $obj->browser,
+ param => $params,
+ pid => $$,
+ debug => $obj->{debug},
+ user => $user,
+ # query the current locale
+ locale => main::getGeneralConfig->{Language},
+ allow => sub{
+ my($cmdobj, $cmdname, $se, $err) = $u->checkCommand($obj, $_[0],"1");
+ return $cmdobj;
+ },
+
+ # Deaktiviert da durch parseData alle Daten
+ # komplett mit entities behandelt wurden
+ entities => sub{ return $_[0] },
+
+ # Remove entities from parameters
+ reentities => sub{ return reentities($_[0]) },
+
+ # Escape strings for javascript
+ escape => sub{
+ my $s = shift; # string
+ $s =~ s/\r//g;
+ $s =~ s/\n//g;
+ $s =~ s/&quot;/\\&quot;/g;
+ $s =~ s/\'/\\\'/g;
+ return $s;
+ },
+
+ # truncate string with entities
+ chop => sub{
+ my $s = shift; # string
+ my $c = shift; # count
+ my $l = shift || 0; # lines
+
+ if ( $c > 3 ) {
+ $s = reentities($s);
+ if($l)
+ {
+ my @text = split ('\r\n', $s);
+ if(scalar @text > 1)
+ {
+ my @lines;
+ foreach my $line (@text)
+ {
+ if ( length( $line ) > $c ) {
+ $line = substr( $line, 0, ( $c - 3 ) ) . '...';
+ }
+ --$l;
+ last if($l < 0);
+ push(@lines,$line);
+ }
+ $s = join("\r\n",@lines);
+ } else {
+ if ( length( $s ) > ($c * $l) ) {
+ $s = substr( $s, 0, ( ($c * $l) - 3 ) ) . '...';
+ }
+ }
+ }
+ elsif ( length( $s ) > $c ) {
+ $s = substr( $s, 0, ( $c - 3 ) ) . '...';
+ }
+ return entities($s);
+ } else {
+ return $s ? '...' : '';
+ }
+ },
+ url => sub{
+ my $s = shift; # string
+ $s = reentities($s);
+ $s =~ s/([^a-z0-9A-Z])/sprintf('%%%X', ord($1))/seg;
+ return $s;
+ },
+
+ # translate string, usage : gettext(foo,truncate) or gettext(foo)
+ # value for truncate are optional
+ gettext => sub{
+ my $t = gettext($_[0]);
+ $t = substr($t,0,$_[1]) . "..."
+ if(defined $_[1] && length($t)>$_[1]);
+ return entities($t);
+ },
+ version => sub{ return main::getVersion },
+ loadfile => sub{ return load_file(@_) },
+ writefile => sub{
+ my $filename = shift || return error('No Filename to write');
+ my $data = shift || return error('Nothing data to write');
+
+ my $dir = $u->userTmp;
+
+ # absolut Path to file
+ my $file = sprintf('%s/%s', $dir, $filename);
+ # absolut Path to file
+ if(save_file($file, $data)) {
+ # return the relative Path
+ my ($relpath) = $file =~ '/(.+?/.+?)$';
+ return sprintf('tempimages/%s', $filename);
+ }
+ },
+ fmttime => sub{ return fmttime(@_) },
+ bench => \&bench,
+ llog => sub{
+ my $lines = shift || 10;
+ my $lmod = main::getModule('LOGREAD');
+ return $lmod->tail($obj->{paths}->{LOGFILE}, $lines);
+ },
+ getModule => sub{
+ return main::getModule(shift);
+ },
+ };
+
+ $t->process($widget, $vars, \$output)
+ or return error($t->error());
+
+ return $output;
+}
+
+# ------------------
+sub out {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $text = shift || 'no Text for Output';
+ my $type = shift || 'text/html';
+ my %args = @_;
+
+ unless(defined $obj->{header}) {
+ # HTTP Header
+ $obj->{output_header} = $obj->header($type, \%args);
+ }
+
+ $obj->{output} .= $text,"\r\n"
+ if($text);
+}
+
+# ------------------
+sub printout {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $nopack = shift || $obj->{nopack} || 0;
+
+ if($obj->{output} && $obj->{handle}) {
+ my $content = $obj->{output};
+
+ $content = Compress::Zlib::memGzip($content)
+ if(! $nopack and $obj->{Zlib} and $obj->{browser}->{accept_gzip});
+
+ $obj->{handle}->print($obj->{output_header}, $content);
+ $obj->{sendbytes}+= length($obj->{output_header});
+ $obj->{sendbytes}+= length($content);
+ $obj->{handle}->close();
+ }
+ undef $obj->{output};
+ undef $obj->{output_header};
+ undef $obj->{nopack};
+ undef $obj->{hasentities};
+ undef $obj->{dontparsedData};
+}
+
+# ------------------
+sub getType {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $typ = shift || 'text/html';
+
+ my $typefile = sprintf('%s/%s', $obj->{htmdir}, 'GENERICTYP');
+ if(-e $typefile and -r $typefile) {
+ $typ = load_file($typefile);
+ $typ =~ s/[\r|\n]//sig;
+ }
+ return $typ;
+}
+
+# ------------------
+sub header {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $typ = $obj->getType(shift) || return error ('No Type!' );
+ my $arg = shift || {};
+
+ $arg->{'Content-encoding'} = 'gzip'
+ if($obj->{browser}->{accept_gzip} && ((!defined $obj->{nopack}) || $obj->{nopack} == 0) );
+
+ if(defined $obj->{nocache} && $obj->{nocache}) {
+ $arg->{'Cache-Control'} = 'no-cache, must-revalidate' if(!defined $arg->{'Cache-Control'});
+ $arg->{'Pragma'} = 'no-cache' if(!defined $arg->{'Pragma'});
+ }
+
+ $obj->{header} = 200;
+ return $obj->{cgi}->header(
+ -type => $typ,
+ -status => "200 OK",
+ -expires => ($typ =~ 'text/html' || (defined $obj->{nocache} && $obj->{nocache})) ? "now" : "+7d",
+ %{$arg},
+ );
+}
+
+# ------------------
+sub statusmsg {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $state = shift || return error ('No Status!');
+ my $msg = shift;
+ my $title = shift;
+
+ unless(defined $obj->{header}) {
+ $obj->{nopack} = 1;
+
+ my $s = {
+ 200 => '200 OK',
+ 204 => '204 No Response',
+ 301 => '301 Moved Permanently',
+ 302 => '302 Found',
+ 303 => '303 See Other',
+ 304 => '304 Not Modified',
+ 307 => '307 Temporary Redirect',
+ 400 => '400 Bad Request',
+ 401 => '401 Unauthorized',
+ 403 => '403 Forbidden',
+ 403 => '404 Not Found',
+ 405 => '405 Not Allowed',
+ 408 => '408 Request Timed Out',
+ 500 => '500 Internal Server Error',
+ 503 => '503 Service Unavailable',
+ 504 => '504 Gateway Timed Out',
+ };
+ my $status = $s->{200};
+ $status = $s->{$state}
+ if(exists $s->{$state});
+
+ my $arg = {};
+ $arg->{'WWW-Authenticate'} = "Basic realm=\"xxvd\""
+ if($state == 401);
+
+ $arg->{'expires'} = "now"
+ if($state != 304);
+
+ $obj->{header} = $state;
+ $obj->{output_header} = $obj->{cgi}->header(
+ -type => 'text/html',
+ -status => $status,
+ %{$arg},
+ );
+ }
+ if($msg && $title) {
+ $obj->{output} = $obj->{cgi}->start_html(-title => $title)
+ . $obj->{cgi}->h1($title)
+ . $obj->{cgi}->p($msg)
+ . $obj->{cgi}->end_html();
+ } else {
+ $obj->{output} = '\r\n';
+ }
+}
+
+# ------------------
+# Send HTTP Status 401 (Authorization Required)
+sub login {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $msg = shift || '';
+
+ $obj->statusmsg(401,$msg,gettext("Authorization required"));
+}
+
+# ------------------
+# Send HTTP Status 403 (Access Forbidden)
+sub status403 {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $msg = shift || '';
+
+ $obj->statusmsg(403,$msg,gettext("Forbidden"));
+}
+
+
+# ------------------
+# Send HTTP Status 404 (File not found)
+sub status404 {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $file = shift || return error ('No File!');
+ my $why = shift || "";
+
+ $file =~ s/$obj->{htmdir}\///g; # Don't post html root, avoid spy out
+
+ $obj->statusmsg(404,sprintf(gettext("Can't open file '%s' : %s"),$file,$why),
+ gettext("Not found"));
+}
+
+# ------------------
+sub question {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $titel = shift || 'undef';
+ my $questions = shift || return error ('No Data!' );
+ my $erg = shift || 0;
+
+ my $q = $obj->{cgi};
+ my $quest;
+
+ # Check Data
+ if(ref $erg eq 'HASH' and ref $questions eq 'ARRAY' and exists $erg->{action}) {
+ my $error;
+ @$quest = @$questions;
+ while (my ($name, $data) = splice(@$quest, 0, 2)) {
+
+ $data->{typ} = 'string'
+ unless($data->{typ});
+
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $erg->{$name});
+
+ # Check Callback
+ if(exists $data->{check} and ref $data->{check} eq 'CODE' and not $error) {
+ ($erg->{$name}, $error) = $data->{check}($erg->{$name}, $data, $erg);
+ }
+
+ # Check on directory
+ if($data->{typ} eq 'dir' and $data->{required} and not -d $erg->{$name}) {
+ ($erg->{$name}, $error) = (undef, sprintf(gettext("Directory '%s' is doesn't exist!"), $erg->{$name}));
+ }
+
+ # Check on file
+ if($data->{typ} eq 'file' and $data->{required} and not -e $erg->{$name}) {
+ ($erg->{$name}, $error) = (undef, sprintf(gettext("File '%s' is doesn't exist!"), $erg->{$name}));
+ }
+
+ # Check on password (is not set the take the old password)
+ if($data->{typ} eq 'password' and not $erg->{$name}) {
+ $erg->{$name} = $data->{def};
+ }
+
+ if($error) {
+ $obj->err(sprintf(gettext("Error at field '%s' (%s) : %s"), $data->{msg}, $name, $error));
+ last;
+ }
+ }
+ unless($error) {
+ delete $erg->{action};
+ return $erg;
+ }
+ }
+
+ $obj->formStart($titel);
+ if(ref $questions eq 'ARRAY') {
+ my $q = $obj->{cgi};
+ @$quest = @$questions;
+ my $c=0;
+ while (my ($name, $data) = splice(@$quest, 0, 2)) {
+ my $type = delete $data->{typ};
+ my $params = delete $data->{param};
+ $params->{count} = $c++;
+ $data->{msg} =~ s/\n/<br \/>/sig if($data->{msg});
+ $data->{NAME} = '__'.$name;
+ $type ||= 'string';
+ $obj->$type($data, $params);
+ }
+ } else {
+ my $type = delete $questions->{typ};
+ $questions->{NAME} = '__'.$type;
+ $type ||= 'string';
+ $obj->$type($questions);
+ }
+ $obj->formEnd;
+ return undef;
+}
+
+# ------------------
+sub wait {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $msg = shift || gettext("Please wait ...");
+ my $min = shift || 0;
+ my $max = shift || 0;
+ my $screen = shift || 0;
+
+ my $http_useragent = $obj->{browser}->{http_useragent};
+ if(grep(/Mozilla/i,$http_useragent) == 0 # Only Mozilla compatible browser support server push
+ || grep(/MSIE/i,$http_useragent) > 0 # Stopp her for Browser e.g. Internet Explorer
+ || grep(/Opera/i,$http_useragent) > 0 # Stopp her for Browser e.g. Opera
+ || grep(/KHTML/i,$http_useragent) > 0) # like Safari,Konqueror
+ {
+ lg sprintf('Sorry, only Mozilla compatible browser support server push, this browser was identify by "%s"',
+ $http_useragent );
+ return 0;
+ }
+ $obj->{nopack} = 1;
+ $obj->{header} = 200;
+ my $waiter = XXV::OUTPUT::HTML::WAIT->new(
+ -cgi => $obj->{cgi},
+ -handle => $obj->{handle},
+ -callback => sub{
+ my ($min, $max, $cur, $steps, $nextmessage, $eta) = @_;
+ my $out = $obj->parseTemplate(
+ 'wait',
+ {
+ msg => $nextmessage || $msg,
+ minimum => $min,
+ current => $cur,
+ maximum => $max,
+ steps => $steps,
+ eta => $eta
+ },
+ );
+ return $out;
+ },
+ );
+
+ if($max) {
+ $waiter->min($min); # Min Value for process Bar
+ $waiter->max($max); # Max Value for process Bar
+ $waiter->screen($screen); # Every call of next will redraw the process bar
+
+ }
+ $waiter->next(1);
+
+ return $waiter;
+}
+
+# ------------------
+sub datei {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $file = shift || return error ('No File!');
+ my $typ = shift;
+
+ my %args = ();
+
+ return $obj->status404($file,$!)
+ if(!-r $file);
+
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks) = stat $file;
+ return $obj->status404($file,$!)
+ if(!$blocks);
+
+ # header only if caching
+ $args{'ETag'} = sprintf('%x-%x-%x',$ino, $size, $mtime);
+ return $obj->statusmsg(304)
+ if($obj->{browser}->{'Match'}
+ && $args{'ETag'} eq $obj->{browser}->{'Match'});
+
+ $typ = $obj->{mime}->{lc((split('\.', $file))[-1])}
+ if(!$typ);
+ $typ = "application/octet-stream"
+ if(!$typ);
+
+ $obj->{nopack} = 1
+ if($typ =~ /image\// || $typ =~ /video\//);
+
+ my(@MON)=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
+ my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;
+ my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($mtime);
+ $args{'Last-Modified'} = sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
+ $WDAY[$wday],$mday,$MON[$mon],$year + 1900,$hour,$min,$sec);
+ $args{'attachment'} = basename($file);
+ $args{'Content-Length'} = $size
+ if($obj->{nopack});
+
+ if($size > (32768 * 16)) { ## Only files bigger then 512k
+
+ lg sprintf("stream file : '%s' (%s)",$file,convert($size));
+
+ $obj->{nopack} = 1;
+ my $handle = $obj->{handle};
+
+ my $child = fork();
+ if ($child < 0) {
+ error("Can't create process for streaming : " . $!);
+ return $obj->status404($file,$!);
+ }
+ elsif ($child > 0) {
+ $obj->{sendbytes} += $size;
+ }
+ elsif ($child == 0) {
+
+ eval
+ {
+ local $SIG{'__DIE__'};
+
+ my $hdr = $obj->header($typ, \%args);
+
+ my $r = 0;
+ if(sysopen( FH, $file, O_RDONLY|O_BINARY )) {
+ $handle->print($hdr);
+
+ my $bytes;
+ my $data;
+ do {
+ $bytes = sysread( FH, $data, 4096 );
+ if($bytes) {
+ $r = $handle->send($data);
+ }
+ } while $r && $bytes > 0;
+ close(FH);
+ } else {
+ error sprintf("I can't open file '%s' : %s", $file,$!);
+ }
+ $handle->close();
+ };
+ error($@) if $@;
+ exit 0;
+ }
+
+ undef $obj->{handle};
+ undef $obj->{output};
+ } else {
+
+ my $data = load_file($file)
+ or return $obj->status404($file,$!);
+ # send data
+ $obj->out($data, $typ, %args );
+ }
+}
+
+# ------------------
+sub image {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $file = shift || return error ('No File!');
+ my $typ = shift;
+ return $obj->datei($file,$typ);
+}
+
+# ------------------
+sub pod {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $modname = uc(shift) || return error ('No Modname!' );
+ $modname = ucfirst($modname) if($modname eq 'GENERAL');
+
+ my $podfile = sprintf('%s/%s.pod', $obj->{paths}->{PODPATH}, $modname);
+ return $obj->err(gettext('Module %s not found!'), $modname)
+ unless(-r $podfile);
+
+ my $u = main::getModule('USER');
+ my $tmpdir = $u->userTmp;
+ my $outfile = sprintf('%s/%s_%d.pod', $tmpdir, $modname, time);
+
+ pod2html(
+ "--cachedir=$tmpdir",
+ "--infile=$podfile",
+ "--outfile=$outfile",
+ );
+ return error('Problem to convert pod2html')
+ unless(-r $outfile);
+
+ my $html = load_file($outfile);
+ $html = $1 if($html =~ /\<body.*?\>(.+?)\<\/body\>/si);
+ $obj->link({
+ text => gettext("Back to configuration screen"),
+ url => $obj->{browser}->{Referer},
+ });
+
+ $obj->message($html);
+}
+
+# ------------------
+sub txtfile {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $filename = shift || return error ('No TxtFile to display!' );
+ my $param = shift || {};
+
+ my $txtfile = sprintf('%s/%s.txt', $obj->{paths}->{DOCPATH}, $filename);
+ my $gzfile = sprintf('%s/%s.txt.gz', $obj->{paths}->{DOCPATH}, $filename);
+
+ $txtfile = main::getModule('HTTPD')->unzip($gzfile)
+ if(! -r $txtfile and -r $gzfile);
+
+ my $topic = gettext("File");
+
+ if($param->{'format'} eq 'txt') {
+ my $txt = load_file($txtfile);
+ return $obj->message($txt, {tags => {first => "$topic: $filename.txt"}});
+ }
+
+ my $u = main::getModule('USER');
+ my $htmlfile = sprintf('%s/temp_txt.html', $u->userTmp);
+
+ $obj->{txt2html}->txt2html(
+ infile=>[$txtfile],
+ outfile=>$htmlfile,
+ title=> $filename,
+ mail=>1,
+ );
+ my $html = load_file($htmlfile);
+ $html = $1 if($html =~ /\<body.*?\>(.+?)\<\/body\>/si);
+ $obj->message($html, {tags => {first => "<h1>$topic: $filename.txt</h1>"}});
+}
+
+# ------------------
+sub typ {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{TYP};
+}
+
+# ------------------
+sub setCall {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Name!' );
+
+ $obj->{call} = $name;
+ return $obj->{call};
+}
+
+# ------------------
+sub browser {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{browser};
+}
+
+# Special Version from Message (with error handling)
+# ------------------
+sub msg {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || 0;
+ my $err = shift || 0;
+
+ unless($err) {
+ $obj->message($data);
+ } else {
+ $obj->err($data || $err);
+ return undef;
+ }
+}
+
+# ------------------
+sub parseData {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $dta = shift || return '';
+
+ if(ref $dta eq 'HASH') {
+ foreach my $name (keys %$dta) {
+ if(ref $dta->{$name}) {
+ $obj->parseData($dta->{$name});
+ } else {
+ $dta->{$name} = reentities($dta->{$name}) if($obj->{hasentities});
+ $dta->{$name} = entities($dta->{$name});
+ }
+ }
+ } elsif (ref $dta eq 'ARRAY') {
+ foreach (@$dta) {
+ if(ref $_) {
+ $obj->parseData($_);
+ } else {
+ $_ = reentities($_) if($obj->{hasentities});
+ $_ = entities($_);
+ }
+ }
+ }
+ $obj->{hasentities} = 1;
+ return $dta;
+}
+
+
+1;
diff --git a/lib/XXV/OUTPUT/NEWS/JABBER.pm b/lib/XXV/OUTPUT/NEWS/JABBER.pm
new file mode 100644
index 0000000..119f4c5
--- /dev/null
+++ b/lib/XXV/OUTPUT/NEWS/JABBER.pm
@@ -0,0 +1,296 @@
+package XXV::OUTPUT::NEWS::JABBER;
+use strict;
+
+use Tools;
+use POSIX qw(locale_h);
+use Locale::gettext;
+
+# News Modules have only three methods
+# init - for intervall or others
+# send - send the informations
+# read - read the news and parse it
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'NEWS::JABBER',
+ Prereq => {
+ 'Net::XMPP' => 'Jabber protocol for connect and send',
+ },
+ Description => gettext(qq|
+This NEWS module generate a Jabber messages for your jabber client.
+If come a Message from xxv with a lever >= as Preferences::level then
+will this module send this Message to your jabber account
+(Preferences::receiveUser).
+
+The Problem xxv need a extra jabber account to allow to send messages in
+the jabber network. This is very simple:
+
+=over 4
+
+=item 1 Start your jabber client, may the exodus (http://exodus.jabberstudio.org/)
+
+=item 2 Create a new Profile with the name 'xxv'
+
+=item 3 In the next window input following things:
+
+ - Jabber Id: newsxxv\@jabber.org (in Example!)
+ - Password: lalala (in Example!)
+ - save Password: yes
+ - new Account?: yes
+
+=back
+
+Thats all!
+
+If you want, you can test the connection to send a testmessage with
+the following url in the Webinterface:
+
+ http://vdr:8080/?cmd=request&data=jabber
+
+or Telnet Interface:
+
+ XXV> request jabber
+
+Then you must receive a message in your running jabber client.
+
+|),
+ Version => '0.01',
+ Date => '31.09.2005',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'n',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = shift;
+ my $erg = $obj->init
+ or return error('Problem to initialize news module')
+ if($value eq 'y' and not exists $obj->{JCON});
+ return $value;
+ },
+ },
+ level => {
+ description => gettext('Minimum level of the messages which can be displayed (1 ... 100)'),
+ default => 1,
+ type => 'integer',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = int(shift) || 0;
+ unless($value >= 1 and $value <= 100) {
+ return undef, 'Sorry, but the value must be between 1 and 100';
+ }
+ return $value;
+ },
+ },
+ receiveUser => {
+ description => gettext('User to advise (as Jabberaccount to@jabber.server.org)'),
+ default => '',
+ type => 'string',
+ required => gettext('This is required!'),
+ },
+ user => {
+ description => gettext('Jabberaccount to send message (from@jabber.server.org)'),
+ default => '',
+ type => 'string',
+ required => gettext('This is required!'),
+ },
+ passwd => {
+ description => gettext('Password from Jabberaccount'),
+ default => '',
+ type => 'password',
+ required => gettext('This is required!'),
+ check => sub{
+ my $value = shift || return;
+
+ return $value unless(ref $value eq 'ARRAY');
+
+ # If no password given the take the old password as default
+ if($value->[0] and $value->[0] ne $value->[1]) {
+ return undef, gettext("Field with 1st and 2nd password must be equal to confirm!");
+ } else {
+ return $value->[0];
+ }
+ },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # host
+ $self->{host} = delete $attr{'-host'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_} || $self->{MOD}->{Preferences}->{$_}->{default}
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{TYP} = 'text/plain';
+
+ # Initiat after load modules ...
+ main::after(sub{
+ # The Initprocess
+ my $erg = $self->init
+ or return error('Problem to initialize news module');
+ }, "NEWS::JABBER: Start initiate the Jabber module ...")
+ if($self->{active} eq 'y');
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $url = sprintf("http://%s:%s/", $obj->{host}, main::getModule('HTTPD')->{Port});
+
+ 1;
+}
+
+# ------------------
+sub jconnect {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ my $jcon = Net::XMPP::Client->new(
+ debuglevel => 0,
+ ) || return error('Problem to create an Jabber Client');
+
+ my ($user, $server) = split('\@', $obj->{user});
+
+ debug ("Connecting to jabber server: %s ...", $server);
+
+ my @res = $jcon->Connect(
+ hostname => $server,
+ );
+ return
+ unless($obj->xmpp_check_result("Connect",\@res,$jcon));
+
+ debug ("Authentificat with User:%s ...", $user);
+
+ @res = $jcon->AuthSend(
+ 'hostname'=>$server,
+ 'username'=>$user,
+ 'password'=>$obj->{passwd},
+ 'resource'=>'xxv'
+ );
+
+ return $jcon
+ if($obj->xmpp_check_result("Login",\@res,$jcon));
+}
+
+# ------------------
+sub jdisconnect {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $cnx = shift || 0;
+
+ $cnx->Disconnect()
+ if(ref $cnx);
+
+ 1;
+}
+
+
+# ------------------
+sub send {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No Vars!' );
+
+ return undef, lg('This function is deactivated!')
+ if($obj->{active} ne 'y');
+
+ my $cnx = $obj->jconnect()
+ || return error ('No connected JabberClient!' );
+
+ $cnx->MessageSend(
+ 'to' => $obj->{receiveUser},
+ 'subject'=> $vars->{Title},
+ 'body' => ($vars->{Text} || $vars->{Url}),
+ );
+
+ $cnx = $obj->jdisconnect($cnx);
+
+ 1;
+}
+
+# ------------------
+sub read {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No News!' );
+
+ return $obj->send($vars);
+
+ 1;
+}
+
+# ------------------
+sub req {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return gettext('The Module NEWS::JABBER is not active!')
+ if($obj->{active} ne 'y');
+
+ my $vars = {
+ AddDate => time,
+ Title => 'This is a testmessage for NEWS::JABBER ...',
+ Text => "abcdefghijklmnopqrstuvwxyz\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n0123456789\näüöÄÜÖ!@#$%^&*()_+=-':;<>?/\n",
+ Level => 100,
+ };
+
+ if($obj->send($vars)) {
+ return sprintf('Message is send to %s at %s', $obj->{receiveUser}, datum($vars->{AddDate}, 'voll'));
+ } else {
+ return sprintf('Upps, problem send Message to %s at %s', $obj->{receiveUser}, datum($vars->{AddDate}, 'voll'));
+ }
+}
+
+# ------------------
+sub xmpp_check_result {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my ($txt,$res,$cnx)=@_;
+
+ return error("Error '$txt': result undefined")
+ unless($res);
+
+ # result can be true or 'ok'
+ if ((@$res == 1 && $$res[0]) || $$res[0] eq 'ok') {
+ return debug "%s: %s", $txt, $$res[0];
+ # otherwise, there is some error
+ } else {
+ my $errmsg = $cnx->GetErrorCode() || '?';
+ $cnx->Disconnect();
+ return error("Error %s: %s [%s]", $txt, join (': ',@$res), $errmsg);
+ }
+}
+
+1;
diff --git a/lib/XXV/OUTPUT/NEWS/MAIL.pm b/lib/XXV/OUTPUT/NEWS/MAIL.pm
new file mode 100644
index 0000000..5f91e05
--- /dev/null
+++ b/lib/XXV/OUTPUT/NEWS/MAIL.pm
@@ -0,0 +1,313 @@
+package XXV::OUTPUT::NEWS::MAIL;
+use strict;
+
+use Tools;
+use POSIX qw(locale_h);
+use Locale::gettext;
+
+# News Modules have only this methods
+# init - for intervall or others
+# send - send the informations
+# read - read the news and parse it
+# req - read the actual news print this out
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'NEWS::MAIL',
+ Prereq => {
+ 'Mail::SendEasy' => 'Simple platform independent mailer',
+ },
+ Description => gettext('This NEWS module generate mails for news.'),
+ Version => '0.01',
+ Date => '31.09.2005',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'n',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = shift;
+ my $erg = $obj->init
+ or return error('Problem to initialize news module')
+ if($value eq 'y' and not exists $obj->{INITE});
+ return $value;
+ },
+ },
+ level => {
+ description => gettext('Minimum level of the messages which can be displayed (1 ... 100)'),
+ default => 1,
+ type => 'integer',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = int(shift) || 0;
+ unless($value >= 1 and $value <= 100) {
+ return undef, 'Sorry, but the value must be between 1 and 100';
+ }
+ return $value;
+ },
+ },
+ interval => {
+ description => gettext('Time in hours to send the next mail'),
+ default => 12,
+ type => 'integer',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = int(shift) || 0;
+ if($value and ref $obj->{INTERVAL}) {
+ my $newinterval = $value*3600;
+ $obj->{INTERVAL}->interval($newinterval);
+ }
+ return $value;
+ },
+ },
+ address => {
+ description => gettext('One or more mail addresses for sending the news'),
+ default => 'unknown@example.com, everybody@example.com',
+ type => 'string',
+ required => gettext('This is required!'),
+ },
+ from_address => {
+ description => gettext('Mail address to senders describe.'),
+ default => 'xxv@vdr.de',
+ type => 'string',
+ },
+ smtp => {
+ description => gettext('Hostname from SMTP mail server'),
+ default => main::getModule('STATUS')->name,
+ type => 'host',
+ required => gettext('This is required!'),
+ },
+ susr => {
+ description => gettext('Username for mail server access'),
+ default => 'xxv',
+ type => 'string',
+ },
+ spwd => {
+ description => gettext('Password for mail server access'),
+ default => 'xxv',
+ type => 'password',
+ check => sub{
+ my $value = shift || return;
+
+ return $value unless(ref $value eq 'ARRAY');
+
+ # If no password given the take the old password as default
+ if($value->[0] and $value->[0] ne $value->[1]) {
+ return undef, gettext("Field with 1st and 2nd password must be equal to confirm!");
+ } else {
+ return $value->[0];
+ }
+ },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # host
+ $self->{host} = delete $attr{'-host'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_} || $self->{MOD}->{Preferences}->{$_}->{default}
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # create Template object
+ $self->{tt} = Template->new(
+ START_TAG => '\<\?\%', # Tagstyle
+ END_TAG => '\%\?\>', # Tagstyle
+ INTERPOLATE => 1, # expand "$var" in plain text
+ PRE_CHOMP => 1, # cleanup whitespace
+ EVAL_PERL => 1, # evaluate Perl code blocks
+ ABSOLUTE => 1,
+ );
+
+ my @tmplfiles = glob(
+ sprintf('%s/%s_*.tmpl',
+ $self->{paths}->{NEWSTMPL},
+ lc((split('::', $self->{MOD}->{Name}))[-1])
+ )
+ );
+ for (@tmplfiles) {
+ my ($order, $typ) = $_ =~ /_(\d+)_(\S+)\.tmpl$/si;
+ $self->{TEMPLATES}->{$typ} = $_;
+ }
+
+ # The Initprocess
+ my $erg = $self->init
+ or return error('Problem to initialize news module')
+ if($self->{active} eq 'y');
+
+ $self->{TYP} = 'text/plain';
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{INITE} = 1;
+
+ $obj->{LastReportTime} = time;
+
+ # Interval to send the next mail
+ $obj->{INTERVAL} = Event->timer(
+ interval => $obj->{interval}*3600,
+ prio => 6, # -1 very hard ... 6 very low
+ cb => sub{
+ $obj->send();
+ },
+ );
+
+ $obj->{COUNT} = 1;
+
+ 1;
+}
+
+# ------------------
+sub send {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+
+ return error('This function is deactivated!')
+ if($obj->{active} ne 'y');
+
+ ++$obj->{COUNT};
+
+ my $content = $obj->req();
+
+ my $smod = main::getModule('STATUS');
+ my @addresses = split(/\s*,\s*/, $obj->{address});
+
+ # Send mail
+ my $status = Mail::SendEasy::send(
+ smtp => $obj->{smtp},
+ user => $obj->{susr},
+ pass => $obj->{spwd},
+ from => $obj->{from_address},
+ from_title => 'XXV MailNewsAgent',
+ to => shift @addresses ,
+ cc => join(',', @addresses),
+ subject => "News from your XXV System!" ,
+ msg => $content,
+ msgid => $obj->{COUNT},
+ ) || return error('Problem to send Mail: %s', $Mail::SendEasy::ER);
+
+ $obj->{LastReportTime} = time;
+
+ lg sprintf('News Mail with nr. %d successfully send at %s', $obj->{COUNT}, scalar localtime);
+ $obj->{NEWSLETTER} = undef;
+ 1;
+}
+
+# ------------------
+sub parseHeader {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $output = '';
+
+ my $vars = {
+ msgnr => $obj->{COUNT},
+ date => datum(time, 'voll'),
+ anzahl=> $obj->{NEWSCOUNT},
+ };
+
+ my $template = $obj->{TEMPLATES}->{'header'};
+ $obj->{tt}->process($template, $vars, \$output)
+ or return error($obj->{tt}->error());
+
+ return $output;
+}
+
+# ------------------
+sub parseFooter {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $output = '';
+
+
+ my $vars = {
+ usage => main::getModule('RECORDS')->{CapacityMessage},
+ uptime => main::getModule('STATUS')->uptime,
+ lastreport => datum($obj->{LastReportTime}, 'voll'),
+ };
+
+ my $template = $obj->{TEMPLATES}->{'footer'};
+ $obj->{tt}->process($template, $vars, \$output)
+ or return error($obj->{tt}->error());
+
+ return $output;
+}
+
+
+# ------------------
+sub read {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No News!' );
+
+ my $output = '';
+ $vars->{count} = ++$obj->{NEWSCOUNT};
+ $vars->{host} = $obj->{host};
+ $vars->{port} = main::getModule('HTTPD')->{Port};
+
+ my $template = $obj->{TEMPLATES}->{'content'};
+ $obj->{tt}->process($template, $vars, \$output)
+ or return error($obj->{tt}->error());
+
+ $obj->{NEWSLETTER} .= $output;
+
+ return $output;
+}
+
+# ------------------
+sub req {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $test = shift || 0;
+
+ return gettext('The Module NEWS::Mail is not active!')
+ if($obj->{active} ne 'y');
+
+ my $content = '';
+ if($test) {
+ $obj->send;
+ $content .= gettext('A mail with the following content is send to your Mailaccount!');
+ $content .= "\n\n";
+ }
+
+ $content .= $obj->parseHeader();
+ $content .= $obj->{NEWSLETTER};
+ $content .= $obj->parseFooter();
+
+ return $content;
+}
+
+
+1;
diff --git a/lib/XXV/OUTPUT/NEWS/RSS.pm b/lib/XXV/OUTPUT/NEWS/RSS.pm
new file mode 100644
index 0000000..82cdbd4
--- /dev/null
+++ b/lib/XXV/OUTPUT/NEWS/RSS.pm
@@ -0,0 +1,233 @@
+package XXV::OUTPUT::NEWS::RSS;
+use strict;
+
+use Tools;
+use POSIX qw(locale_h);
+use Locale::gettext;
+
+# News Modules have only three methods
+# init - for intervall or others
+# send - send the informations
+# read - read the news and parse it
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'NEWS::RSS',
+ Prereq => {
+ 'XML::RSS' => 'SMTP Protocol module to connect and send emails',
+ },
+ Description => gettext('This NEWS module generate an RSS Newsfeed for your rss reader.'),
+ Version => '0.01',
+ Date => '31.09.2005',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = shift;
+ my $erg = $obj->init
+ or return error('Problem to initialize news module')
+ if($value eq 'y' and not exists $obj->{INITE});
+ return $value;
+ },
+ },
+ level => {
+ description => gettext('Minimum level of the messages which can be displayed (1 ... 100)'),
+ default => 1,
+ type => 'integer',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = int(shift) || 0;
+ unless($value >= 1 and $value <= 100) {
+ return undef, 'Sorry, but the value must be between 1 and 100';
+ }
+ return $value;
+ },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # host
+ $self->{host} = delete $attr{'-host'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_} || $self->{MOD}->{Preferences}->{$_}->{default}
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{TYP} = 'application/xhtml+xml';
+
+ # Initiat after load modules ...
+ main::after(sub{
+ # The Initprocess
+ my $erg = $self->init
+ or return error('Problem to initialize News Module');
+ }, "NEWS::RSS: Start initiate the RSS Feed ...")
+ if($self->{active} eq 'y');
+
+
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ $obj->{INITE} = 1;
+
+ 1;
+}
+
+# ------------------
+sub createRSS {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $ver = shift || 1;
+ my $account = sprintf("%s@%s", $ENV{USER}, main::getModule('STATUS')->name);
+ my $url = sprintf("http://%s:%s/", $obj->{host}, main::getModule('HTTPD')->{Port});
+
+ my $rss;
+ if($ver == 1) {
+ $rss = XML::RSS->new(
+ version => '1.0',
+ ) || return error('Problem to create an RSS Object');
+
+
+ $rss->channel(
+ title => gettext("XXV RSS 1.0"),
+ 'link' => $url,
+ description => gettext("Important messages from your vdr/xxv"),
+ dc => {
+ date => datum(time,'int'),
+ subject => gettext("XXV Messages"),
+ creator => $account,
+ language => setlocale(POSIX::LC_MESSAGES),
+ },
+ syn => {
+ updatePeriod => "hourly",
+ updateFrequency => "1",
+ updateBase => datum(time, 'int'),
+ },
+ );
+
+ } elsif($ver == 2) {
+ my $lastbuild = (exists $obj->{lastBuildDate} ? $obj->{lastBuildDate} : time);
+
+ $rss = XML::RSS->new(
+ version => '2.0',
+ ) || return error('Problem to create an RSS Object');
+
+ $rss->channel(
+ title => gettext("XXV RSS 2.0"),
+ 'link' => $url,
+ description => gettext("Important messages from your vdr/xxv"),
+ language => setlocale(POSIX::LC_MESSAGES),
+ pubDate => datum(time, 'rss'),
+ lastBuildDate => datum($lastbuild, 'rss'),
+ managingEditor => $account,
+ );
+ }
+ $obj->{lastBuildDate} = time;
+
+ return $rss;
+}
+
+
+# ------------------
+sub send {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No Vars!' );
+
+ ++$obj->{COUNT};
+
+ push(@{$obj->{STACK}}, [
+ entities($vars->{Title}),
+ entities($vars->{Url}),
+ entities($vars->{Text}),
+ datum($vars->{AddDate},'int'),
+ $vars->{LevelName},
+ ]);
+
+ lg sprintf('News RSS with nr. %d successfully send at %s', $obj->{COUNT}, scalar localtime);
+ 1;
+}
+
+# ------------------
+sub read {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No News!' );
+
+ return undef, lg('This function is deactivated!')
+ if($obj->{active} ne 'y');
+
+
+ $vars->{count} = ++$obj->{NEWSCOUNT};
+ $vars->{host} = $obj->{host};
+ $vars->{port} = main::getModule('HTTPD')->{Port};
+
+ $obj->send($vars);
+
+ return 1;
+}
+
+# ------------------
+sub req {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $params = shift || {};
+
+ return gettext('The Module NEWS::RSS is not active!')
+ if($obj->{active} ne 'y');
+
+ my $rss = $obj->createRSS($params->{version})
+ || return error('Problem to create a RSS Object!');
+
+ foreach my $entry (@{$obj->{STACK}}) {
+ my ($title, $link, $descr, $adddate, $level) = @{$entry};
+ $rss->add_item(
+ title => $title,
+ link => $link,
+ description => $descr,
+ dc => {
+ date => $adddate,
+ subject => $level
+ },
+ );
+ }
+
+ return $rss->as_string;
+}
+
+
+1;
diff --git a/lib/XXV/OUTPUT/NEWS/VDR.pm b/lib/XXV/OUTPUT/NEWS/VDR.pm
new file mode 100644
index 0000000..9f56793
--- /dev/null
+++ b/lib/XXV/OUTPUT/NEWS/VDR.pm
@@ -0,0 +1,165 @@
+package XXV::OUTPUT::NEWS::VDR;
+use strict;
+
+use Tools;
+use POSIX qw(locale_h);
+use Locale::gettext;
+
+# News Modules have only three methods
+# init - for intervall or others
+# send - send the informations
+# read - read the news and parse it
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'NEWS::VDR',
+ Description => gettext('This NEWS module generate a messages for vdr interface.'),
+ Version => '0.01',
+ Date => '31.09.2005',
+ Author => 'xpix',
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'n',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = shift;
+ my $erg = $obj->init
+ or return error('Problem to initialize news module')
+ if($value eq 'y' and not exists $obj->{INITE});
+ return $value;
+ },
+ },
+ level => {
+ description => gettext('Minimum level of the messages which can be displayed (1 ... 100)'),
+ default => 1,
+ type => 'integer',
+ required => gettext('This is required!'),
+ check => sub {
+ my $value = int(shift) || 0;
+ unless($value >= 1 and $value <= 100) {
+ return undef, 'Sorry, but the value must be between 1 and 100';
+ }
+ return $value;
+ },
+ },
+ },
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # paths
+ $self->{paths} = delete $attr{'-paths'};
+
+ # host
+ $self->{host} = delete $attr{'-host'};
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # all configvalues to $self without parents (important for ConfigModule)
+ map {
+ $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_} || $self->{MOD}->{Preferences}->{$_}->{default}
+ } keys %{$self->{MOD}->{Preferences}};
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{TYP} = 'text/plain';
+
+ # Initiat after load modules ...
+ main::after(sub{
+ # The Initprocess
+ my $erg = $self->init
+ or return error('Problem to initialize news module');
+ }, "NEWS::VDR: Start initiate the News vdr module ...")
+ if($self->{active} eq 'y');
+
+ return $self;
+}
+
+# ------------------
+sub init {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $url = sprintf("http://%s:%s/", $obj->{host}, main::getModule('HTTPD')->{Port});
+ $obj->{INITE} = 1;
+
+ $obj->{SVDRP} = main::getModule('SVDRP');
+
+ 1;
+}
+
+# ------------------
+sub send {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No Vars!' );
+
+ return undef, lg('This function is deactivated!')
+ if($obj->{active} ne 'y');
+
+ return undef, lg('Title is not set!')
+ unless($vars->{Title});
+
+
+ my $cmd = sprintf('MESG %s', $vars->{Title});
+
+ my $svdrp = $obj->{SVDRP} || return error ('No SVDRP!' );
+ $svdrp->command($cmd);
+
+ 1;
+}
+
+# ------------------
+sub read {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $vars = shift || return error ('No News!' );
+
+ return $obj->send($vars);
+
+ 1;
+}
+
+# ------------------
+sub req {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $test = shift || 0;
+
+ return gettext('The Module NEWS::VDR is not active!')
+ if($obj->{active} ne 'y');
+
+ my $vars = {
+ AddDate => time,
+ Title => 'This is only a Test for the xxv news vdr module!',
+ Text => 'This is only a Test for the xxv news vdr module!',
+ Cmd => 'request',
+ Id => 'vdr',
+ Url => sprintf("http://%s:%s/", $obj->{host}, main::getModule('HTTPD')->{Port}),
+ Level => 'harmless',
+ };
+ $obj->read($vars);
+
+ return gettext('A message is send to your SVDRPServer!');
+
+}
+
+
+1;
diff --git a/lib/XXV/OUTPUT/Wml.pm b/lib/XXV/OUTPUT/Wml.pm
new file mode 100644
index 0000000..fc110c2
--- /dev/null
+++ b/lib/XXV/OUTPUT/Wml.pm
@@ -0,0 +1,431 @@
+package XXV::OUTPUT::Wml;
+
+use strict;
+
+use vars qw($AUTOLOAD);
+use Locale::gettext;
+use Tools;
+use File::Path;
+use Pod::Html;
+
+# This module method must exist for XXV
+# ------------------
+sub module {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $args = {
+ Name => 'Wml',
+ Prereq => {
+ 'Template' => 'Front-end module to the Template Toolkit ',
+ },
+ Description => gettext('This receive and send Wap messages.'),
+ Version => '0.01',
+ Date => '27.10.2004',
+ Author => 'xpix',
+ };
+ return $args;
+}
+
+# ------------------
+sub AUTOLOAD {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || {};
+ my $params = shift || 0;
+
+ my $name = (split('::', $AUTOLOAD))[-1];
+ return if($name eq 'DESTROY');
+
+ my $output = $obj->parseTemplate($name, $data, $params);
+
+ $obj->out( $output );
+
+ $obj->{call} = '';
+}
+
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ # who am I
+ $self->{MOD} = $self->module;
+
+ # Try to use the Requirments
+ map {
+ eval "use $_";
+ return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
+ } keys %{$self->{MOD}->{Prereq}};
+
+ $self->{handle} = $attr{'-handle'}
+ || return error('No handle defined!');
+
+ $self->{paths} = $attr{'-paths'}
+ || return error('No Paths defined!');
+
+ $self->{dbh} = $attr{'-dbh'}
+ || return error('No DBH defined!');
+
+ $self->{wmldir} = $attr{'-wmldir'}
+ || return error('No wmldir given!');
+
+ $self->{cgi} = $attr{'-cgi'}
+ || return error('No TemplateDir given!');
+
+ $self->{mime} = $attr{'-mime'}
+ || return error('No Mimehash given!');
+
+ $self->{browser} = $attr{'-browser'}
+ || return error('No Mimehash given!');
+
+# $self->{start} = $attr{'-start'}
+# || return error('No StartPage given!');
+
+ $self->{TYP} = 'WML';
+
+ eval "use Template::Stash::XS";
+ $Template::Config::STASH = 'Template::Stash::XS' unless($@);
+
+ # create Template object
+ $self->{tt} = Template->new(
+ START_TAG => '\<\?\%', # Tagstyle
+ END_TAG => '\%\?\>', # Tagstyle
+ INCLUDE_PATH => $self->{wmldir}, # or list ref
+ INTERPOLATE => 1, # expand "$var" in plain text
+ PRE_CHOMP => 1, # cleanup whitespace
+ EVAL_PERL => 1, # evaluate Perl code blocks
+ );
+
+ return $self;
+}
+
+# ------------------
+sub parseTemplate {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Name!' );
+ my $data = shift || return error ('No Data!' );
+ my $params = shift || {};
+
+ my $t = $obj->{tt};
+ my $u = main::getModule('USER');
+
+ # you can use two templates, first is a user defined template
+ # and second the standard template
+ # i.e. call the htmlhelp command the htmlhelp.tmpl
+ # SpecialTemplate: ./wmlRoot/usage.tmpl
+ # StandardTemplate: ./wmlRoot/widgets/menu.tmpl
+ my $widget_first = sprintf('%s.tmpl', (exists $obj->{call}) ? $obj->{call} : 'nothing');
+ my $widget_second = sprintf('widgets/%s.tmpl', $name);
+ my $widget = (-e sprintf('%s/%s', $obj->{wmldir}, $widget_first) ? $widget_first : $widget_second);
+ my $user = ($u->{active} eq 'y' && $obj->{USER}->{Name} ? $obj->{USER}->{Name} : "nobody" );
+ my $output;
+ my $vars = {
+ cgi => $obj->{cgi},
+ call => $name,
+ data => $data,
+ type => ref $data,
+ info => $obj->browser,
+ param => $params,
+ pid => $$,
+ debug => 1,
+ user => $user,
+ allow => sub{
+ my($cmdobj, $cmdname, $se, $err) = $u->checkCommand($obj, $_[0],"1");
+ return 1 if($cmdobj);
+ },
+ basedir => $obj->{wmldir},
+ entities => sub{ return entities($_[0]) },
+ # translate string, usage : gettext(foo,truncate) or gettext(foo)
+ # value for truncate are optional
+ gettext => sub{
+ my $t = gettext($_[0]);
+ $t = substr($t,0,$_[1]) . "..."
+ if(defined $_[1] && length($t)>$_[1]);
+ return entities($t);
+ },
+ version => sub{ return main::getVersion },
+ loadfile => sub{ return load_file(@_) },
+ writefile => sub{
+ my $filename = shift || return error('No Filename to write');
+ my $data = shift || return error('Nothing data to write');
+
+ my $dir = $u->userTmp;
+
+ # absolut Path to file
+ my $file = sprintf('%s/%s', $dir, $filename);
+ # absolut Path to file
+ if(save_file($file, $data)) {
+ # return the relative Path
+ my ($relpath) = $file =~ '/(.+?/.+?)$';
+ return sprintf('tempimages/%s', $filename);
+ }
+ },
+ };
+ $t->process($widget, $vars, \$output)
+ or return error($t->error());
+
+ return $output;
+}
+
+# ------------------
+sub out {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $text = shift || 'no Text for Output';
+ my $type = shift || 'text/vnd.wap.wml';
+ my %args = @_;
+
+ my $q = $obj->{cgi};
+ unless(defined $obj->{header}) {
+ # HTTP Header
+ $obj->{handle}->print(
+ $obj->header($type, \%args)
+ );
+ }
+
+ $obj->{handle}->print( $text,"\r\n" );
+}
+
+# ------------------
+sub header {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $typ = shift || return error ('No Type!' );
+ my $arg = shift || {};
+
+ $obj->{header} = 1;
+ return $obj->{cgi}->header(
+ -type => $typ,
+ -status => "200 OK",
+ -expires => ($typ =~ 'text/vnd.wap.wml' || (defined $obj->{nocache} && $obj->{nocache})) ? "now" : "+12h",
+ %{$arg},
+ );
+}
+
+# ------------------
+sub statusmsg {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $msg = shift || return error ('No Msg!');
+ my $status = shift || return error ('No Status!');
+
+ unless(defined $obj->{header}) {
+ $obj->{nopack} = 1;
+ $obj->{header} = 1;
+ my $data = $obj->{cgi}->header(
+ -type => 'text/vnd.wap.wml',
+ -status => $status,
+ -expires => "now",
+ );
+ $obj->out($data);
+ }
+
+ my @title = split ('\n', $status);
+ $obj->start(undef,{ title => $title[0] });
+ $obj->err($msg);
+ $obj->footer();
+}
+
+# ------------------
+# Send HTTP Status 401 (Authorization Required)
+sub login {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $msg = shift || '';
+
+ $obj->statusmsg($msg,"401 Authorization Required\nWWW-Authenticate: Basic realm=\"xxvd\"");
+}
+
+# ------------------
+# Send HTTP Status 403 (Access Forbidden)
+sub status403 {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $msg = shift || '';
+
+ $obj->statusmsg($msg,"403 Forbidden");
+}
+
+
+# ------------------
+# Send HTTP Status 404 (File not found)
+sub status404 {
+# ------------------
+ my $obj = shift || return error ('No Object!');
+ my $file = shift || return error ('No File!');
+ my $why = shift || "";
+
+ warn("I can't read file $file");
+
+ $file =~ s/$obj->{wmldir}\///g; # Don't post wml root, avoid spy out
+
+ $obj->statusmsg(sprintf(gettext("Can't open file '%s' : %s"),$file,$why),"404 File not found");
+}
+
+# ------------------
+sub question {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $titel = shift || 'undef';
+ my $questions = shift || return error ('No Data!' );
+ my $erg = shift || 0;
+
+ my $q = $obj->{cgi};
+ my $quest;
+
+ # Check Data
+ if(ref $erg eq 'HASH' and ref $questions eq 'ARRAY' and exists $erg->{action}) {
+ my $error;
+ @$quest = @$questions;
+ while (my ($name, $data) = splice(@$quest, 0, 2)) {
+ # Required value ...
+ $error = $data->{req}
+ if($data->{req} and not $erg->{$name});
+
+ # Check Callback
+ if(exists $data->{check} and ref $data->{check} eq 'CODE' and not $error) {
+ ($erg->{$name}, $error) = $data->{check}($erg->{$name}, $data);
+ }
+
+ # Check on directory
+ if($data->{typ} eq 'dir' and $data->{required} and not -d $erg->{$name}) {
+ ($erg->{$name}, $error) = (undef, sprintf(gettext("Directory '%s' is doesn't exist!"), $erg->{$name}));
+ }
+
+ # Check on file
+ if($data->{typ} eq 'file' and $data->{required} and not -e $erg->{$name}) {
+ ($erg->{$name}, $error) = (undef, sprintf(gettext("File '%s' is doesn't exist!"), $erg->{$name}));
+ }
+
+ if($error) {
+ $obj->err(sprintf(gettext("Error at field '%s' (%s) : %s"), $data->{msg}, $name, $error));
+ last;
+ }
+ }
+ unless($error) {
+ delete $erg->{action};
+ return $erg;
+ }
+ }
+
+ $obj->formStart($titel);
+ if(ref $questions eq 'ARRAY') {
+ my $q = $obj->{cgi};
+ @$quest = @$questions;
+ while (my ($name, $data) = splice(@$quest, 0, 2)) {
+ my $type = delete $data->{typ};
+ $data->{msg} =~ s/\n/<br \/>/sig if($data->{msg});
+ $data->{NAME} = '__'.$name;
+ $type ||= 'string';
+ $obj->$type($data);
+ }
+ } else {
+ my $type = delete $questions->{typ};
+ $questions->{NAME} = '__'.$type;
+ $type ||= 'string';
+ $obj->$type($questions);
+ }
+ $obj->formEnd;
+ return undef;
+}
+
+# ------------------
+sub image {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $file = shift || return error ('No File!' );
+ my $typ = shift || $obj->{mime}->{lc((split('\.', $file))[-1])}
+ or return error("No Type in Mimehash or File: $file");
+
+ my $data = load_file($file)
+ or return $obj->status404($file,$!);
+
+ $obj->out($data, $typ);
+}
+
+# ------------------
+sub datei {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $file = shift || return error ('No File!' );
+
+ my $data = load_file($file)
+ or return $obj->status404($file,$!);
+
+ $obj->out($data, 'text/vnd.wap.wml');
+}
+
+# ------------------
+sub pod {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $modname = shift || return error ('No Modname!' );
+ $modname = ucfirst($modname) if($modname eq 'GENERAL');
+
+ my $podfile = sprintf('%s/%s.pod', $obj->{paths}->{PODPATH}, $modname);
+ my $tmpdir = main::getModule('USER')->userTmp;
+ my $outfile = sprintf('%s/%s_%d.pod', $tmpdir, $modname, time);
+
+ pod2html(
+ "--cachedir=$tmpdir",
+ "--infile=$podfile",
+ "--outfile=$outfile",
+ );
+ return error('Problem to convert pod2html')
+ unless(-r $outfile);
+
+ my $html = load_file($outfile);
+ $html = $1 if($html =~ /\<body.*?\>(.+?)\<\/body\>/si);
+ $obj->link({
+ text => gettext("Back to configuration screen"),
+ url => $obj->{browser}->{Referer},
+ });
+ $obj->message($html);
+}
+
+# ------------------
+sub typ {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{TYP};
+}
+
+# ------------------
+sub setCall {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $name = shift || return error ('No Name!' );
+
+ $obj->{call} = $name;
+ return $obj->{call};
+}
+
+# ------------------
+sub browser {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ return $obj->{browser};
+}
+
+# Special Version from Message (with error handling)
+# ------------------
+sub msg {
+# ------------------
+ my $obj = shift || return error ('No Object!' );
+ my $data = shift || {};
+ my $err = shift;
+
+ unless($err) {
+ $obj->message($data);
+ } else {
+ $obj->err($data);
+ }
+}
+
+
+
+1;