summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAndreas Brachold <vdr07@deltab.de>2008-06-15 06:21:31 +0000
committerAndreas Brachold <vdr07@deltab.de>2008-06-15 06:21:31 +0000
commit0b70320be6c8a4e13e02c70f7644678157d703ee (patch)
treeea16e3ce9a6b325c6d8287955158f406b4ba5d21 /lib
parent217af3aa226018610f812b2deb41dddd82203dc9 (diff)
downloadxxv-0b70320be6c8a4e13e02c70f7644678157d703ee.tar.gz
xxv-0b70320be6c8a4e13e02c70f7644678157d703ee.tar.bz2
* Remove linked templates [a,m,r,t]search.tmpl, widget selected now by console->setcall('tlist')
* Add modul to manage keywords withhin recordings * Store AUX-parameter (autotimer id, keywords) inside timer now as xml struct
Diffstat (limited to 'lib')
-rw-r--r--lib/XXV/MODULES/AUTOTIMER.pm96
-rw-r--r--lib/XXV/MODULES/KEYWORDS.pm319
-rw-r--r--lib/XXV/MODULES/MUSIC.pm1
-rw-r--r--lib/XXV/MODULES/RECORDS.pm143
-rw-r--r--lib/XXV/MODULES/TIMERS.pm161
5 files changed, 643 insertions, 77 deletions
diff --git a/lib/XXV/MODULES/AUTOTIMER.pm b/lib/XXV/MODULES/AUTOTIMER.pm
index ca43d20..5eb010f 100644
--- a/lib/XXV/MODULES/AUTOTIMER.pm
+++ b/lib/XXV/MODULES/AUTOTIMER.pm
@@ -15,6 +15,7 @@ sub module {
Name => 'AUTOTIMER',
Prereq => {
'Date::Manip' => 'date manipulation routines',
+ 'XML::Simple' => 'Easy API to maintain XML (esp config files)'
},
Description => gettext('This module searches for EPG entries with user-defined text and creates new timers.'),
Version => (split(/ /, '$Revision$'))[1],
@@ -242,7 +243,7 @@ sub new {
# paths
$self->{paths} = delete $attr{'-paths'};
- # who am I
+ # who am I
$self->{MOD} = $self->module;
# all configvalues to $self without parents (important for ConfigModule)
@@ -263,8 +264,9 @@ sub new {
# read the DB Handle
$self->{dbh} = delete $attr{'-dbh'};
- # file
- $self->{file} = $self->{config}->{file};
+ $self->{xml} = XML::Simple->new( NumericEscape => ($self->{charset} eq 'UTF-8' ? 0 : 1)
+ )
+ || return error("Can't create XML instance!");
# The Initprocess
my $erg = $self->_init or return error('Problem to initialize module');
@@ -310,6 +312,7 @@ sub _init {
startdate datetime default NULL,
stopdate datetime default NULL,
count int(11) default NULL,
+ keywords text,
PRIMARY KEY (Id)
) COMMENT = '$version'
|);
@@ -446,22 +449,22 @@ sub _autotimerLookup {
# 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) {
+ foreach my $eventid (sort keys %$events) {
- my $output = [ [gettext("Title"), $events->{$Id}->{title}] ];
- push(@$output, [gettext("Subtitle"), $events->{$Id}->{subtitle}])
- if($events->{$Id}->{subtitle});
- push(@$output, [gettext("Channel"), $events->{$Id}->{channelname}]);
+ my $output = [ [gettext("Title"), $events->{$eventid}->{title}] ];
+ push(@$output, [gettext("Subtitle"), $events->{$eventid}->{subtitle}])
+ if($events->{$eventid}->{subtitle});
+ push(@$output, [gettext("Channel"), $events->{$eventid}->{channelname}]);
- if($events->{$Id}->{vpsstart} and $a->{VPS} eq 'y' and $modT->{usevpstime} eq 'y') {
- push(@$output, [gettext("Start"), datum($events->{$Id}->{vpsstart} )]);
- push(@$output, [gettext("Stop"), datum($events->{$Id}->{vpsstop} )]);
+ if($events->{$eventid}->{vpsstart} and $a->{VPS} eq 'y' and $modT->{usevpstime} eq 'y') {
+ push(@$output, [gettext("Start"), datum($events->{$eventid}->{vpsstart} )]);
+ push(@$output, [gettext("Stop"), datum($events->{$eventid}->{vpsstop} )]);
} else {
- push(@$output, [gettext("Start"), datum($events->{$Id}->{starttime})]);
- push(@$output, [gettext("Stop"), datum($events->{$Id}->{stoptime} )]);
+ push(@$output, [gettext("Start"), datum($events->{$eventid}->{starttime})]);
+ push(@$output, [gettext("Stop"), datum($events->{$eventid}->{stoptime} )]);
}
- push(@$output,[gettext("Description"), $events->{$Id}->{description}])
- if($events->{$Id}->{description});
+ push(@$output,[gettext("Description"), $events->{$eventid}->{description}])
+ if($events->{$eventid}->{description});
$console->table($output);
};
}
@@ -472,8 +475,8 @@ sub _autotimerLookup {
# Every found and save this as timer
my $c = 0;
my $m = 0;
- foreach my $Id (sort keys %$events) {
- my $event = $events->{$Id};
+ foreach my $eventid (sort keys %$events) {
+ my $event = $events->{$eventid};
$event->{activ} = 'y';
$event->{priority} = $a->{Priority};
@@ -501,10 +504,16 @@ sub _autotimerLookup {
$event->{start} = sprintf("%02d%02d",$bhour,$bmin);
$event->{stop} = sprintf("%02d%02d",$ehour,$emin);
- $event->{file} = $obj->_placeholder($event, $a);
+ my $keywords;
+ ($event->{file},$keywords) = $obj->_placeholder($event, $a);
# Add anchor for reidentify timer
- $event->{aux} = sprintf('#~AT[%d]', $id);
+ my $args = {
+ 'autotimer' => $id,
+# 'eventid' => $eventid
+ };
+ $args->{'keywords'} = $keywords if($keywords);
+ $event->{aux} = $obj->{xml}->XMLout($args, RootName => 'xxv');
# Wished timer already exist with same data from autotimer ?
next if($obj->_timerexists($event));
@@ -1006,6 +1015,11 @@ You can also fine tune your search :
return undef, gettext('The day is incorrect or was in a wrong format!');
},
},
+ 'keywords' => {
+ typ => 'string',
+ def => $epg->{keywords},
+ msg => gettext('Add keywords to recording'),
+ },
];
# Ask Questions
@@ -1260,6 +1274,8 @@ sub list {
$info->{sortable} = '1';
$info->{timers} = main::getModule('TIMERS')->getTimersByAutotimer();
}
+
+ $console->setCall('alist');
$console->table($erg, $info );
}
@@ -1554,24 +1570,26 @@ sub _placeholder {
my $file;
+ 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->{description} =~ m/\|Director: (.*?)\|/;
+ $at_details{'category'} = $1 if $data->{description} =~ m/\|Category: (.*?)\|/;
+ $at_details{'genre'} = $1 if $data->{description} =~ m/\|Genre: (.*?)\|/;
+ $at_details{'year'} = $1 if $data->{description} =~ m/\|Year: (.*?)\|/;
+ $at_details{'country'} = $1 if $data->{description} =~ m/\|Country: (.*?)\|/;
+ $at_details{'originaltitle'} = $1 if $data->{description} =~ m/\|Originaltitle: (.*?)\|/;
+ $at_details{'fsk'} = $1 if $data->{description} =~ m/\|FSK: (.*?)\|/;
+ $at_details{'episode'} = $1 if $data->{description} =~ m/\|Episode: (.*?)\|/;
+ $at_details{'rating'} = $1 if $data->{description} =~ m/\|Rating: (.*?)\|/;
+ $at_details{'cast'} = $1 if $data->{description} =~ m/\|Cast: (.*?)\|/;
+
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->{description} =~ m/\|Director: (.*?)\|/;
- $at_details{'category'} = $1 if $data->{description} =~ m/\|Category: (.*?)\|/;
- $at_details{'genre'} = $1 if $data->{description} =~ m/\|Genre: (.*?)\|/;
- $at_details{'year'} = $1 if $data->{description} =~ m/\|Year: (.*?)\|/;
- $at_details{'country'} = $1 if $data->{description} =~ m/\|Country: (.*?)\|/;
- $at_details{'originaltitle'} = $1 if $data->{description} =~ m/\|Originaltitle: (.*?)\|/;
- $at_details{'fsk'} = $1 if $data->{description} =~ m/\|FSK: (.*?)\|/;
- $at_details{'episode'} = $1 if $data->{description} =~ m/\|Episode: (.*?)\|/;
- $at_details{'rating'} = $1 if $data->{description} =~ m/\|Rating: (.*?)\|/;
- $title =~ s/%([\w_-]+)%/$at_details{lc($1)}/sieg;
- $file = $title;
+ $title =~ s/%([\w_-]+)%/$at_details{lc($1)}/sieg;
+ $file = $title;
} else { # Classic mode DIR~TITLE~SUBTILE
if($data->{subtitle}) {
$file = sprintf('%s~%s~%s', $at->{Dir}, $data->{title},$data->{subtitle});
@@ -1585,12 +1603,20 @@ sub _placeholder {
$file = $data->{title};
}
+ my $keywords;
+ if ($at->{keywords}) {
+ $keywords = $at->{keywords};
+ if($keywords =~ /.*%.*%.*/sig) {
+ $keywords =~ s/%([\w_-]+)%/$at_details{lc($1)}/sieg;
+ }
+ }
+
# sind irgendweche Tags verwendet worden, die leer waren und die doppelte Verzeichnisse erzeugten?
$file =~s#~+#~#g;
$file =~s#^~##g;
$file =~s#~$##g;
- return $file;
+ return ($file,$keywords);
}
# ------------------
diff --git a/lib/XXV/MODULES/KEYWORDS.pm b/lib/XXV/MODULES/KEYWORDS.pm
new file mode 100644
index 0000000..c1c5d1a
--- /dev/null
+++ b/lib/XXV/MODULES/KEYWORDS.pm
@@ -0,0 +1,319 @@
+package XXV::MODULES::KEYWORDS;
+
+use strict;
+use Tools;
+
+# ------------------
+sub module {
+# ------------------
+ my $self = shift || return error('No object defined!');
+
+ my $args = {
+ Name => 'KEYWORDS',
+ Prereq => {
+# 'XML::Simple' => 'Easy API to maintain XML (esp config files)'
+ },
+ Description => gettext('This module manages keywords and tag within timer and recordings.'),
+ Version => (split(/ /, '$Revision: 1332 $'))[1],
+ Date => (split(/ /, '$Date: 2008-05-24 09:05:56 +0200 (Sa, 24 Mai 2008) $'))[1],
+ Author => 'anbr',
+ LastAuthor => (split(/ /, '$Author: anbr $'))[1],
+# Status => sub{ $self->status(@_) },
+ Preferences => {
+ active => {
+ description => gettext('Activate this service'),
+ default => 'y',
+ type => 'confirm',
+ required => gettext('This is required!'),
+ },
+ },
+ Commands => {
+ tkeywords => {
+ description => gettext("Search timers 'keywords'"),
+ short => 'tk',
+ callback => sub{ $self->timer_keywords(@_) },
+ DenyClass => 'tlist',
+ },
+ tsuggestkeywords => {
+ hidden => 'yes',
+ callback => sub{ $self->suggest('timer',@_) },
+ DenyClass => 'tlist',
+ },
+ rkeywords => {
+ description => gettext("Search recordings 'keywords'"),
+ short => 'rk',
+ callback => sub{ $self->recording_keywords(@_) },
+ DenyClass => 'rlist',
+ },
+ rsuggestkeywords => {
+ hidden => 'yes',
+ callback => sub{ $self->suggest('recording',@_) },
+ DenyClass => 'rlist',
+ }
+ }
+ };
+ return $args;
+}
+
+# ------------------
+sub new {
+# ------------------
+ my($class, %attr) = @_;
+ my $self = {};
+ bless($self, $class);
+
+ $self->{charset} = delete $attr{'-charset'};
+ if($self->{charset} eq 'UTF-8'){
+ eval 'use utf8';
+ }
+
+ # 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 $_";
+ if($@) {
+ my $m = (split(/ /, $_))[0];
+ return panic("\nCouldn't load perl module: $m\nPlease install this module on your system:\nperl -MCPAN -e 'install $m'");
+ }
+ } keys %{$self->{MOD}->{Prereq}};
+
+ # read the DB Handle
+ $self->{dbh} = delete $attr{'-dbh'};
+
+ #$self->{xml} = XML::Simple->new( NumericEscape => ($self->{charset} eq 'UTF-8' ? 0 : 1))
+ # || return error("Can't create XML instance!");
+
+ # The Initprocess
+ my $erg = $self->_init or return error('Problem to initialize modul!');
+
+ return $self;
+}
+
+# ------------------
+sub _init {
+# ------------------
+ my $self = shift || return error('No object defined!');
+
+ unless($self->{dbh}) {
+ panic("Session to database is'nt connected");
+ return 0;
+ }
+
+ my $version = 29; # Must be increment if rows of table changed
+ # this tables hasen't handmade user data,
+ # therefore old table could dropped if updated rows
+ if(!tableUpdated($self->{dbh},'KEYWORDS',$version,1)) {
+ return 0;
+ }
+
+ # Look for table or create this table
+ $self->{dbh}->do(qq|
+ CREATE TABLE IF NOT EXISTS KEYWORDS (
+ id int(11) NOT NULL auto_increment,
+ md5 varchar(32) NOT NULL,
+ keyword varchar(128) NOT NULL,
+ rank tinyint NOT NULL,
+ total tinyint NOT NULL,
+ source enum('recording', 'timer'),
+ PRIMARY KEY (id),
+ UNIQUE KEY (md5,keyword)
+ ) COMMENT = '$version'
+ |);
+
+
+
+ 1;
+}
+
+# ------------------
+sub insert {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $type = shift || return error('No type defined!');
+ my $id = shift || return undef;
+ my $keywords = shift || return undef;
+
+ return unless($self->{active} eq 'y');
+
+ my $sth = $self->{dbh}->prepare(qq|REPLACE INTO KEYWORDS(md5, keyword, rank, total, source ) VALUES (?,?,?,?,?)|);
+ my @words = split(/[,;\r\n]/, $keywords);
+ my $total = scalar @words;
+ my $rank = $total + 1;
+ foreach my $w (@words) {
+ $rank --;
+ $w =~ s/^\s+//; # no leading white space
+ $w =~ s/\s+$//; # no trailing white space
+ next unless($w);
+ if(!$sth->execute($id,$w,$rank,$total,$type)) {
+ error sprintf("Couldn't insert keyword!: '%s' !",$w);
+ return undef;
+ }
+ }
+ return 1;
+}
+
+# ------------------
+sub remove {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $type = shift || return error('No type defined!');
+ my $md5 = shift || return undef;
+
+ return unless($self->{active} eq 'y');
+
+ my $sql = sprintf('DELETE FROM KEYWORDS WHERE md5 IN (%s)', join(',' => ('?') x @$md5));
+ my $sth = $self->{dbh}->prepare($sql);
+ $sth->execute(@$md5)
+ or return error sprintf("Couldn't execute query: %s.",$sth->errstr);
+ return 1;
+}
+
+# ------------------
+sub removesource {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $type = shift || return error('No type defined!');
+
+ return unless($self->{active} eq 'y');
+
+ my $sth = $self->{dbh}->prepare('DELETE FROM KEYWORDS WHERE source = ?');
+ $sth->execute($type)
+ or return error sprintf("Couldn't execute query: %s.",$sth->errstr);
+ return 1;
+}
+
+# ------------------
+sub suggest {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $type = shift || return error('No type defined!');
+ my $watcher = shift || return error('No watcher defined!');
+ my $console = shift || return error('No console defined!');
+ my $search = shift;
+ my $params = shift;
+
+ if($search) {
+ my $sth = $self->{dbh}->prepare(
+qq|SELECT SQL_CACHE keyword from KEYWORDS
+ WHERE source = ?
+ AND keyword LIKE ?
+ GROUP BY keyword
+ LIMIT 25|);
+ if(!$sth->execute($type, '%'.$search.'%')) {
+ error sprintf("Couldn't execute query: %s.",$sth->errstr);
+ } else {
+ my $result = $sth->fetchall_arrayref();
+ $console->table($result)
+ if(ref $console && $result);
+ }
+ }
+
+}
+
+# ------------------
+sub list {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $type = shift || return error('No type defined!');
+ my $md5 = shift;
+
+ return (undef,0,0) unless($self->{active} eq 'y');
+
+ # Get keywords with highest ranking
+ my $list = $self->_list($type,$md5);
+ return (undef,0,0) unless($list and scalar @$list);
+ # Remember highest and lowest ranking for scaling
+ my $keywordmax = $list->[0]->[1];
+ my $keywordmin = $list->[-1]->[1];
+ # sort keyworks by name
+ my @keywords = sort {$a->[0] cmp $b->[0]} @$list;
+
+ return (\@keywords,$keywordmax,$keywordmin);
+}
+
+# ------------------
+sub _list {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $type = shift || return error('No type defined!');
+ my $md5 = shift;
+ my $sth;
+ if($md5 and ref $md5 eq 'ARRAY') {
+ my $sql = sprintf(qq|SELECT SQL_CACHE keyword,sum(100/total*rank) as pos
+ FROM KEYWORDS
+ WHERE source = ? AND md5 IN (%s)
+ GROUP BY keyword
+ ORDER BY pos desc
+ LIMIT 20|, join(',' => ('?') x @$md5));
+ unshift(@$md5,$type);
+ $sth = $self->{dbh}->prepare($sql);
+ $sth->execute(@$md5)
+ or return error sprintf("Couldn't execute query: %s.",$sth->errstr);
+ } else {
+ my $sql = qq|SELECT SQL_CACHE keyword,sum(100/total*rank) as pos
+ FROM KEYWORDS
+ WHERE source = ?
+ GROUP BY keyword
+ ORDER BY pos desc
+ LIMIT 20|;
+ $sth = $self->{dbh}->prepare($sql);
+ $sth->execute($type)
+ or return error sprintf("Couldn't execute query: %s.",$sth->errstr);
+ }
+ my $result = $sth->fetchall_arrayref();
+ return $result;
+}
+
+# ------------------
+sub timer_keywords {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $watcher = shift || return error('No watcher defined!');
+ my $console = shift || return error('No console defined!');
+ my $text = shift;
+ my $params = shift;
+
+ my $tmod = main::getModule('TIMERS');
+ unless($text) {
+ return $tmod->list($watcher,$console);
+ }
+
+ my $term;
+ my $search;
+ my $query = buildsearch("k.keyword",$text);
+ $search = sprintf('AND ( %s ) AND ( t.id = k.md5 )', $query->{query});
+ foreach(@{$query->{term}}) { push(@{$term},$_); }
+
+ return $tmod->_list($watcher,$console,$search,$term,$params,', KEYWORDS as k');
+}
+
+# ------------------
+sub recording_keywords {
+# ------------------
+ my $self = shift || return error('No object defined!');
+ my $watcher = shift || return error('No watcher defined!');
+ my $console = shift || return error('No console defined!');
+ my $text = shift;
+ my $params = shift;
+
+ my $rmod = main::getModule('RECORDS');
+ unless($text) {
+ return $rmod->list($watcher,$console);
+ }
+
+ my $query = buildsearch("k.keyword",$text);
+ return $rmod->_search($watcher,$console,$query->{query}.' ) AND ( r.RecordMD5 = k.md5 ',$query->{term},$params,', KEYWORDS as k');
+}
+
+1;
diff --git a/lib/XXV/MODULES/MUSIC.pm b/lib/XXV/MODULES/MUSIC.pm
index ae07897..7bee9c2 100644
--- a/lib/XXV/MODULES/MUSIC.pm
+++ b/lib/XXV/MODULES/MUSIC.pm
@@ -708,6 +708,7 @@ sub list {
getCover => sub{ return $obj->_findcoverfromcache(@_, 'relative') },
};
+ $console->setCall('mlist');
$console->table($erg, $params);
}
diff --git a/lib/XXV/MODULES/RECORDS.pm b/lib/XXV/MODULES/RECORDS.pm
index 0fdd5b0..e551da5 100644
--- a/lib/XXV/MODULES/RECORDS.pm
+++ b/lib/XXV/MODULES/RECORDS.pm
@@ -23,7 +23,8 @@ sub module {
Prereq => {
'Time::Local' => 'efficiently compute time from local and GMT time ',
'Digest::MD5 qw(md5_hex)' => 'Perl interface to the MD5 Algorithm',
- 'Linux::Inotify2' => 'scalable directory/file change notification'
+ 'Linux::Inotify2' => 'scalable directory/file change notification',
+ 'XML::Simple' => 'Easy API to maintain XML (esp config files)'
},
Description => gettext('This module manages recordings.'),
Version => (split(/ /, '$Revision$'))[1],
@@ -242,7 +243,7 @@ sub new {
# paths
$self->{paths} = delete $attr{'-paths'};
- # who am I
+ # who am I
$self->{MOD} = $self->module;
# all configvalues to $self without parents (important for ConfigModule)
@@ -263,13 +264,16 @@ sub new {
# read the DB Handle
$self->{dbh} = delete $attr{'-dbh'};
+ $self->{xml} = XML::Simple->new( NumericEscape => ($self->{charset} eq 'UTF-8' ? 0 : 1))
+ || return error("Can't create XML instance!");
+
# define framerate PAL 25, NTSC 30
$self->{framerate} = Tools->FRAMESPERSECOND;
# The Initprocess
my $erg = $self->_init or return error('Problem to initialize modul!');
- return $self;
+ return $self;
}
# ------------------
@@ -282,7 +286,7 @@ sub _init {
return 0;
}
- my $version = 28; # Must be increment if rows of table changed
+ my $version = 29; # Must be increment if rows of table changed
# this tables hasen't handmade user data,
# therefore old table could dropped if updated rows
if(!tableUpdated($obj->{dbh},'RECORDS',$version,1)) {
@@ -303,6 +307,7 @@ sub _init {
Marks text,
Type enum('TV', 'RADIO', 'UNKNOWN') default 'TV',
preview text NOT NULL,
+ aux text,
addtime timestamp,
PRIMARY KEY (eventid),
UNIQUE KEY (eventid)
@@ -325,6 +330,11 @@ sub _init {
return 0;
}
+ $obj->{keywords} = main::getModule('KEYWORDS');
+ unless($obj->{keywords}) {
+ return 0;
+ }
+
my $updatefile = sprintf("%s/.update",$obj->{videodir});
if( -r $updatefile) {
my $inotify = new Linux::Inotify2
@@ -525,6 +535,7 @@ sub readData {
unless(scalar @$vdata) {
# Delete old Records
$obj->{dbh}->do('DELETE FROM RECORDS');
+ $obj->{keywords}->removesource('recording');
my $msg = gettext('No recordings available!');
con_err($console,$msg);
@@ -567,6 +578,7 @@ sub readData {
my $db_data;
if($forceUpdate) {
$obj->{dbh}->do('DELETE FROM RECORDS');
+ $obj->{keywords}->removesource('recording');
} else {
# read database for compare with vdr data
my $sql = qq|SELECT SQL_CACHE r.eventid as eventid, r.RecordId as id,
@@ -669,6 +681,9 @@ sub readData {
if($obj->insert($info)) {
push(@merkMD5,$info->{RecordMD5});
$insertedData++;
+
+ $obj->{keywords}->insert('recording',$info->{RecordMD5},$info->{keywords});
+
} else {
push(@{$err},sprintf(gettext("Can't add recording '%s' into database!"),$info->{title}));
}
@@ -693,13 +708,14 @@ sub readData {
my $sth = $obj->{dbh}->prepare($sql);
$sth->execute(@todel)
or return con_err($console, sprintf("Couldn't execute query: %s.",$sth->errstr));
+
+ $obj->{keywords}->remove('recording',\@todel);
}
my $removedData = $db_data ? scalar keys %$db_data : 0;
debug sprintf 'Finish .. %d recordings inserted, %d recordings updated, %d recordings removed',
$insertedData, $updatedState, $removedData;
-
error sprintf("Unsupported unit '%s' to calc free capacity",$freeUnit) unless($freeUnit eq 'MB');
# use store capacity and recordings length to calc free capacity
$obj->{CapacityTotal} = $totalDuration;
@@ -832,8 +848,8 @@ sub insert {
my $sth = $obj->{dbh}->prepare(
qq|
REPLACE INTO RECORDS
- (eventid, RecordId, RecordMD5, Path, Prio, Lifetime, State, FileSize, Marks, Type, preview, addtime )
- VALUES (?,?,?,?,?,?,?,?,?,?,?,NOW())
+ (eventid, RecordId, RecordMD5, Path, Prio, Lifetime, State, FileSize, Marks, Type, preview, aux, addtime )
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,NOW())
|);
$attr->{Marks} = ""
@@ -850,7 +866,8 @@ sub insert {
$attr->{FileSize},
$attr->{Marks},
$attr->{Type},
- $attr->{preview}
+ $attr->{preview},
+ $attr->{aux}
);
}
@@ -959,7 +976,9 @@ sub analyze {
eventid => $event->{eventid},
Type => $info->{type} || 'UNKNOWN',
State => $recattr->{state},
- FileSize => $info->{FileSize}
+ FileSize => $info->{FileSize},
+ aux => $info->{aux},
+ keywords => $info->{keywords}
};
$ret->{Marks} = join(',', @{$info->{marks}})
if(ref $info->{marks} eq 'ARRAY');
@@ -1079,6 +1098,24 @@ sub readinfo {
$info->{audio} .= "\n" if($info->{audio});
$info->{audio} .= $1;
}
+ elsif($zeile =~ /^@\s+(.+)$/s) {
+ $info->{aux} = $1;
+ $info->{aux} =~ s/\|/\r\n/g; # pipe used from vdr as linebreak
+ $info->{aux} =~ s/^\s+//; # no leading white space
+ $info->{aux} =~ s/\s+$//; # no trailing white space
+
+ if($info->{aux} && $info->{aux} =~ /^<.*/ ) {
+ my $args = $obj->{xml}->XMLin($info->{aux}, KeepRoot => 1 );
+ if(defined $args
+ && defined $args->{'xxv'} ) {
+ my $root = $args->{'xxv'};
+# $info->{keywords} = int($root->{'autotimer'})
+# if(defined $root->{'autotimer'} );
+ $info->{keywords} = $root->{'keywords'}
+ if(defined $root->{'keywords'} );
+ }
+ }
+ }
}
}
return $info;
@@ -1149,6 +1186,12 @@ sub saveinfo {
}
undef $info->{audio};
}
+ }
+ elsif($zeile =~ /^@\s+(.+)/s) {
+ if(defined $info->{aux} && $info->{aux}) {
+ $out .= "@ ". $info->{aux} . "\n" if($info->{aux});
+ undef $info->{aux};
+ }
} else {
$out .= $zeile . "\n" if($zeile);
}
@@ -1179,6 +1222,9 @@ sub saveinfo {
$out .= "X 2 ". $line . "\n" if($line);
}
}
+ if(defined $info->{aux} && $info->{aux}) {
+ $out .= "@ ". $info->{aux} . "\n" if($info->{aux});
+ }
return save_file($file, $out);
}
@@ -1478,8 +1524,11 @@ where
$_ =~ s/\s*\:.*$//;
} @reccmds;
+ my ($keywords,$keywordmax,$keywordmin) = $obj->{keywords}->list('recording',[ $erg->{'RecordId'} ]);
+
my $param = {
reccmds => \@reccmds,
+ keywords => $keywords
};
$console->table($erg,$param);
}
@@ -1667,10 +1716,19 @@ ORDER BY __IsRecording asc,
my $fields = $sth->{'NAME'};
my $erg = $sth->fetchall_arrayref();
+ my $keywords;
+ my $keywordmax;
+ my $keywordmin;
+
unless($console->typ eq 'AJAX') {
+ my $md5;
map {
+ push(@$md5,$_->[0]);
$_->[5] = datum($_->[5],'short');
} @$erg;
+
+ ($keywords,$keywordmax,$keywordmin) = $obj->{keywords}->list('recording',$md5);
+
unshift(@$erg, $fields);
}
@@ -1681,6 +1739,9 @@ ORDER BY __IsRecording asc,
total => $obj->{CapacityTotal},
free => $obj->{CapacityFree},
previewcommand => $obj->{previewlistthumbs},
+ keywords => $keywords,
+ keywordsmax => $keywordmax,
+ keywordsmin => $keywordmin,
rows => $rows
};
return $console->table($erg, $param);
@@ -1696,8 +1757,21 @@ sub search {
my $params = shift;
my $query = buildsearch("e.title,e.subtitle,e.description",$text);
- my $search = $query->{query};
- my $term = $query->{term};
+ return $obj->_search($watcher,$console,$query->{query},$query->{term},$params);
+}
+
+# ------------------
+sub _search {
+# ------------------
+ my $obj = shift || return error('No object defined!');
+ my $watcher = shift;
+ my $console = shift;
+ my $search = shift;
+ my $term = shift;
+ my $params = shift;
+ my $tables = shift || '';
+
+
my %f = (
'RecordMD5' => gettext('Index'),
@@ -1725,6 +1799,7 @@ SELECT SQL_CACHE
FROM
RECORDS as r,
OLDEPG as e
+ $tables
WHERE
e.eventid = r.eventid
AND ( $search )
@@ -1776,10 +1851,19 @@ ORDER BY
my $fields = $sth->{'NAME'};
my $erg = $sth->fetchall_arrayref();
+ my $keywords;
+ my $keywordmax;
+ my $keywordmin;
+
unless($console->typ eq 'AJAX') {
+ my $md5;
map {
+ push(@$md5,$_->[0]);
$_->[5] = datum($_->[5],'short');
} @$erg;
+
+ ($keywords,$keywordmax,$keywordmin) = $obj->{keywords}->list('recording',$md5);
+
unshift(@$erg, $fields);
}
@@ -1790,8 +1874,13 @@ ORDER BY
total => $obj->{CapacityTotal},
free => $obj->{CapacityFree},
previewcommand => $obj->{previewcommand},
+ keywords => $keywords,
+ keywordsmax => $keywordmax,
+ keywordsmin => $keywordmin,
rows => $rows
};
+
+ $console->setCall('rlist');
return $console->table($erg, $param);
}
@@ -1894,6 +1983,7 @@ sub delete {
$sth->execute(@{$md5delete})
or return con_err($console, sprintf("Couldn't execute query: %s.",$sth->errstr));
+ $obj->{keywords}->remove('recording',$md5delete);
}
$obj->readData($watcher,$console,$waiter)
@@ -2032,6 +2122,15 @@ WHERE
msg => gettext("Description"),
def => $status->{description} || '',
},
+ 'aux' => {
+ typ => 'hidden',
+ def => $status->{aux},
+ },
+ 'keywords' => {
+ typ => 'string',
+ msg => gettext('Keywords'),
+ def => $status->{keywords},
+ },
'video' => {
typ => 'textfield',
msg => gettext('Video'),
@@ -2066,6 +2165,7 @@ WHERE
if($data->{title} ne $rec->{title}
or $data->{description} ne $status->{description}
or $data->{channel} ne $status->{channel}
+ or $data->{keywords} ne $status->{keywords}
or $data->{video} ne $status->{video}
or $data->{audio} ne $status->{audio}) {
@@ -2077,9 +2177,27 @@ WHERE
$info->{title} = join('~',@t);
}
+ my $root = {};
+ if(exists $info->{aux}) {
+ $info->{aux} =~ s/(\r|\n)//sg;
+ if($info->{aux} && $info->{aux} =~ /^<.*/ ) {
+ my $args = $obj->{xml}->XMLin($info->{aux}, KeepRoot => 1 );
+ if(defined $args
+ && defined $args->{'xxv'} ) {
+ $root = $args->{'xxv'};
+ }
+ }
+ }
+ #$root->{'autotimer'} = $data->{autotimerid} if($data->{autotimerid});
+ $root->{'keywords'} = $info->{keywords} if($info->{keywords});
+ if($root && keys %$root) {
+ $info->{aux} = $obj->{xml}->XMLout($root, RootName => 'xxv');
+ }
+
$obj->saveinfo($rec->{Path},$info)
or return con_err($console,sprintf(gettext("Couldn't write file '%s' : %s"),$rec->{Path} . '/info.vdr',$!));
+ $ChangeRecordingData = 1 if($data->{keywords} ne $status->{keywords});
$dropEPGEntry = 1;
}
@@ -2152,6 +2270,8 @@ WHERE
my $sth = $obj->{dbh}->prepare('DELETE FROM RECORDS WHERE RecordMD5 = ?');
$sth->execute($recordid)
or return con_err($console,sprintf("Couldn't execute query: %s.",$sth->errstr));
+
+ $obj->{keywords}->remove('recording',\[$recordid]);
}
if($dropEPGEntry || $ChangeRecordingData) {
@@ -2690,4 +2810,5 @@ sub image {
}
return $console->datei(sprintf('%s/%s_shot/%s.jpg', $obj->{previewimages}, $recordid, $frame));
}
+
1;
diff --git a/lib/XXV/MODULES/TIMERS.pm b/lib/XXV/MODULES/TIMERS.pm
index a53afe6..e5b949d 100644
--- a/lib/XXV/MODULES/TIMERS.pm
+++ b/lib/XXV/MODULES/TIMERS.pm
@@ -13,6 +13,8 @@ sub module {
Name => 'TIMERS',
Prereq => {
'Date::Manip' => 'date manipulation routines',
+ 'Digest::MD5 qw(md5_hex)' => 'Perl interface to the MD5 Algorithm',
+ 'XML::Simple' => 'Easy API to maintain XML (esp config files)'
},
Description => gettext('This module reads timers and saves it to the database.'),
Version => (split(/ /, '$Revision$'))[1],
@@ -73,7 +75,7 @@ sub module {
tsearch => {
description => gettext("Search timers 'text'"),
short => 'ts',
- callback => sub{ $obj->list(@_) },
+ callback => sub{ $obj->search(@_) },
DenyClass => 'tlist',
},
tupdate => {
@@ -313,9 +315,9 @@ sub module {
my $timer = getDataById($i, 'TIMERS', 'pos');
my $level = 1;
- if($timer->{autotimerid} and ($timer->{flags} & 1)) {
+ if($timer->{autotimerid} and ($timer->{flags} && $timer->{flags} & 1)) {
$level = (($timer->{priority} <= 50 or $timer->{lifetime} < 33) ? 2 : 3);
- } elsif($timer->{flags} & 1) {
+ } elsif($timer->{flags} && $timer->{flags} & 1) {
$level = (($timer->{priority} <= 50 or $timer->{lifetime} < 33) ? 4 : 5);
}
@@ -372,7 +374,7 @@ sub new {
# paths
$self->{paths} = delete $attr{'-paths'};
- # who am I
+ # who am I
$self->{MOD} = $self->module;
# all configvalues to $self without parents (important for ConfigModule)
@@ -393,6 +395,9 @@ sub new {
# read the DB Handle
$self->{dbh} = delete $attr{'-dbh'};
+ $self->{xml} = XML::Simple->new( NumericEscape => ($self->{charset} eq 'UTF-8' ? 0 : 1))
+ || return error("Can't create XML instance!");
+
# The Initprocess
my $erg = $self->_init or return error('Problem to initialize modul!');
@@ -409,7 +414,7 @@ sub _init {
return 0;
}
- my $version = 28; # Must be increment if rows of table changed
+ my $version = 29; # Must be increment if rows of table changed
# this tables hasen't handmade user data,
# therefore old table could dropped if updated rows
if(!tableUpdated($obj->{dbh},'TIMERS',$version,1)) {
@@ -451,6 +456,10 @@ sub _init {
panic ("Couldn't get modul SVDRP");
return 0;
}
+ $obj->{keywords} = main::getModule('KEYWORDS');
+ unless($obj->{keywords}) {
+ return 0;
+ }
# merge source from channels to enum typ of used DVB cards like S19.2E,S19.2E,T
$obj->{MOD}->{Preferences}->{DVBCardsTyp}->{default} = $obj->_buildDVBCardsTyp();
@@ -512,7 +521,24 @@ sub _saveTimer {
$data->{flags} |= ($data->{vps} eq 'y' ? 4 : 0);
$data->{file} =~ s/(\r|\n)//sg;
- $data->{aux} =~ s/(\r|\n)//sg if(exists $data->{aux});
+
+ # Add anchor for reidentify timer
+ my $root = {};
+ if(exists $data->{aux}) {
+ $data->{aux} =~ s/(\r|\n)//sg;
+ if($data->{aux} && $data->{aux} =~ /^<.*/ ) {
+ my $args = $obj->{xml}->XMLin($data->{aux}, KeepRoot => 1 );
+ if(defined $args
+ && defined $args->{'xxv'} ) {
+ $root = $args->{'xxv'};
+ }
+ }
+ }
+ #$root->{'autotimer'} = $data->{autotimerid} if($data->{autotimerid});
+ $root->{'keywords'} = $data->{keywords} if($data->{keywords});
+ if($root && keys %$root) {
+ $data->{aux} = $obj->{xml}->XMLout($root, RootName => 'xxv');
+ }
my $file = $data->{file};
$file =~ s/:/|/g;
@@ -654,9 +680,22 @@ WHERE
$timerData = $data;
}
- $timerData->{aux} =~ s/(\r|\n)//sig
- if(defined $timerData->{aux});
-
+ if(defined $timerData->{aux}) {
+ $timerData->{aux} =~ s/(\r|\n)//sig;
+
+ $timerData->{keywords} = '';
+ if($timerData->{aux} && $timerData->{aux} =~ /^<.*/ ) {
+ my $args = $obj->{xml}->XMLin($timerData->{aux}, KeepRoot => 1 );
+ if(defined $args
+ && defined $args->{'xxv'} ) {
+ my $root = $args->{'xxv'};
+# $aid = int($root->{'autotimer'})
+# if(defined $root->{'autotimer'} );
+ $timerData->{keywords} = $root->{'keywords'}
+ if(defined $root->{'keywords'} );
+ }
+ }
+ }
my $modC = main::getModule('CHANNELS');
my $con = $console->typ eq "CONSOLE";
@@ -806,6 +845,11 @@ WHERE
}
},
},
+ 'keywords' => {
+ typ => 'string',
+ def => $timerData->{keywords},
+ msg => gettext('Add keywords to recording'),
+ },
'aux' => {
typ => 'hidden',
def => $timerData->{aux},
@@ -1054,7 +1098,7 @@ sub toggleTimer {
# ------------------
sub _insert {
# ------------------
- my $obj = shift || return error('No object defined!');
+ my $self = shift || return error('No object defined!');
my $timer = shift || return;
my $checked = shift || 0;
@@ -1070,15 +1114,11 @@ sub _insert {
$timer->{file} =~ s/\|/\:/g;
# NextTime
- my $nexttime = $obj->getNextTime( $timer->{day}, $timer->{start}, $timer->{stop} )
+ my $nexttime = $self->getNextTime( $timer->{day}, $timer->{start}, $timer->{stop} )
or return error(sprintf("Couldn't get time from this timer: %d '%s' '%s' '%s'", $timer->{pos}, $timer->{day}, $timer->{start}, $timer->{stop}));
- # AutotimerId
- my $atxt = (split('~', $timer->{aux}))[-1];
- my $aid = $1 if(defined $atxt and $atxt =~ /AT\[(\d+)\]/);
-
# Search for event at EPG
- my $e = $obj->_getNextEpgId( {
+ my $e = $self->_getNextEpgId( {
pos => $timer->{pos},
flags => $timer->{flags},
channel => $timer->{channel},
@@ -1087,12 +1127,27 @@ sub _insert {
stop => $nexttime->{stop},
});
- my $sth = $obj->{dbh}->prepare(
+ # Tags
+ my $aid;
+ my $keywords;
+ if($timer->{aux} && $timer->{aux} =~ /^<.*/ ) {
+ my $args = $self->{xml}->XMLin($timer->{aux}, KeepRoot => 1 );
+ if(defined $args
+ && defined $args->{'xxv'} ) {
+ my $root = $args->{'xxv'};
+ $aid = int($root->{'autotimer'})
+ if(defined $root->{'autotimer'} );
+ $keywords = $root->{'keywords'}
+ if(defined $root->{'keywords'} );
+ }
+ }
+ my $sth = $self->{dbh}->prepare(
q|REPLACE INTO TIMERS VALUES
- (MD5(CONCAT(?,?,?)),?,?,?,?,?,?,?,?,?,?,FROM_UNIXTIME(?), FROM_UNIXTIME(?),0,?,?,?,?,?,NOW())
+ (?,?,?,?,?,?,?,?,?,?,?,FROM_UNIXTIME(?), FROM_UNIXTIME(?),0,?,?,?,?,?,NOW())
|);
+ my $id = md5_hex($timer->{channel} . $nexttime->{start} . $nexttime->{stop} );
$sth->execute(
- $timer->{channel},$nexttime->{start},$nexttime->{stop},
+ $id,
$timer->{pos},
$timer->{flags},
$timer->{channel},
@@ -1111,8 +1166,9 @@ q|REPLACE INTO TIMERS VALUES
$aid,
$checked
) or return error sprintf("Couldn't execute query: %s.",$sth->errstr);
-}
+ $self->{keywords}->insert('timer',$id,$keywords);
+}
# Read data
# ------------------
@@ -1129,6 +1185,7 @@ sub _readData {
my $oldTimers = &getDataByTable('TIMERS');
$obj->{dbh}->do('DELETE FROM TIMERS');
+ $obj->{keywords}->removesource('timer');
# read from svdrp
my $tlist = $obj->{svdrp}->command('lstt');
@@ -1165,6 +1222,7 @@ sub _readData {
# Get new timers by User
if($oldTimers or exists $obj->{changedTimer}) {
+
my $timers = $obj->getNewTimers($oldTimers);
foreach my $timerdata (@$timers) {
event sprintf('New timer "%s" with id: "%d"', $timerdata->{file}, $timerdata->{pos});
@@ -1219,26 +1277,56 @@ sub updated {
}
}
}
+
# ------------------
sub list {
# ------------------
my $obj = shift || return error('No object defined!');
my $watcher = shift || return error('No watcher defined!');
my $console = shift || return error('No console defined!');
- my $text = shift || '';
+ my $id = shift;
+ my $params = shift;
my $term;
- my $search = '';
- if($text and $text =~ /^[0-9a-f,_ ]+$/ and length($text) >= 32 ) {
- my @timers = split(/[^0-9a-f]/, $text);
+ my $search;
+ if($id and $id =~ /^[0-9a-f,_ ]+$/ and length($id) >= 32 ) {
+ my @timers = split(/[^0-9a-f]/, $id);
$search = sprintf(" AND t.id in (%s)",join(',' => ('?') x @timers));
foreach(@timers) { push(@{$term},$_); }
- } elsif($text) {
- my $query = buildsearch("t.file,(SELECT description FROM EPG as e WHERE t.eventid = e.eventid LIMIT 1)",$text);
- $search = sprintf('AND ( %s )', $query->{query});
- foreach(@{$query->{term}}) { push(@{$term},$_); }
}
+ return $obj->_list($watcher,$console,$search,$term,$params);
+}
+
+# ------------------
+sub search {
+# ------------------
+ my $obj = shift || return error('No object defined!');
+ my $watcher = shift || return error('No watcher defined!');
+ my $console = shift || return error('No console defined!');
+ my $text = shift || return $obj->list($watcher,$console);
+ my $params = shift;
+
+ my $term;
+ my $search;
+ my $query = buildsearch("t.file,(SELECT description FROM EPG as e WHERE t.eventid = e.eventid LIMIT 1)",$text);
+ $search = sprintf('AND ( %s )', $query->{query});
+ foreach(@{$query->{term}}) { push(@{$term},$_); }
+
+ return $obj->_list($watcher,$console,$search,$term,$params);
+}
+
+# ------------------
+sub _list {
+# ------------------
+ my $obj = shift || return error('No object defined!');
+ my $watcher = shift;
+ my $console = shift;
+ my $search = shift || '';
+ my $term = shift;
+ my $params = shift;
+ my $table = shift || '';
+
my %f = (
'id' => gettext('Service'),
'flags' => gettext('Status'),
@@ -1273,6 +1361,7 @@ SELECT SQL_CACHE
FROM
TIMERS as t,
CHANNELS as c
+ $table
WHERE
t.stoptime > NOW()
AND t.channel = c.Id
@@ -1312,20 +1401,33 @@ ORDER BY
my $fields = $sth->{'NAME'};
my $erg = $sth->fetchall_arrayref();
+
+ my $keywords;
+ my $keywordmax;
+ my $keywordmin;
+
unless($console->typ eq 'AJAX') {
+ my $md5;
map {
+ push(@$md5,$_->[0]);
$_->[4] = datum($_->[4],'weekday');
} @$erg;
+ ($keywords,$keywordmax,$keywordmin) = $obj->{keywords}->list('timer',$md5);
+
unshift(@$erg, $fields);
}
my @DVBCARDS = split(',',$obj->{DVBCardsTyp});
my $cards = scalar @DVBCARDS;
+ $console->setCall('tlist');
$console->table($erg, {
cards => $cards,
capacity => main::getModule('RECORDS')->{CapacityFree},
+ keywords => $keywords,
+ keywordsmax => $keywordmax,
+ keywordsmin => $keywordmin,
rows => $rows
});
}
@@ -2064,6 +2166,3 @@ sub suggest {
}
1;
-
-
-1;