#!/usr/bin/perl -w use strict; use Cwd 'abs_path'; use File::Spec; use Getopt::Long; use XMLTV; use XMLTV::Version "$XMLTV::VERSION"; # How long shall a grabber have to respond to our calls in seconds? my $CMD_TIMEOUT = 15; =pod =head1 NAME tv_find_grabbers - Find all XMLTV grabbers that are installed on the system. =head1 SYNOPSIS tv_find_grabbers --help tv_find_grabbers [-I ] [--slow] [capability] ... =head1 DESCRIPTION tv_find_grabbers searches the PATH for XMLTV grabbers and returns a list of all grabbers that it finds. The list contains one entry per line in the format /usr/bin/tv_grab_fr|France i.e. the name of the executable and the region that it serves, separated by a vertical bar. =head1 OPTIONS -I Include a directory in the search for grabbers. May be used multiple times. The default is to search the PATH. --slow When checking grabbers, compile and run them instead of searching their source code for capabilities and description --verbose Print progress information to STDERR. =head1 AUTHOR Mattias Holmlund, mattias -at- holmlund -dot- se. =cut my $opt = { "include" => [], help => 0, verbose => 0, slow => 0, }; my $res = GetOptions( $opt, qw/ include|I=s help|h verbose|v slow|s / ); if( (not $res) or $opt->{help} ) { print << "EOHELP"; Usage: $0 [-I dir] [capability] ... EOHELP exit 1; } my( @req_cap ) = ("baseline", @ARGV); my @paths = File::Spec->path(); push @paths, @{$opt->{include}}; # Find only unique entries in PATH to avoid investigating the same # grabber twice. From "perldoc -q duplicate". my %seen = (); my @unique = grep { ! $seen{ abs_path($_) }++ } @paths; foreach my $p (@unique) { print STDERR "Searching in $p\n" if $opt->{verbose}; next if (!opendir(DIR, $p)); my @grabbers = grep(/^tv_grab_/, readdir(DIR)); closedir(DIR); foreach my $grabber (@grabbers) { $grabber = File::Spec->catfile ($p, $grabber); print STDERR "Investigating $grabber\n" if $opt->{verbose}; my $cap = undef; my $cap_src = undef; open GRABBER, "<", $grabber; unless ($opt->{slow}) { while (my $line = ) { # First read the grabber script and try to determine the capabilities # it supports - first for older grabbers using XMLTV::Capabilities if ($line =~ m{^use\s+XMLTV::Capabilities\s+qw/(.*)/;}) { $cap = $1; $cap_src = "source"; last; } # and second for newer grabbers using XMLTV::Options elsif ($line =~ m{capabilities\s+=>\s+\[qw/(.*)/\]}) { $cap = $1; $cap_src = "source"; last; } } } # Having not found the capabilities by checking the code directly, we # compile and run the grabber and capture the output if (not defined $cap) { $cap = run_capture( "$grabber --capabilities 2>/dev/null" ); $cap_src = "run_capture"; } if (not defined $cap) { close GRABBER; print STDERR " No capabilities found...\n" if $opt->{verbose}; next; } else { print STDERR " Found capabilities ($cap_src): $cap\n" if $opt->{verbose}; } my @capabilities = split( /\s+/, $cap ); my %capability; foreach my $c (@capabilities) { $capability{$c} = 1; } my $failed = 0; foreach my $c (@req_cap) { $failed=1 if not defined( $capability{$c} ); } if ($failed) { close GRABBER; next; } my $desc = undef; my $desc_src = undef; seek GRABBER, 0, 0; # reset to start of file unless ($opt->{slow}) { while (my $line = ) { # Now read the grabber script and try to determine its description # - first for older grabbers using XMLTV::Description if ($line =~ m{^use\s+XMLTV::Description\s+["|'](.*)["|'];}) { $desc = $1; $desc_src = "source"; last; } # and second for newer grabbers using XMLTV::Options elsif ($line =~ m{description\s+=>\s+["|'](.*)["|']}) { $desc = $1; $desc_src = "source"; last; } } } # Having not found the description by checking the code directly, we # compile and run the grabber and capture the output if (not defined $desc) { $desc = run_capture( "$grabber --description 2>/dev/null" ); $desc_src = "run_capture"; } if (not defined $desc) { close GRABBER; print STDERR " No description found...\n" if $opt->{verbose}; next; } else { print STDERR " Found description ($desc_src): $desc\n" if $opt->{verbose}; } $desc =~ s/^\s+//; $desc =~ s/\s+$//; print "$grabber|$desc\n"; close GRABBER; } } # Run an external command and return the output. Exit if the command is # interrupted with ctrl-c. sub run_capture { my( $cmd ) = @_; # print "Running $cmd\n"; my $killed = 0; my $result; # Set a timer and run the real command. eval { local $SIG{ALRM} = sub { # ignore SIGHUP here so the kill only affects children. local $SIG{HUP} = 'IGNORE'; kill 1,(-$$); $killed = 1; }; alarm $CMD_TIMEOUT; $result = qx/$cmd/; alarm 0; }; $SIG{HUP} = 'DEFAULT'; if( $killed ) { print STDERR "Timeout from: $cmd\n"; return undef; } if ($? == -1) { return undef; } elsif ($? & 127) { exit 1; } if( $? >> 8 ) { return undef; } else { return $result; } } =head1 COPYRIGHT Copyright (C) 2005 Mattias Holmlund. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =cut ### Setup indentation in Emacs ## Local Variables: ## perl-indent-level: 4 ## perl-continued-statement-offset: 4 ## perl-continued-brace-offset: 0 ## perl-brace-offset: -4 ## perl-brace-imaginary-offset: 0 ## perl-label-offset: -2 ## cperl-indent-level: 4 ## cperl-brace-offset: 0 ## cperl-continued-brace-offset: 0 ## cperl-label-offset: -2 ## cperl-extra-newline-before-brace: t ## cperl-merge-trailing-else: nil ## cperl-continued-statement-offset: 2 ## indent-tabs-mode: t ## End: