diff options
| author | Andreas Brachold <vdr07@deltab.de> | 2008-06-15 06:21:31 +0000 |
|---|---|---|
| committer | Andreas Brachold <vdr07@deltab.de> | 2008-06-15 06:21:31 +0000 |
| commit | 0b70320be6c8a4e13e02c70f7644678157d703ee (patch) | |
| tree | ea16e3ce9a6b325c6d8287955158f406b4ba5d21 /lib | |
| parent | 217af3aa226018610f812b2deb41dddd82203dc9 (diff) | |
| download | xxv-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.pm | 96 | ||||
| -rw-r--r-- | lib/XXV/MODULES/KEYWORDS.pm | 319 | ||||
| -rw-r--r-- | lib/XXV/MODULES/MUSIC.pm | 1 | ||||
| -rw-r--r-- | lib/XXV/MODULES/RECORDS.pm | 143 | ||||
| -rw-r--r-- | lib/XXV/MODULES/TIMERS.pm | 161 |
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; |
