root/tv_grab_au @ 191

Revision 191, 29.1 kB (checked in by lincoln, 7 years ago)

CPAN dependency diet

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl -w
2
3my $version = '0.3.2';
4
5# tv_grab_au
6# "Shepherd"
7# A wrapper for various Aussie TV guide data grabbers
8#
9# Use --help for command-line options.
10#
11# Shepherd is an attempt to reconcile many different tv_grab_au scripts and
12# make one cohesive reliable data set. It works by calling a series of
13# scripts that grab data from a large variety of sources, and then
14# analysing the resulting XML data sets and determining which of the many
15# is the most reliable.
16
17# Shepherd runs in 4 passes:
18#  pass 1: (tv_grab_au)  Checks that all components are up-to-date, auto-
19#                        updates if not.
20#                        Passes control onto shepherd
21#  pass 2: (shepherd)    calls grabbers to fill in missing data
22#  pass 3: (shepherd)    calls reconciler to reconcile overlapping data
23#                        and normalize programme titles to our preferred title
24#  pass 4: (shepherd)    calls postprocessors to postprocess data
25#                        (e.g. flag HDTV programmes, augment with IMDb etc.)
26
27# Changelog:
28# 0.2.31  : split tv_grab_au (install/test/upgrade/enable/disable) from
29#           shepherd (grab/reconcile/postprocess). Previous changelog history
30#           is in shepherd
31# 0.3.0   : This split deserves a real version bump, Linc! :)
32# 0.3.1   : honour $option_ready
33
34BEGIN { *CORE::GLOBAL::die = \&my_die; }
35
36use strict;
37no strict 'refs';
38
39use LWP::UserAgent;
40use Cwd;
41use Getopt::Long;
42use Data::Dumper;
43use XMLTV::Ask;
44use POSIX qw(strftime mktime);
45use Compress::Zlib;
46
47# ---------------------------------------------------------------------------
48# --- Global Variables
49# ---------------------------------------------------------------------------
50
51my $myprogname = 'tv_grab_au';
52my $progname = 'shepherd';
53
54my $HOME = 'http://www.whuffy.com/shepherd';
55
56my $invoked = Cwd::realpath($0);
57my @options = @ARGV;
58
59# By default, Shepherd runs from ~/.shepherd/. If it's not run as a user,
60# it will try /opt/shepherd/ instead.
61my $CWD = ($ENV{HOME} ? $ENV{HOME} . "/." : "/opt/") . $progname;
62-d $CWD or mkdir $CWD or die "Cannot create directory $CWD: $!";
63chdir($CWD);
64
65my $ARCHIVE_DIR = "$CWD/archive";
66
67my $opt;
68my $pref_title_source;
69my $mirror_site;
70my $debug = 0;
71my $components = { };
72my $region;
73my $channels;
74my $opt_channels;
75my $config_file =   "$CWD/$progname.conf";
76my $channels_file = "$CWD/channels.conf";
77my $log_file = "$CWD/$progname.log";
78my $days = 7;
79
80# OBSOLETE: will be removed
81my $preferred;
82my $title_translation_table;
83my $pref_order;
84
85# ---------------------------------------------------------------------------
86# --- Setup
87# ---------------------------------------------------------------------------
88
89# Any options Shepherd doesn't understand, we'll pass to the grabber(s)
90Getopt::Long::Configure(qw/pass_through/);
91
92&get_initial_command_line_options;
93
94&capabilities if ($opt->{capabilities});
95&description if ($opt->{description});
96&version if ($opt->{version});
97
98$| = 1; 
99print ucfirst($myprogname) . " v$version\n\n";
100
101&help if ($opt->{help});
102
103&read_config_file;
104&read_channels_file;
105
106&get_remaining_command_line_options;
107
108if ($opt->{status})
109{
110    &status;
111    exit;
112}
113
114if ($opt->{show_config})
115{
116    &show_config;
117    exit;
118}
119
120&open_logfile unless ($opt->{nolog});
121
122&process_setup_commands;
123
124# ---------------------------------------------------------------------------
125# --- Update
126# ---------------------------------------------------------------------------
127
128unless ($opt->{noupdate})
129{
130    if (&update()) 
131    {
132        &write_config_file;
133    }
134}
135
136if ($opt->{configure})
137{
138    &configure;
139}
140
141# ---------------------------------------------------------------------------
142# --- Go!
143# ---------------------------------------------------------------------------
144
145unless ($opt->{update})
146{
147    write_config_file();
148    &log("Passing control to Shepherd: $CWD/shepherd @options\n");
149
150    call_prog("$CWD/shepherd @options");
151}
152
153&log("Done.\n");
154&close_logfile() unless $opt->{nolog};
155
156# ---------------------------------------------------------------------------
157# --- Subroutines
158# ---------------------------------------------------------------------------
159
160# -----------------------------------------
161# Subs: Updates & Installations
162# -----------------------------------------
163
164sub update
165{
166    &log("\nChecking for updates:\n\n");
167
168    my $data = fetch_shepherd_file("status");
169
170    return 0 unless ($data);
171
172    my $made_changes = 0;
173    my %clist = %$components;
174
175    while ($data =~ /(\S+)\s+(\S+)\s+(\S+)/g)
176    {
177        my ($progtype, $proggy, $latestversion) = ($1,$2,$3);
178        if (update_component($proggy, $latestversion, $progtype))
179        {
180            $made_changes = 1;
181        }
182        delete $clist{$proggy};
183    }
184
185    # work out what components disappeared (if any)
186    foreach (keys %clist) {
187        unless ($components->{$_}->{disabled}) {
188            &log("\nDeleted component: $_.\n");
189            disable($_, 2);
190            $made_changes = 1;
191        }
192    }
193    $made_changes;
194}
195
196sub update_component
197{
198    my ($proggy, $latestversion, $progtype) = @_;
199
200    my $ver = 0;
201
202    if ($progtype eq "shepherd")
203    {
204        if (-e "$CWD/$proggy")
205        {
206            $ver = `$CWD/$proggy --version`;
207            chop($ver);
208        }
209    } else {
210        $ver = $components->{$proggy}->{ver} if (defined $components->{$proggy} and -e ($progtype . "s/$proggy/$proggy"));
211    }
212
213    my $result = versioncmp($ver, $latestversion);
214    my $action =    $result == -1 ? ($ver ? "UPGRADING" : "NEW") :
215                    $result ==  1 ? "DOWNGRADING" :
216                    "up to date";
217    &log(sprintf    "* %-40s %30s\n",
218                    ucfirst($progtype) . " $proggy" .
219                        ($ver ? " v$ver" : '') . "...",
220                    $action);
221    return 0 unless ($result);
222    install($proggy, $latestversion, $progtype, $ver);
223    return 1;
224}
225
226sub install
227{
228    my ($proggy, $latestversion, $progtype, $oldver) = @_;
229    my $config;
230
231    &log("Downloading $proggy v$latestversion.\n");
232
233    my $rdir = "";
234    my $ldir = query_ldir($proggy, $progtype);
235
236    if ($progtype ne "shepherd") {
237        $rdir = $progtype . "s";
238        -d ("$CWD/$progtype" . "s") 
239            or mkdir ("$CWD/$progtype" . "s") 
240            or die "Cannot create directory $CWD/$progtype" . "s: $!";
241    }
242    -d $ldir or mkdir $ldir or die "Cannot create directory $ldir: $!";
243
244    my $newfile = "$ldir/$proggy-$latestversion";
245    my $rfile = "$rdir/$proggy";
246
247    return unless (fetch_shepherd_file($rfile, $newfile));
248
249    # Fetch grabber config file
250    if ($progtype eq 'grabber')
251    {
252        $rfile .= ".conf";
253        $config = fetch_shepherd_file($rfile);
254        return unless ($config); # grabbers MUST have config files
255        eval $config;
256    }
257
258    # Make component executable
259    chmod 0755,$newfile;
260
261    -d $ARCHIVE_DIR or mkdir $ARCHIVE_DIR or die "Cannot create directory $ARCHIVE_DIR: $!";
262
263    if (-e "$ldir/$proggy")
264    {
265        rename("$ldir/$proggy", "$ARCHIVE_DIR/$proggy-$oldver");
266    }
267    rename($newfile, "$ldir/$proggy");
268   
269    &log("Installed $proggy v$latestversion.\n") if ($debug);
270
271    # if the update was for shepherd itself, restart it
272    if ($progtype eq "shepherd") {
273        &log("\n*** Restarting ***\n\n");
274        &close_logfile unless $opt->{nolog};
275        exec("$CWD/$myprogname @options");
276        # This exits.
277        exit(0);
278    }
279
280    my $result = test_proggy($proggy, $progtype);
281
282    $components->{$proggy}->{type} = $progtype;
283    $components->{$proggy}->{ver} = $latestversion;
284    $components->{$proggy}->{ready} = $result;
285    $components->{$proggy}->{config} = $config if ($progtype eq 'grabber');
286
287    # If this component was disabled automatically, re-enable it.
288    # But if it was disabled manually, leave it off.
289    my $d = $components->{$proggy}->{disabled};
290    if ($d and $d == 2)
291    {
292        enable($proggy);
293    }
294
295    $components->{$proggy}->{laststatus} = sprintf "updated to %s on %s", $latestversion, (strftime "%a%d%b%y",localtime(time));
296
297}
298
299sub fetch_shepherd_file
300{
301    my ($fn, $store) = @_;
302
303    my $sites = "";
304    $sites = "$mirror_site," if ($mirror_site);
305    $sites .= $HOME;
306
307    my $ret;
308    foreach my $site (split(/,/,$sites)) 
309    {
310        $ret = fetch_file("$site/$fn", $store, 1);
311        return $ret if ($ret);
312    }
313    return undef;
314}
315
316sub test_proggy
317{
318    my ($proggy, $progtype) = @_;
319
320    &log("Testing $proggy...\n");
321
322    my $ldir = query_ldir($proggy, $progtype);
323    my $opt_ready = query_config($proggy, 'option_ready');
324    $opt_ready ||= '--version';
325   
326    chdir($ldir);
327    my $result = call_prog(query_filename($proggy, $progtype) . " $opt_ready");
328    chdir ($CWD);
329
330    print "Return value: $result\n" if ($debug);
331
332    if ($result)
333    {
334        &log("\n" . ucfirst($progtype) . " $proggy did not exit cleanly!\n" .
335             "It may require configuration.\n\n");
336    }
337    else
338    {
339        &log("OK.\n");
340    }
341    return !$result;
342}
343
344sub enable
345{
346    my $proggy = shift;
347
348    # confirm it exists first
349    if (!$components->{$proggy}) {
350        printf "No such component: \"%s\".\n",$proggy;
351        return;
352    }
353    print "Enabling $proggy.\n";
354
355    delete $components->{$proggy}->{disabled};
356    $components->{$proggy}->{laststatus} = sprintf "enabled on %s, not run yet",(strftime "%a%d%b%y", localtime(time));
357}
358
359sub disable
360{
361    my ($proggy, $n) = @_;
362
363    # confirm it exists first
364    if (!$components->{$proggy}) {
365        printf "No such component: \"%s\".\n",$proggy;
366        return;
367    }
368    print "Disabling $proggy.\n";
369   
370    $n ||= 1;
371    $components->{$proggy}->{disabled} = $n;
372    $components->{$proggy}->{laststatus} = sprintf "manually disabled on %s",(strftime "%a%d%b%y", localtime(time));
373}
374
375sub check
376{
377    my $result;
378    foreach my $proggy (keys %$components) {
379        my $progtype = $components->{$proggy}->{type};
380        $result = test_proggy($proggy, $components->{$proggy}->{type});
381        if (!$result ne !$components->{$proggy}->{ready}) {
382            $components->{$proggy}->{ready} = $result;
383        }
384    }
385}
386# -----------------------------------------
387# Subs: Utilities
388# -----------------------------------------
389#
390
391# versioncmp from Sort::Versions by Kenneth J. Albanowski
392sub versioncmp( $$ ) {
393    my @A = ($_[0] =~ /([-.]|\d+|[^-.\d]+)/g);
394    my @B = ($_[1] =~ /([-.]|\d+|[^-.\d]+)/g);
395
396    my ($A, $B);
397    while (@A and @B) {
398        $A = shift @A;
399        $B = shift @B;
400        if ($A eq '-' and $B eq '-') {
401            next;
402        } elsif ( $A eq '-' ) {
403            return -1;
404        } elsif ( $B eq '-') {
405            return 1;
406        } elsif ($A eq '.' and $B eq '.') {
407            next;
408        } elsif ( $A eq '.' ) {
409            return -1;
410        } elsif ( $B eq '.' ) {
411            return 1;
412        } elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
413            if ($A =~ /^0/ || $B =~ /^0/) {
414                return $A cmp $B if $A cmp $B;
415            } else {
416                return $A <=> $B if $A <=> $B;
417            }
418        } else {
419            $A = uc $A;
420            $B = uc $B;
421            return $A cmp $B if $A cmp $B;
422        }       
423    }
424    @A <=> @B;
425}
426
427sub query_grabbers
428{
429    my ($conf, $val) = @_;
430    return query_component_type('grabber',$conf,$val);
431}
432
433sub query_reconcilers
434{
435    return query_component_type('reconciler');
436}
437
438sub query_postprocessors
439{
440    return query_component_type('postprocessor');
441}
442
443sub query_component_type
444{
445    my ($progtype,$conf,$val) = @_;
446
447    my @ret = ();
448    foreach (keys %$components)
449    {
450        if ($components->{$_}->{type} eq $progtype) {
451            if (defined $conf) {
452                push (@ret, $_) if (query_config($_,$conf) eq $val);
453            } else {
454                push (@ret, $_);
455            }
456        }
457    }
458    return @ret;
459}
460
461sub query_name
462{
463    my $str = shift;
464    if ($str =~ /(.*) \[cache\]/)
465    {
466        return $1;
467    }
468    return $str;
469}
470
471sub query_filename
472{
473    my ($proggy, $progtype) = @_;
474
475    return query_ldir($proggy, $progtype) . "/$proggy";
476}
477
478sub query_ldir
479{
480    my ($proggy, $progtype) = @_;
481    my $ret = $CWD;
482    $ret .= ('/' . $progtype . "s/$proggy") if ($progtype ne 'shepherd');
483    return $ret;
484}
485
486sub query_config
487{
488    my ($grabber, $key) = @_;
489
490    $grabber = query_name($grabber);
491    return undef unless ($components->{$grabber});
492    return $components->{$grabber}->{config}->{$key};
493}
494
495sub rotate_logfiles
496{
497    # keep last 4 log files
498    my $num;
499    for ($num = 4; $num > 0; $num--) {
500        my $f1 = sprintf "%s.%d.gz",$log_file,$num;
501        my $f2 = sprintf "%s.%d.gz",$log_file,$num+1;
502        unlink($f2);
503        rename($f1,$f2);
504    }
505
506    my $f2 = sprintf "%s.1",$log_file;
507    rename($log_file,$f2);
508}
509
510sub compress_file
511{
512    my $infile = shift;
513    my $outfile = sprintf "%s.gz",$infile;
514    my $gz;
515
516    if (!(open(INFILE,"<$infile"))) {
517        warn "could not open file $infile for reading: $!\n";
518        return;
519    }
520
521    if (!($gz = gzopen($outfile,"wb"))) {
522        warn "could not open file $outfile for writing: $!\n";
523        return;
524    }
525
526    while (<INFILE>) {
527        my $byteswritten = $gz->gzwrite($_);
528        warn "error writing to compressed file: error $gz->gzerror"
529          if ($byteswritten == 0);
530    }
531    close(INFILE);
532    $gz->gzclose();
533    unlink($infile);
534}
535
536sub open_logfile
537{
538    &rotate_logfiles;
539    printf "Logging to $log_file.\n";
540    open(LOG_FILE,">>$log_file") || die "can't open log file $log_file for writing: $!\n";
541
542    my $now = localtime(time);
543    printf LOG_FILE "$myprogname version $version started at $now\n\n";
544
545    compress_file($log_file.".1");
546}
547
548sub close_logfile
549{
550    close(LOG_FILE);
551}
552
553sub log
554{
555    my $entry = shift;
556    print $entry;
557    printf LOG_FILE "%s",$entry unless $opt->{nolog};
558}
559
560sub call_prog
561{
562    my $prog = shift;
563    if (!(open(PROG,"$prog|"))) {
564        &log("warning: couldn't exec \"$prog\": $!\n");
565        return -1;
566    }
567    while(<PROG>) {
568        &log($_);
569    }
570    close(PROG);
571
572    if ($? == -1) {
573        &log("Failed to execute prog: $!\n");
574        return -1;
575    } elsif ($? & 127) {
576        &log((sprintf "prog died with signal %d, %s coredump\n",
577          ($? & 127),  ($? & 128) ? "with" : "without"));
578        return $?;
579    } else {
580        &log((sprintf "prog exited with value %d\n", $? >> 8)) if ($debug or $?);
581        return ($? >> 8);
582    }
583}
584
585sub fetch_file
586{
587    my ($url, $store, $id_self) = @_;
588
589    &log("Fetching $url.\n");
590   
591    my $ua = LWP::UserAgent->new();
592    if ($id_self)
593    {
594        $ua->agent(ucfirst("$progname/$myprogname/$version"));
595    }
596    else
597    {
598        $ua->agent('Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322')
599    }
600
601    my $response = $ua->get($url);
602    if ($response->is_success())
603    {
604        if ($store)
605        {
606            open (FILE, ">$store") 
607                or (&log("ERROR: Unable to open $store for writing.\n") and return undef);
608            print FILE $response->content();
609            close FILE;
610            return 1;
611        }
612        else 
613        {
614            return $response->content();
615        } 
616    }
617    &log("Failed to retrieve $url!\n" . $response->status_line() . "\n");
618    return undef;
619}
620
621# -----------------------------------------
622# Subs: Setup
623# -----------------------------------------
624
625sub read_config_file
626{
627    read_file($config_file, 'configuration');
628}
629
630sub read_channels_file
631{
632    read_file($channels_file, 'channels');
633}
634
635sub read_file
636{
637    my $fn = shift;
638    my $name = shift;
639
640    print "Reading $name file: $fn\n";
641    unless (-r $fn)
642    {
643        unless ($opt->{configure})
644        {
645            print "\nNo $name file found.\n" .
646                  ucfirst($progname) . " must be configured: " .
647                  "configuring now.\n\n";
648            $opt->{'configure'} = 1;
649        }
650        return;
651    }
652    local (@ARGV, $/) = ($fn);
653    no warnings 'all';
654    eval <>;
655    if ($@ and !$opt->{configure})
656    {
657        warn "\nERROR in $name file! Details:\n$@";
658        print "You may wish to CTRL-C and fix this.\n\nContinuing anyway in:";
659        foreach (1 .. 5)
660        {
661            print " " . (6 - $_);
662            sleep 1;
663        }
664        print "\n";
665    }
666}
667
668sub write_config_file
669{
670    write_file($config_file, 'configuration', 
671        [$region,  $pref_title_source,  $mirror_site,  $components ],
672        ["region", "pref_title_source", "mirror_site", "components" ]);
673}
674
675sub write_channels_file
676{
677    write_file($channels_file, 'channels',
678        [ $channels,  $opt_channels ],
679        [ 'channels', 'opt_channels' ]);
680}
681
682sub write_file
683{
684    my ($fn, $name, $vars, $varnames) = @_;
685    open (FN, ">$fn") or die "Can't write to $name file $fn: $!";
686    print FN Data::Dumper->Dump($vars, $varnames);
687    close FN;
688    print "Wrote $name file $fn.\n" if ($debug);
689}
690
691sub get_initial_command_line_options
692{
693  GetOptions( 'config-file=s'   => \$opt->{configfile},
694              'help'            => \$opt->{help},
695              'configure'       => \$opt->{configure},
696              'setmirror=s'     => \$opt->{setmirror},
697              'setpreftitle=s'  => \$opt->{setpreftitlesource},
698              'clearpreftitle'  => \$opt->{clearpreftitlesource},
699              'dontcallgrabbers' => \$opt->{dontcallgrabbers},
700             
701              # http://xmltv.org/wiki/xmltvcapabilities.html
702              'capabilities'    => \$opt->{capabilities},
703              'description'     => \$opt->{description},
704              'quiet'           => \$opt->{quiet},
705              'version'         => \$opt->{version},
706
707              'debug'           => \$debug);
708}
709
710sub get_remaining_command_line_options
711{
712    GetOptions(
713              'status'          => \$opt->{status},
714              'list'            => \$opt->{list},
715              'show-config'     => \$opt->{show_config},
716
717              'update'          => \$opt->{update},
718              'noupdate'        => \$opt->{noupdate},
719
720              'disable=s'       => \$opt->{disable},
721              'enable=s'        => \$opt->{enable},
722
723              'nolog'           => \$opt->{nolog},
724
725              'days=i'          => \$days,
726              'offset=i'        => \$opt->{offset},
727              'show-channels'   => \$opt->{show_channels},
728              'output=s'        => \$opt->{output},
729              'randomize'       => \$opt->{randomize}, # experimental
730              'check'           => \$opt->{check}
731            );
732}
733
734sub process_setup_commands
735{
736    my @opts = qw( enable disable setorder check \
737                   setpreftitlesource clearpreftitlesource setmirror );
738
739    my $run = 0;
740    foreach (@opts)
741    {
742        if ($opt->{$_})
743        {
744            $run = 1;
745            &$_($opt->{$_});
746        }
747    }
748    return unless ($run);
749    write_config_file();
750    status();
751    exit;
752}
753
754# if a preferred title source has been specified, add it to our config
755sub setpreftitlesource
756{
757    my $arg = shift;
758    $pref_title_source = $arg;
759    print "Added preferred title source: $pref_title_source\n";
760    1;
761}
762
763# if requesting to clear preferred title and we have one, remove it
764sub clearpreftitlesource
765{
766    $pref_title_source = undef;
767    print "Removed preferred title source $pref_title_source\n";
768    1;
769}
770
771# if a mirror has been specified, add it into our config
772sub setmirror
773{
774    my $arg = shift;
775    $mirror_site = $arg;
776    print "Setting mirror site(s): $mirror_site\n";
777}
778
779# -----------------------------------------
780# Subs: Configuration
781# -----------------------------------------
782
783sub configure
784{
785    my $REGIONS = {
786        "ACT" => 126,
787        "NSW: Sydney" => 73,
788        "NSW: Newcastle" => 184,
789        "NSW: Central Coast" => 66,
790        "NSW: Griffith" => 67,
791        "NSW: Broken Hill" => 63,
792        "NSW: Northern NSW" => 69,
793        "NSW: Southern NSW" => 71,
794        "NSW: Remote and Central" => 106,
795        "NT: Darwin" => 74,
796        "NT: Remote & Central" => 108,
797        "QLD: Brisbane" => 75,
798        "QLD: Gold Coast" => 78,
799        "QLD: Regional" => 79,
800        "QLD: Remote & Central" => 114,
801        "SA: Adelaide" => 81,
802        "SA: Renmark" => 82,
803        "SA: Riverland" => 83,
804        "SA: South East SA" => 85,
805        "SA: Spencer Gulf" => 86,
806        "SA: Remote & Central" => 107,
807        "Tasmania" => 88,
808        "VIC: Melbourne" => 94,
809        "VIC: Geelong" => 93,
810        "VIC: Eastern Victoria" => 90,
811        "VIC: Mildura/Sunraysia" => 95,
812        "VIC: Western Victoria" => 98,
813        "WA: Perth" => 101,
814        "WA: Regional" => 102
815    };
816
817    print "\nConfiguring.\n\n" .
818          "Select your region:\n";
819    foreach (sort keys %$REGIONS)
820    {
821        printf(" (%3d) %s\n", $REGIONS->{$_}, $_);
822    }
823    $region = ask_choice("Enter region code:", "94", values %$REGIONS);
824
825    print "\nFetching channel information... ";
826
827    my @channellist = get_channels();
828
829    print "done.\n\n" .
830          "For each channel you want guide data for, enter an XMLTV id\n" .
831          "of your choice (e.g. \"seven.free.au\"). If you don't need\n" .
832          "guide data for this channel, just press Enter.\n\n" .
833          "Please don't subscribe to unneeded channels.\n\nChannels:\n";
834    $channels = {};
835    my $line;
836    foreach (@channellist)
837    {
838        $line = ask(" \"$_\"? ");
839        $channels->{$_} = $line if ($line);
840    }
841
842    print "\nHigh Definition TV (HDTV)\n".
843          "Most Australian TV networks broadcast at least some\n".
844          "programmes in HDTV each week, but for the most part\n".
845          "either upsample SD to HD or play a rolling demonstration\n".
846          "HD clip when they don't have the programme in HD format.\n\n".
847          "If you have a HDTV capable system and are interested in\n".
848          "having Shepherd's postprocessors populate HDTV content\n".
849          "then Shepherd will need to know the XMLTV IDs for the HD\n".
850          "channels also.\n";
851    if (ask_boolean("\nDo you wish to include HDTV channels?")) {
852        print "\nFor each channel you want guide data for, enter an XMLTV id\n" .
853              "of your choice (e.g. \"sevenhd.free.au\"). If you don't need\n" .
854              "guide data for this channel, just press Enter.\n\n";
855
856        $opt_channels = {};
857        foreach (@channellist)
858        {
859            next if (($_ =~ /ABC2/i) || ($_ =~ /SBS News/i) || ($_ =~ /31/));
860            $_ .= "HD";
861            $line = ask(" \"$_\"? ");
862            $opt_channels->{$_} = $line if ($line);
863        }
864    }
865
866
867    print "\nWould you like to transition seamlessly from your current grabber?\n\n".
868          "Different data sources can have different names for the same show. For\n".
869          "example, one grabber might call a show \"Spicks & Specks\" while another\n".
870          "calls it \"Spicks and Specks\". These differences can make MythTV think\n".
871          "they're actually different shows.\n\n".
872          ucfirst($progname) . " is able to merge these differences so that it always\n".
873          "presents shows with a consistent name, no matter where it actually sourced\n".
874          "show data from. If you'd like, it can also rename shows so they're consistent\n".
875          "with whichever grabber you've been using until now.\n\n".
876          "The advantage of this is that you should get a smoother transition to\n".
877          ucfirst($progname) . ", with no shows changing names and no need to re-create\n".
878          "any recording rules. The main disadvantage is that if your previous grabber\n".
879          "used an inferior data source -- i.e. it sometimes has typos or less\n".
880          "informative program names -- then you'll continue to see these.\n\n".
881          "If you were using one of the following grabbers previously AND you want\n".
882          ucfirst($progname) . " to use that grabber's program names, select it here.\n\n";
883
884    my $def = "Do not transition; just use best quality titles";
885    my %transition = (  "ltd (aka tv_grab_au, versions 1,30, 1.40 or 1.41)" => "yahoo7widget,abc2_website",
886                        "OzTivo" => 'oztivo',
887                        "Rex" => 'rex',
888                        "JRobbo" => 'jrobbo' );
889    my $pref = ask_choice("Transition from grabber?", $def,
890                $def, keys %transition);
891    $pref_title_source = $transition{$pref};
892   
893    print "\n";
894    show_channels();
895    unless(ask_boolean("\nCreate configuration file?"))
896    {
897        print "Aborting configuration.\n";
898        exit 0;
899    }
900
901    write_config_file();
902    write_channels_file();
903
904    print "Finished configuring.\n\n" .
905          "Shepherd is installed into $CWD.\n\n";
906   
907    if ($invoked ne "$CWD/$myprogname" and $invoked =~ /$myprogname/)
908    {
909        print "Warning: you invoked this program as $invoked.\n" .
910            "In the future, it should be run as $CWD/$myprogname,\n" .
911            "to avoid constantly re-downloading the latest version.\n\n" .
912            "MythTV users may wish to create the following symlink, by " .
913            "doing this (as root):\n" .
914            "\"ln -s $CWD/$myprogname /usr/bin/tv_grab_au\".\n\n" .
915            "You may safely delete $invoked.\n\n";
916    }
917
918    status();
919
920    unless (ask_boolean("\nGrab data now?"))
921    {
922        exit 0;
923    }
924}
925
926sub get_channels
927{
928    my @date = localtime;
929    my $page = fetch_file(
930        "http://au.tv.yahoo.com/results.html?rg=$region&dt=" .
931        ($date[5] + 1900) . "-$date[4]-$date[3]");
932    my @channellist;
933    while ($page =~ /<tr class=rtb><td class=rth><a .*?>(.*?)<\/a>/g)
934    {
935        push @channellist, $1;
936    }
937    return @channellist;
938}
939
940# -----------------------------------------
941# Subs: Status & Help
942# -----------------------------------------
943
944sub show_config
945{
946    print "\nConfiguration\n".
947          "-------------\n" .
948          "Config file: $config_file\n" .
949          "Debug mode : " . is_set($debug) . "\n" .
950          "Output file: " . ($opt->{output} ? $opt->{output} : "None") . "\n" .
951          "Region ID  : $region\n";
952  show_channels();
953  print "\n";
954  status();
955  print "\n";
956}
957
958sub show_channels
959{
960  print "Subscribed channels:\n";
961  print "    $_ -> $channels->{$_}\n" for sort keys %$channels;
962  print "Optional (HDTV) channels:\n";
963  print "    $_ -> $opt_channels->{$_}\n" for sort keys %$opt_channels;
964}
965
966sub is_set
967{
968    my $arg = shift;
969    return $arg ? "Yes" : "No";
970}
971
972sub pretty_print
973{
974    my ($p, $len) = @_;
975    my $spaces = ' ' x (79-$len);
976    my $ret = "";
977
978    while (length($p) > 0) {
979        if (length($p) <= $len) {
980            $ret .= $p;
981            $p = "";
982        } else {
983            # find a space to the left of cutoff
984            my $len2 = $len;
985            while ((substr($p,$len2,1) ne ' ') && ($len2 > 0)) {
986                $len2--;
987            }
988            if ($len2 == 0) {
989                # no space - just print it with cutoff
990                $ret .= substr($p,0,$len);
991                $p = substr($p,$len,(length($p)-$len));
992            } else {
993                # print up to space
994                $ret .= substr($p,0,$len2);
995                $p = substr($p,($len2+1),(length($p)-$len2+1));
996            }
997            # print whitespace
998            $ret .= "\n".$spaces;
999        }
1000    }
1001    return $ret;
1002}
1003
1004sub status
1005{
1006    print "\nThe following plugins are known:\n",
1007          " Type     Name           Version Description\n".
1008          " -------- -------------- ------- ----------------------------------------------\n";
1009
1010    foreach (sort { $components->{$a}->{type} cmp $components->{$b}->{type} } keys %{$components}) {
1011        printf " %-8s %-15s%7s %46s\n",
1012          substr($components->{$_}->{type},0,8), 
1013          length($_) > 15 ? substr($_,0,13).".." : $_,
1014          ($components->{$_}->{ver} ? substr($components->{$_}->{ver},0,7) : "unknown"),
1015          (defined $components->{$_}->{config}->{desc} ?
1016            pretty_print($components->{$_}->{config}->{desc},46) : "");
1017    }
1018    printf "\n";
1019
1020    print "Grabbers, listed in order of quality:\n".
1021          "                   Enabled/\n".
1022          " Grabber        Qual Ready Last Run   Status\n" .
1023          " -------------- ---- ----- ---------- -----------------------------------------\n";
1024    my %qual_table = ( 3 => "Best", 2 => "Good", 1 => "Avg" );
1025    foreach (sort { $components->{$b}->{config}->{quality} <=> $components->{$a}->{config}->{quality} } query_grabbers()) {
1026        my $h = $components->{$_};
1027        printf  " %-15s%-4s  %1s/%1s %11s %s\n",
1028          length($_) > 15 ? substr($_,0,13).".." : $_,
1029          $qual_table{($h->{config}->{quality})},
1030          $h->{disabled} ? 'N' : 'Y',
1031          $h->{ready} ? 'Y' : 'N',
1032          $h->{lastdata} ? (strftime "%a%d%b%y", localtime($h->{lastdata})) : 'never',
1033          $h->{laststatus} ? pretty_print($h->{laststatus},41) : '';
1034    }
1035
1036    print "\n".
1037          "              Enabled/\n".
1038          " Reconciler     Ready Last Run   Status\n" .
1039          " -------------- ----- ---------- ----------------------------------------------\n";
1040    foreach (sort { $components->{$a} <=> $components->{$b} } query_reconcilers()) {
1041        my $h = $components->{$_};
1042        printf  " %-15s %1s/%1s %11s %s\n",
1043          length($_) > 15 ? substr($_,0,13).".." : $_,
1044          $h->{disabled} ? 'N' : 'Y',
1045          $h->{ready} ? 'Y' : 'N',
1046          $h->{lastdata} ? (strftime "%a%d%b%y", localtime($h->{lastdata})) : 'never',
1047          $h->{laststatus} ? pretty_print($h->{laststatus},46) : '';
1048    }
1049
1050    print "\n".
1051          "              Enabled/\n".
1052          " Postprocessor  Ready Last Run   Status\n" .
1053          " -------------- ----- ---------- ----------------------------------------------\n";
1054    foreach (sort { $components->{$a} <=> $components->{$b} } query_postprocessors()) {
1055        my $h = $components->{$_};
1056        printf  " %-15s %1s/%1s %11s %s\n",
1057          length($_) > 15 ? substr($_,0,13).".." : $_,
1058          $h->{disabled} ? 'N' : 'Y',
1059          $h->{ready} ? 'Y' : 'N',
1060          $h->{lastdata} ? (strftime "%a%d%b%y", localtime($h->{lastdata})) : 'never',
1061          $h->{laststatus} ? pretty_print($h->{laststatus},46) : '';
1062    }
1063    printf "\nPreferred titles from grabber '%s'\n",$pref_title_source if ($pref_title_source);
1064    printf "\n";
1065}
1066
1067sub capabilities
1068{
1069    print "baseline\nmanualconfig\n";
1070    exit 0;
1071}
1072
1073sub description
1074{
1075    print "Australia\n";
1076    exit 0;
1077}
1078
1079sub version
1080{
1081    print "$version\n";
1082    exit 0;
1083}
1084
1085sub help
1086{
1087    print q{
1088Command-line options:
1089    --help                Print this message
1090
1091    --status              Print a list of grabbers maintained
1092    --list                Print a detailed list of grabbers
1093    --setmirror <s>       Set URL <s> as primary location to check for updates
1094
1095    --configure           Setup
1096    --show-config         Print setup details
1097
1098    --setpreftitle <s>    Set preferred 'title' source as grabber <s>
1099    --clearpreftitle      clear preferred 'title' source
1100
1101    --disable <s>         Don't ever use grabber/postprocessor <s>
1102    --enable <s>          Okay, maybe use it again then
1103    --uninstall <s>       Remove a disabled grabber/postprocessor
1104
1105    --noupdate            Do not attempt to update before running
1106    --update              Update only; do not grab data
1107
1108    --check               Check status of all grabbers and postprocessors
1109
1110    --capabilities        Report capabilities to XMLTV
1111
1112    --nolog               Don't write a logfile
1113};
1114    exit 0;
1115}
1116
1117# -----------------------------------------
1118# Subs: override handlers for standard perl.
1119# -----------------------------------------
1120
1121# ugly hack. please don't try this at home kids!
1122sub my_die {
1123    my ($arg,@rest) = @_;
1124    my ($pack,$file,$line,$sub) = caller(0);
1125
1126    # check if we are in an eval()
1127    if ($^S) {
1128        printf STDERR "  shepherd caught a die() within eval{} from file $file line $line\n";
1129    } else {
1130            printf STDERR "\nDIE: line %d in file %s\n",$line,$file;
1131            if ($arg) {
1132                CORE::die($arg,@rest);
1133            } else {
1134                CORE::die(join("",@rest));
1135            }
1136    }
1137}
Note: See TracBrowser for help on using the browser.