root/shepherd @ 1

Revision 1, 15.7 kB (checked in by max, 7 years ago)

Initial import

Line 
1#!/usr/bin/perl -w
2
3# "Shepherd"
4
5my $version = '0.2.3';
6
7# A wrapper for various Aussie TV guide data grabbers
8#
9# Use --help for command-line options.
10# See shepherd.txt for details.
11#
12#  A current version of this script, plus a README file, might be here:
13#  http://www.whuffy.com/tv_grab_au/
14#
15# Changelog:
16# 0.1.0   : Basic self-updating and grabber management
17# 0.2.0   : --configure
18# 0.2.1   : Has a home in ~/.shepherd/
19# 0.2.2   : --check
20# 0.2.3   : Bugfix: archives correctly
21#
22# ToDo:
23# * --mirror <url> option, to let you get software from somewhere other
24#   than whuffy. Store this in shepherd.conf. Whuffy is merely the first
25#   mirror.
26# * parse that XMLTV output and do something smart with it!
27# * Make it check compilation after installing by calling --version or
28#   --desc or --ready
29# * --ready  option that says whether config is required?
30# * "Last Data" column in status
31
32use strict;
33use LWP::Simple;
34use Sort::Versions;
35use Cwd;
36use Getopt::Long;
37use Data::Dumper;
38use XMLTV;
39use XMLTV::Ask;
40
41# ---------------------------------------------------------------------------
42# --- Global Variables
43# ---------------------------------------------------------------------------
44
45my $progname = 'shepherd';
46
47my $HOME = 'http://www.whuffy.com';
48my $STATUS = "$HOME/status";
49
50my $invoked = Cwd::realpath($0);
51
52# By default, Shepherd runs from ~/.shepherd/. If it's not run as a user,
53# it will try /opt/shepherd/ instead.
54my $CWD = ($ENV{HOME} ? $ENV{HOME} . "/." : "/opt/") . $progname;
55-d $CWD or mkdir $CWD or die "Cannot create directory $CWD: $!";
56chdir($CWD);
57
58my $GRABBER_DIR = "$CWD/grabbers";
59my $ARCHIVE_DIR = "$CWD/archive";
60
61my $opt;
62my $debug = 1;
63my $grabbers = { };
64my $region;
65my $channels;
66my $preferred;
67my $config_file =   "$CWD/$progname.conf";
68my $channels_file = "$CWD/channels.conf";
69my $days;
70
71# ---------------------------------------------------------------------------
72# --- Setup
73# ---------------------------------------------------------------------------
74
75print ucfirst($progname) . " v$version\n\n";
76#print "Cwd: $CWD.\n";
77
78# Any options Shepherd doesn't understand, we'll pass to the grabber(s)
79Getopt::Long::Configure(qw/pass_through/);
80
81get_initial_command_line_options();
82
83help() if ($opt->{help});
84
85unless ($opt->{configure})
86{
87    read_config_file();
88    read_channels_file();
89}
90
91get_remaining_command_line_options();
92
93if ($opt->{status})
94{
95    status();
96    exit;
97}
98
99if ($opt->{show_config})
100{
101    show_config();
102    exit;
103}
104
105if ($opt->{enable})
106{
107    enable($opt->{enable});
108}
109
110if ($opt->{disable})
111{
112    disable($opt->{disable});
113}
114
115if ($opt->{prefer})
116{
117    set_preferred($opt->{prefer});
118}
119
120if ($opt->{check})
121{
122    check();
123}
124
125if ($opt->{enable} or $opt->{disable} or $opt->{prefer} or $opt->{check})
126{
127    write_config_file();
128    status();
129    exit;
130}
131
132# ---------------------------------------------------------------------------
133# --- Update
134# ---------------------------------------------------------------------------
135
136unless ($opt->{noupdate})
137{
138    update($progname, $version);
139    write_config_file() unless ($opt->{configure});
140}
141
142if ($opt->{configure})
143{
144    configure();
145}
146
147# ---------------------------------------------------------------------------
148# --- Go!
149# ---------------------------------------------------------------------------
150
151unless ($opt->{update})
152{
153    grab_data();
154}
155
156print "Done.\n";
157
158status();
159
160# ---------------------------------------------------------------------------
161# --- Subroutines
162# ---------------------------------------------------------------------------
163
164# -----------------------------------------
165# Subs: Grabbing
166# -----------------------------------------
167
168sub grab_data
169{
170    my $grabber = choose_grabber();
171
172    unless ($grabber)
173    {
174        print "No valid grabbers installed/enabled!\n";
175        return;
176    }
177    print "\nUsing grabber: $grabber.\n\n";
178
179    my $output = $opt->{output};
180    $output ||= "$CWD/output.xmltv";
181    my $comm = "$GRABBER_DIR/$grabber/$grabber " .
182               "--region $region " .
183               "--channels_file $channels_file " .
184               "--output $output";
185    $comm .= " --days $days" if ($days);
186    $comm .= " --offset $opt->{offset}" if ($opt->{offset});
187    $comm .= " --debug" if ($debug);
188    $comm .= " @ARGV" if (@ARGV);
189    print "Excuting command:\n$comm\n\n";
190
191    chdir "$GRABBER_DIR/$grabber/";
192    system($comm);
193    chdir $CWD;
194}
195
196sub choose_grabber
197{
198    return $preferred if ($preferred 
199            and $grabbers->{$preferred} 
200            and !$grabbers->{$preferred}->{disabled});
201    foreach (keys %$grabbers)
202    {
203        return $_ unless ($grabbers->{$_}->{disabled});
204    }
205    return undef;
206}
207
208# -----------------------------------------
209# Subs: Updates & Installations
210# -----------------------------------------
211
212sub update
213{
214    print "\nFetching status file: $STATUS.\n";
215    my $data = LWP::Simple::get($STATUS);
216    unless ($data)
217    {
218        print "Failed to retrieve status file.\n";
219        return;
220    }
221
222    my %glist = %$grabbers;
223    while ($data =~ /(.*):(.*)/g)
224    {
225        update_component($1, $2);
226        delete $glist{$1};
227    }
228    foreach (keys %glist)
229    {
230        unless ($grabbers->{$_}->{disabled})
231        {
232            print "\nDeleted grabber: $_.\n";
233            disable($_);
234        }
235    }
236}
237
238sub update_component
239{
240    my ($proggy, $latestversion) = @_;
241
242    print "\n";
243
244    if ($proggy eq $progname)
245    {
246        if(! -e "$CWD/$progname")
247        {
248            print "Missing: $CWD/$progname\n";
249            install($progname, $latestversion);
250            return;
251        }
252    }
253    else
254    {
255        if (!$grabbers->{$proggy} or ! -e "$GRABBER_DIR/$proggy/$proggy")
256        {
257            print "New grabber: $proggy.\n";
258            install($proggy, $latestversion);
259            return;
260        }
261        if ($grabbers->{$proggy}->{disabled})
262        {
263            print "Warning: $proggy disabled by config file.\n";
264        }
265    }
266
267    # Compare versions
268    my $ver = ($proggy eq $progname ? $version : $grabbers->{$proggy}->{ver});
269
270    my $result = versioncmp($ver, $latestversion);
271    if ($result == -1)
272    {
273        print "Upgrading $proggy from v$ver to v$latestversion.\n";
274    }
275    elsif ($result == 1)
276    {
277        print "Downgrading $proggy from v$ver to v$latestversion.\n";
278    }
279    else
280    {
281        print "Already have latest version of $proggy: v$ver.\n";
282        return;
283    }
284    install($proggy, $latestversion);
285}
286
287sub install
288{
289    my ($proggy, $latestversion) = @_;
290
291    print "Downloading $proggy v$latestversion.\n";
292
293    my $rdir = $HOME;
294    my $ldir = $CWD;
295    my $ver = $version;
296
297    if ($proggy ne $progname)
298    {
299        -d $GRABBER_DIR or mkdir $GRABBER_DIR
300            or die "Cannot create directory $GRABBER_DIR: $!";
301
302        $ldir = "$GRABBER_DIR/$proggy";
303        -d $ldir or mkdir $ldir or die "Cannot create directory $ldir: $!";
304        $rdir = "$rdir/grabbers";
305        $ver = $grabbers->{$proggy}->{ver};
306    }
307
308    my $newfile = "$ldir/$proggy-$latestversion";
309   
310    my $rc = LWP::Simple::getstore("$rdir/$proggy-$latestversion", $newfile);
311
312    unless (is_success($rc))
313    {
314        print "Failed to retrieve $rdir/$proggy-$latestversion.\n";
315        return;
316    }
317
318    # Make it executable
319    system('chmod u+x ' . $newfile);
320
321    -d $ARCHIVE_DIR or mkdir $ARCHIVE_DIR
322      or die "Cannot create directory $ARCHIVE_DIR: $!";
323
324    if (-e "$ldir/$proggy")
325    {
326        rename("$ldir/$proggy", "$ARCHIVE_DIR/$proggy-$ver");
327    }
328    rename($newfile, "$ldir/$proggy");
329   
330    print "Installed $proggy v$latestversion.\n" if ($debug);
331
332    if ($proggy eq $progname)
333    {
334        print "\n*** Restarting ***\n\n";
335        exec("$ldir/$proggy");
336        # This exits.
337    }
338
339    print "Testing $proggy...\n" if ($debug);
340    my $result = test_grabber($proggy);
341
342    # Update grabbers list
343    my $disabled = $grabbers->{$proggy}->{disabled};
344    $grabbers->{$proggy} = 
345        { 
346            'ver' => $latestversion, 
347            'disabled' => $disabled,
348            'ready' => $result
349        };
350}
351
352sub test_grabber
353{
354    my $proggy = shift;
355
356    chdir("$GRABBER_DIR/$proggy");
357    system("$GRABBER_DIR/$proggy/$proggy --ready");
358    chdir ($CWD);
359
360    my $result = $?;
361    print "Return value: $result\n" if ($debug);
362
363    if ($result)
364    {
365        print "\nGrabber $proggy did not exit cleanly!\n" .
366              "It may require configuration.\n\n";
367    }
368
369    return !$result;
370}
371
372sub enable
373{
374    my $proggy = shift;
375
376    print "Enabling $proggy.\n";
377    if (!$grabbers->{$proggy})
378    {
379        print "No such grabber: \"$proggy\".\n";
380    }
381    else
382    {
383        delete $grabbers->{$proggy}->{disabled};
384    }
385}
386
387sub disable
388{
389    my $proggy = shift;
390
391    print "Disabling $proggy.\n";
392    $grabbers->{$proggy}->{disabled} = 1;
393}
394
395sub set_preferred
396{
397    my $proggy = shift;
398
399    if (!$grabbers->{$proggy})
400    {
401        print "No such grabber: \"$proggy\".\n";
402    }
403    else
404    {
405        $preferred = $proggy;
406    }
407}
408
409sub check
410{
411    my $result;
412    foreach (keys %$grabbers)
413    {
414        $result = test_grabber($_);
415        print "Grabber $_: " . 
416              ($result ? "OK" : "Failed") . "\n\n";
417        if (!$result ne !$grabbers->{$_}->{ready})
418        {
419            $grabbers->{$_}->{ready} = $result;
420        }
421    }
422}
423
424# -----------------------------------------
425# Subs: Setup
426# -----------------------------------------
427
428sub read_config_file
429{
430    read_file($config_file, 'configuration');
431}
432
433sub read_channels_file
434{
435    read_file($channels_file, 'channels');
436}
437
438sub read_file
439{
440    my $fn = shift;
441    my $name = shift;
442
443    print "Reading $name file: $fn\n";
444    unless (-r $fn)
445    {
446        unless ($opt->{configure})
447        {
448            print "\nNo $name file found.\n" .
449                  ucfirst($progname) . " must be configured: " .
450                  "configuring now.\n\n";
451            $opt->{'configure'} = 1;
452        }
453        return;
454    }
455    local (@ARGV, $/) = ($fn);
456    no warnings 'all';
457    eval <>;
458    if ($@ and !$opt->{configure})
459    {
460        die "\nError in $name file!\nDetails:\n$@";
461    }
462}
463
464sub write_config_file
465{
466    open(CONF, ">$config_file") or die "cannot write to $config_file: $!";
467    print CONF Data::Dumper->Dump(
468        [$region,  $preferred,  $grabbers ],
469        ["region", "preferred", "grabbers" ]);
470    close CONF;
471    print "\nUpdated configuration file $config_file.\n" if ($debug);
472}
473
474sub write_channels_file
475{
476    open(CHAN, ">$channels_file") or die "cannot write to $channels_file: $!";
477    print CHAN Data::Dumper->Dump([$channels], ["channels"]);
478    close CHAN;
479    print "Updated channels file $channels_file.\n" if ($debug);
480}
481
482sub get_initial_command_line_options
483{
484  GetOptions( 'config-file=s'   => \$opt->{configfile},
485              'help'            => \$opt->{help},
486              'configure'       => \$opt->{configure},
487              'debug'           => \$debug);
488}
489
490sub get_remaining_command_line_options
491{
492    GetOptions(
493              'version'         => \$opt->{status},
494              'status'          => \$opt->{status},
495              'list'            => \$opt->{list},
496              'show-config'     => \$opt->{show_config},
497
498              'update'          => \$opt->{update},
499              'noupdate'        => \$opt->{noupdate},
500
501              'prefer=s'        => \$opt->{prefer},
502              'disable=s'       => \$opt->{disable},
503              'enable=s'        => \$opt->{enable},
504
505              'days=i'          => \$days,
506              'offset=i'        => \$opt->{offset},
507              'show-channels'   => \$opt->{show_channels},
508              'output=s'        => \$opt->{output},
509              'check'           => \$opt->{check}
510            );
511}
512
513
514# -----------------------------------------
515# Subs: Configuration
516# -----------------------------------------
517
518sub configure
519{
520    my $REGIONS = {
521        "ACT" => 126,
522        "NSW: Sydney" => 73,
523        "NSW: Newcastle" => 184,
524        "NSW: Central Coast" => 66,
525        "NSW: Griffith" => 67,
526        "NSW: Broken Hill" => 63,
527        "NSW: Northern NSW" => 69,
528        "NSW: Southern NSW" => 71,
529        "NSW: Remote and Central" => 106,
530        "NT: Darwin" => 74,
531        "NT: Remote & Central" => 108,
532        "QLD: Brisbane" => 75,
533        "QLD: Gold Coast" => 78,
534        "QLD: Regional" => 79,
535        "QLD: Remote & Central" => 114,
536        "SA: Adelaide" => 81,
537        "SA: Renmark" => 82,
538        "SA: Riverland" => 83,
539        "SA: South East SA" => 85,
540        "SA: Spencer Gulf" => 86,
541        "SA: Remote & Central" => 107,
542        "Tasmania" => 88,
543        "VIC: Melbourne" => 94,
544        "VIC: Geelong" => 93,
545        "VIC: Eastern Victoria" => 90,
546        "VIC: Mildura/Sunraysia" => 95,
547        "VIC: Western Victoria" => 98,
548        "WA: Perth" => 101,
549        "WA: Regional" => 102
550    };
551
552    print "\nConfiguring.\n\n" .
553          "Select your region:\n";
554    foreach (sort keys %$REGIONS)
555    {
556        printf(" (%3d) %s\n", $REGIONS->{$_}, $_);
557    }
558    $region = ask_choice("Enter region code:", "94", values %$REGIONS);
559
560    print "\nFetching channel information... ";
561
562    my @channellist = get_channels();
563
564    print "done.\n\n" .
565          "For each channel you want guide data for, enter an XMLTV id\n" .
566          "of your choice (e.g. \"seven.free.au\"). If you don't need\n" .
567          "guide data for this channel, just press Enter.\n\n" .
568          "Please don't subscribe to unneeded channels.\n\nChannels:\n";
569    $channels = {};
570    my $line;
571    foreach (@channellist)
572    {
573        $line = ask(" \"$_\"? ");
574        $channels->{$_} = $line if ($line);
575    }
576
577
578    print "\nRandomly selecting preferred grabber.\n\n";
579    set_preferred(choose_grabber());
580
581    show_channels();
582    unless(ask_boolean("\nCreate configuration file?"))
583    {
584        print "Aborting configuration.\n";
585        exit 0;
586    }
587
588    write_config_file();
589    write_channels_file();
590
591    print "Finished configuring.\n\n" .
592          "Shepherd is installed into $CWD.\n\n";
593   
594    if ($invoked ne "$CWD/$progname" and $invoked =~ /$progname/)
595    {
596        print "Warning: you invoked this program as $invoked.\n" .
597            "In the future, it should be run as $CWD/$progname,\n" .
598            "to avoid constantly re-downloading the latest version.\n\n" .
599            "MythTV users may wish to create the following symlink, by " .
600            "doing this (as root):\n" .
601            "\"ln -s $CWD/$progname /usr/bin/tv_grab_au\".\n\n" .
602            "You may safely delete $invoked.\n\n";
603    }
604
605    status();
606
607    unless (ask_boolean("\nGrab data now?"))
608    {
609        exit 0;
610    }
611}
612
613sub get_channels
614{
615    my @date = localtime;
616    my $page = LWP::Simple::get(
617        "http://au.tv.yahoo.com/results.html?rg=$region&dt=" .
618        ($date[5] + 1900) . "-$date[4]-$date[3]");
619    my @channellist;
620    while ($page =~ /<tr class=rtb><td class=rth><a .*?>(.*?)<\/a>/g)
621    {
622        push @channellist, $1;
623    }
624    return @channellist;
625}
626
627# -----------------------------------------
628# Subs: Status & Help
629# -----------------------------------------
630
631sub show_config
632{
633    print "\nConfiguration\n".
634          "-------------\n" .
635          "Config file: $config_file\n" .
636          "Debug mode : " . is_set($debug) . "\n" .
637          "Output file: " . ($opt->{output} ? $opt->{output} : "None") . "\n" .
638          "Region ID  : $region\n";
639  show_channels();
640  print "\n";
641  status();
642  print "\n";
643}
644
645sub show_channels
646{
647  print "Subscribed channels:\n";
648  print "    $_ -> $channels->{$_}\n" for sort keys %$channels;
649}
650
651sub is_set
652{
653    my $arg = shift;
654    return $arg ? "Yes" : "No";
655}
656
657sub status
658{
659    print "    Grabber               Version  Enabled  Ready    Last Data\n" .
660          "    -----------------------------------------------------------\n";
661    my $star;
662    foreach (sort keys %$grabbers)
663    {
664        my $h = $grabbers->{$_};
665        printf  "%-3s %-20s %8s %5s %7s %10s\n",
666                $_ eq $preferred ? '(*)' : '',
667                $_,
668                $h->{ver},
669                $h->{disabled} ? '' : 'Y',
670                $h->{ready} ? 'Y' : '',
671                $h->{lastdata} ? localtime($h->{lastdata}) : '';
672    }
673
674    print "\n(*) = Preferred\n";
675}
676
677sub help
678{
679    print q{
680Command-line options:
681    --help                Print this message
682
683    --status              Print a list of grabbers maintained
684    --list                Print a detailed list of grabbers
685
686    --configure           Setup
687    --show-config         Print setup details
688
689    --prefer <s>          Designate <s> as your preferred grabber
690    --disable <s>         Don't ever use grabber <s>
691    --enable <s>          Okay, maybe use it again then
692    --uninstall <s>       Remove a disabled grabber
693
694    --noupdate            Do not attempt to update before running
695    --update              Update only; do not grab data
696
697    --check               Check status of all grabbers
698};
699    exit 0;
700}
Note: See TracBrowser for help on using the browser.