#!/usr/bin/perl -w
# Copyright (c) 2007 Andrew Ruthven <andrew@etc.gen.nz>
# This code is hereby licensed for public consumption under the GNU GPL v2.
#
# 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 St, Fifth Floor, Boston, MA  02110-1301  USA

# Display the current status of a MythTV system.

use LWP::Simple;
use XML::LibXML;
use Date::Manip;
use Getopt::Long;
use Text::Wrap;
use POSIX qw/strftime/;
use MIME::Entity;
use Pod::Usage;

# Some sane defaults.
my $host = "localhost";
my $port = "6544";
my $description = undef;
my $episode = undef;
my $encoder_details = undef;
my $colour = undef;
my %display;
my @email;
my $email_only_on_alert = 0;
my $help = undef;
my $xml_file = undef;
my $verbose = 0;
my $disk_space_warn = 95;  # Percent to warn at.
my $guide_days_warn = 2;   # How many days we require.
my $auto_expire_count = 10; # How many auto expire shows to display.

my $VERSION = '0.7.3';

# Some display blocks are disabled by default:
$display{'Shows due to Auto Expire'} = 0;

GetOptions(
  'c|colour|color' => \$colour,
  'd|descripton' => \$description,
  'e|episode'    => \$episode,
  'encoder-details' => \$encoder_details,
  'h|host=s'     => \$host,
  'p|port=i'     => \$port,
  'v|version'    => \&print_version,
  'email=s@'     => \@email,
  'email-only-on-conflict|email-only-on-alert|email-only-on-alerts'
		 => \$email_only_on_alert,
  'disk-space-warn=i'      => \$disk_space_warn,
  'guide-days-warn=i'      => \$guide_days_warn,
  'auto-expire-count=i'    => \$auto_expire_count,

  'status!'               => \$display{'Status'},
  'encoders!'             => \$display{'Encoders'},
  'recording-now!'        => \$display{'Recording Now'},
  'scheduled-recordings!' => \$display{'Scheduled Recordings'},
  'schedule-conflicts!'   => \$display{'Schedule Conflicts'},
  'next-recording!'       => \$display{'Next Recording In'},
  'total-disk-space!'     => \$display{'Total Disk Space'},
  'disk-space!'           => \$display{'Disk Space'},
  'guide-data!'           => \$display{'Guide Data'},
  'auto-expire!'          => \$display{'Shows due to Auto Expire'},

  'file=s'                => \$xml_file,

  'verbose' => \$verbose,
  'help|?' => \$help,
) || pod2usage("\nUse --help for help.\n");

pod2usage(verbose => 1)
  if defined $help;

# Get the email address into a format we can use.
@email = split(',', join(',', @email));

# Default to not showing some blocks if we're sending email, but let the
# user override us.
if (scalar(@email) > 0) {
  for my $block ('Encoders', 'Recording Now', 'Next Recording In') {
    if (! defined $display{$block}) {
      $display{$block} = 0;
    }
  }
}

# Possibly use some colour, but not in emails.
my $safe = '';
my $warning = '';
my $normal = '';
if (defined $colour && scalar(@email) == 0) {
  $safe    = "\033[0;32m";
  $warning = "\033[1;31m";
  $normal  = "\033[0m";
}

# Allow setting some defaults for the output blocks.
my %defaults = (
  'schedule' => {
    'attrs' => [ qw/title startTime NODE_TEXT subTitle channelName:.\/Channel[@channelName] chanNum:.\/Channel[@chanId] inputId:.\/Channel[@inputId]/ ],
    'template' => "__startTime__"
      . (defined $encoder_details ? " - Enc: __inputId__, Chan: __chanNum__" : '')
      . " - __title__"
      . (defined $episode ? " - __subTitle__" : '')
      . " (__channelName__)"
      . (defined $description ? "\n__NODE_TEXT__" : ''),
    'filter' =>  {
       # Only show recordings for today and tomorrow.
       'startTime' => sub {
         my $date = substr(ParseDate($_[0]), 0, 8);
         return ! (($date cmp $today) == 0
          || ($date cmp $tomorrow) == 0) }
     },
     'rewrite' => {
       '/startTime/' => { 'T' => ' ' },
     }
   }
);

# The time of the next scheduled recording.
my $next_time = 'Never';
# Are there any alerts that should be notified via email?
my @alerts = ();

# The blocks of output which we might generate.
my @blocks = (
  # All the one liners together
  {
    'name' => 'One Liners',
    'type' => 'sub',
    'template' => '',
    'sub' => sub { return 'Place holder' },
  },

  # Date/Time from server
  {
    'name'  => 'Status',
    'type'  => 'xpath',
    'xpath' => "//Status",
    'attrs' => [ qw/time date/ ],
    'template' => "__date__, __time__",
    'format' => 'one line'
  },

  # Info about the encoders.
  {
    'name'  => 'Encoders',
    'type'  => 'xpath',
    'xpath' => "//Status/Encoders/Encoder",
    'attrs' => [ qw/hostname id state connected/ ],
    'template' => "__hostname__ (__id__) - __state____connected__",
    'rewrite' => {
      '/connected/' => { '1' => '', '0' => ' (Disconnected)' },
      '/state/' =>{ '^0$' => "${safe}Idle${normal}",
                  '^1$' => "${warning}Watching LiveTV${normal}",
                  '^2$' => "${warning}Watching Pre-recorded${normal}",
      '^3$' => "${warning}Watching Recording${normal}",
      '^4$' => "${warning}Recording${normal}" },
    }
  },

  # What programs (if any) are being recorded right now?
  {
    'name'  => 'Recording Now',
    'type'  => 'xpath',
    'xpath' => "//Status/Encoders/Encoder/Program",
    'hide'  => 'after',
    'attrs' => [ qw/title endTime channelName:.\/Channel[@channelName]/ ],
    'template' => "__title__ (__channelName__) Ends: __endTime__",
    'rewrite' => {
      '/endTime/' => { '.*T' => '' },
     },
     'subs' => {
       'find_next' => sub { $next_time = 'now' }
     }
  },

  # The upcoming recordings.
  {
    'name'  => 'Scheduled Recordings',
    'type'  => 'xpath',
    'xpath' => '//Status/Scheduled/Program',
    'defaults' => 'schedule',
    'hide'  => 'after',
    'subs' => {
      'find_next' => sub {
   my $vars = shift;
         return
     if defined $next_time && $next_time eq 'now';

         my $date = ParseDate($vars->{'startTime'});
	 if ($next_time eq 'Never' || Date_Cmp($date, $next_time) < 0) {
	   $next_time = $date
	 };
       }
     }
  },

  # Conflicts
  {
    'name' => 'Schedule Conflicts',
    'type' => 'sub',
    'defaults' => 'schedule',
    'sub' => \&process_conflicts
  },

  # Auto Expire
  {
    'name' => 'Shows due to Auto Expire',
    'type' => 'sub',
    'defaults' => 'schedule',
    'sub' => \&process_auto_expire,
    'filter' => {
      'startTime' => sub {
	$auto_expire_count--;
	return ($auto_expire_count < 0);
      },
    },
  },

  # Diskspace, before storage groups
  {
    'name' => 'Total Disk Space',
    'type' => 'xpath',
    'xpath' => '//Status/MachineInfo/Storage',
    'protocol_version' => [ "<= 31" ],
    'attrs' => [ qw/_total_total _total_used/ ],
    'commify' => [ qw/_total_total _total_used/ ],
    'template' => "Total space is ___total_total__ MB, with ___total_used__ MB used (__percent__)",
    'format' => 'one line',
    'subs' => {
      'percent' => sub {
        calc_disk_space_percentage($_[0]->{'_total_used'}, $_[0]->{'_total_total'})
      }
    }
  },

  # Diskspace, with storage groups
  {
    'name' => 'Total Disk Space',
    'type' => 'xpath',
    'xpath' => '//Status/MachineInfo/Storage',
    'protocol_version' => [ ">= 32" ],
    'xml_version' => [ "== 0" ],
    'attrs' => [ qw/drive_total_total drive_total_used/ ],
    'commify' => [ qw/drive_total_total drive_total_used/ ],
    'template' => "Total space is __drive_total_total__ MB, with __drive_total_used__ MB used (__percent__)",
    'format' => 'one line',
    'subs' => {
      'percent' => sub {
        calc_disk_space_percentage($_[0]->{'drive_total_used'}, $_[0]->{'drive_total_total'})
      }
    }
  },

  # Diskspace, with storage groups and sensible XML layout.
  {
    'name' => 'Total Disk Space',
    'type' => 'xpath',
    'xpath' => '//Status/MachineInfo/Storage/Group[@id="total"]',
    'protocol_version' => [ ">= 39" ],
    'attrs' => [ qw/total used/ ],
    'commify' => [ qw/total used/ ],
    'template' => "Total space is __total__ MB, with __used__ MB used (__percent__)",
    'format' => 'one line',
    'subs' => {
      'percent' => sub {
        calc_disk_space_percentage($_[0]->{'used'}, $_[0]->{'total'})
      }
    }
  },

  # Diskspace, with storage groups and sensible XML layout.
  {
    'name' => 'Disk Space',
    'type' => 'xpath',
    'xpath' => '//Status/MachineInfo/Storage/Group',
    'protocol_version' => [ ">= 39" ],
    'attrs' => [ qw/id total used/ ],
    'commify' => [ qw/total used/ ],
    'template' => "Total space for group __id__ is __total__ MB, with __used__ MB used (__percent__)",
    'filter' =>  {
      'id' => sub { return $_[0] eq 'total' },
      'used' => sub {
        return ! (
	  (defined $display{'Disk Space'} && $display{'Disk Space'})
	    || ($_[1]->{'used'} / $_[1]->{'total'}) * 100 > $disk_space_warn)
      }
    },
    'subs' => {
      'percent' => sub {
        calc_disk_space_percentage($_[0]->{'used'}, $_[0]->{'total'})
      }
    }
  },

  
  # How many hours till the next recording.
  {
    'name' => 'Next Recording In',
    'type' => 'sub',
    'format' => 'one line',
    'template' => '__next_time__',
    'rewrite' => {
      '&next_time' => sub {
	return $next_time
	  if $next_time eq 'Never' || $next_time eq 'now';

        my $str = Delta_Format(DateCalc('now', $next_time, undef, 1), 0, '%hh Hours, %mv Minutes');
        $str =~ s/\b1 (Hour|Minute)s/1 $1/;
        $str =~ s/^0 Hours, (.*)$/$warning$1$normal/;
        $str =~ s/ 0 Minutes//;

  return $str;
      }
    },
    'filter' =>  {
      'next_time' => sub { return $_[0] eq 'now' }
    },
    'sub' => sub {
      return substitute_vars($_[0], { 'next_time' => $next_time });
    }
  },

  # Check how much Guide data we have
  {
    'name'     => 'Guide Data',
    'format'   => 'one line',
    'type'     => 'xpath',
    'xpath'    => '//Status/MachineInfo/Guide[@guideDays]',
    'attrs'    => [qw/guideDays status guideThru/],
    'template' => 'There is __guideDays__ days worth of data, through to __guideThru__',
    'filter' => {
      'guideDays' => sub {
        if ($_[0] > $guide_days_warn) {
	  return
	    (defined $display{'Guide Data'} && ! $display{'Guide Data'}) || 1;
        } else {
          push @alerts, "GUIDE DATA";
          return 0;
        }
      },
    },
    'rewrite'  => {
      '&guideDays' => sub {
        if ($_[0] <= $guide_days_warn) {
          return "$warning$_[0]$normal";
	} else {
          return "$safe$_[0]$normal";
        }
      },
      '/guideThru/' => { 'T\d+:\d+:\d+' => ' ' },
      '&guideThru' => sub {
        if ($_[1]->{'guideDays'} <= $guide_days_warn) {
          return "$warning$_[0]$normal";
	} else {
          return "$safe$_[0]$normal";
        }
      },
    },
  },

  {
    'name'     => 'Guide Data',
    'format'   => 'one line',
    'type'     => 'xpath',
    'xpath'    => '//Status/MachineInfo/Guide[@status=""]',
    'template' => "${warning}No guide data!${normal}",
  },
);

###
### Set some useful variables
###
our $today    = substr(ParseDate('today'), 0, 8);
our $tomorrow = substr(ParseDate('tomorrow'), 0, 8);

# A couple of global variables
my ($xml, $myth);
my %version;

my $title =  "MythTV status for $host";
my $output = "$title\n";
$output .= '=' x length($title) . "\n";

for my $block (@blocks) {
  $block->{'format'} ||= 'multi line';

  my $hide = undef;
  if (defined $display{ $block->{'name'} }
    && $display{ $block->{'name'} } == 0) {
    if (defined $block->{'hide'} && lc($block->{'hide'}) eq 'after') {
      $hide = 1;
    } else {
      next;
    }
  }

  # We might need to set some defaults.
  if (defined $block->{'defaults'}) {
    for my $field (keys %{ $defaults{ $block->{'defaults'} } }) {
      $block->{$field} ||= $defaults{ $block->{'defaults'} }{$field};
    }
  }

  my $result = undef;
  if ($block->{'type'} eq 'xpath') {
    $xml ||= load_xml();

    $result = process_xml($block, $xml);

  } elsif ($block->{'type'} eq 'sub') {

    $result = &{ $block->{'sub'} }($block)
      if defined $block->{'sub'};
  }

  if (defined $result && $result ne '' && ! defined $hide) {
    if ($block->{'format'} eq 'one line') {
      push @oneliners, [ $block->{'name'}, $result ];
    } else {
      $output .= "$block->{'name'}:\n";
      $output .= $result . "\n\n";
    }
  }
}

# Deal with the one liners.
if (scalar(@oneliners) > 0) {
  # Find the longest header
  my $length = 0;
  for $line (@oneliners) {
     if (length($line->[0]) > $length) {
       $length = length($line->[0]);
     }
  }

  # Put the one liners together, with leading dots to the colon.
  my $oneliners = "";
  for $line (@oneliners) {
     $oneliners .= "$line->[0]"
       . ('.' x ($length - length($line->[0]))) . ": $line->[1]\n";
  }

  # What a hacky way of putting the one liners where I want them...
  $output =~ s/^One Liners:\nPlace holder\n/$oneliners/m;
}

# Either print the status out, or email it.
if (scalar(@email) == 0) {
  print "\n$output";
} else {
  if ((! $email_only_on_alert) ||
      ($email_only_on_alert && scalar(@alerts) > 0)) {
    my $suffix = undef;
    if (@alerts == 1) {
      $suffix = $alerts[0];
    } elsif (@alerts > 1) {
      $suffix = "MULTIPLE WARNINGS";
    }

    my $mail = MIME::Entity->build(
      To      => \@email,
      Subject => $title . (defined $suffix ? " - $suffix" : ''),
      Data    => $output
    );

    $mail->send('sendmail');
  }
}

exit 0;

# Fetch the XML status from the backend.
sub load_xml {
  my $status = "";

  if (defined $xml_file) {
    open (IN, "< $xml_file")
      || die "Failed to open $xml_file for reading: $!\n";

    $status = join("", <IN>);

    close IN;
  } else {
    my $url = "http://$host:$port/xml";
    $status = get($url);

    die "Sorry, failed to fetch $url.\n"
      unless defined $status;
  }

  # Parse the XML
  my $parser = XML::LibXML->new();

  # Some XML data seems to have badness in it, including non-existant
  # UTF-8 characters.  We'll try and recover.
  $parser->recover(1);
  $parser->recover_silently(1)
    unless $verbose;

  clean_xml(\$status);

  my $xml = eval { $parser->parse_string( $status ) };

  if ($@) {
    die "Failed to parse XML: $@\n";
  }

  # Pick out the XML version.
  my $items = $xml->documentElement->find('//Status');
  $version{'xml'}      = @{ $items }[0]->getAttribute('xmlVer') || 0;
  $version{'protocol'} = @{ $items }[0]->getAttribute('protoVer');

  return $xml;
}


# Prep the Perl MythTV API if available.
sub load_perl_api {
  my $myth = undef;

  eval { require MythTV };
  if ($@) {
    print $@
      if $verbose;
  } else {
    # Surpress warnings from DBI.  I tried unsetting $^W but that is ignored.
    local($SIG{__WARN__}) = sub { if ($verbose) { print shift } };
    eval { $myth = new MythTV() };

    print $@
      if $@ && $verbose;
  }

  return $myth;
}

# We are sometimes passed dodgy XML from MythTV, make some attempts to clean
# it.
sub clean_xml {
  my ($xml) = shift;

  # Deal to invalid Unicode.
  for my $bad ("&#xdbff;", "&#xdee9;") {
    if ($$xml =~ s/$bad/?/g) {
      warn "Found and replaced: $bad\n"
        if $verbose;
    }
  }
}

sub process_xml {
  my ($block, $xml) = @_;

  # Only work on this block if we have received the appropriate version of
  # the XML.
  for my $vers (qw/protocol xml/) {
    if (defined $block->{"${vers}_version"}) {
      my $result = undef;

      # All the version checks must pass.
      for my $check (@{ $block->{"${vers}_version"} }) {
        my $res = eval ( "$version{$vers} $check" );

        if (! defined $result || $res != 1) {
	  $result = $res;
	}
      }

      return
        unless defined $result && $result ne '';

      warn "We have the correct $vers version for $block->{'name'}\n"
        if $verbose;
    }
  }

  my $items = $xml->documentElement->find($block->{'xpath'});

  # Don't do any work on this block if there is nothing for it.
  return undef
    if (scalar(@$items) == 0);

  my @lines;
  for my $item (@{ $items }) {
    my %vars;
    for my $key (@{ $block->{'attrs'} }) {
      if ($key =~ /(.*?):(.*)/) {
	my $subitem = $item->findnodes($2);
        $vars{$1} = @{ $subitem }[0]->getAttribute($1)
          if defined @{ $subitem }[0];
      } else {
        $vars{$key} = $key eq 'NODE_TEXT' ? $item->string_value : $item->getAttribute($key);
      }
    }

    my $str = substitute_vars($block, \%vars);
    push @lines, $str
      if defined $str;
  }

  return join("\n", @lines);
}

sub process_conflicts {
  my ($block) = @_;
  $myth ||= load_perl_api();

  return "Unable to access MythTV Perl API.  Try with --verbose to find out why."
    unless defined $myth;

  my @lines;

  # This isn't defined in the 0.20 version of the API.  It is in 0.21svn.
  my $recstatus_conflict = 7;

  my %rows = $myth->backend_rows('QUERY_GETALLPENDING', 2);

  foreach my $row (@{$rows{'rows'}}) {
    my $show;
    {
      # MythTV::Program currently has a slightly broken line with a numeric
      # comparision.
      local($^W) = undef;
      $show = new MythTV::Program(@$row);
    }

    if ($show->{'recstatus'} == $recstatus_conflict) {
      my %vars = (
        'title'     => $show->{'title'},
        'startTime' => strftime("%FT%T", localtime($show->{'starttime'})),
        'NODE_TEXT' => $show->{'description'},
        'subTitle'  => $show->{'subtitle'},
	'channelName' => $show->{'channame'},
	'inputId'   => $show->{'inputid'},
	'chanNum'   => $show->{'channum'},
       );

      my $str = substitute_vars($block, \%vars);
      push @lines, $str
        if defined $str;
    }
  }

  if (scalar(@lines) == 1) {
    push @alerts, "CONFLICT";
  } elsif (scalar(@lines) > 1) {
    push @alerts, "CONFLICTS";
  }

  return join("\n", @lines);
}

sub process_auto_expire {
  my ($block) = @_;
  $myth ||= load_perl_api();

  return "Unable to access MythTV Perl API.  Try with --verbose to find out why."
    unless defined $myth;

  my @lines;

  # This isn't defined in the 0.20 version of the API.  It is in 0.21svn.
  my %rows = $myth->backend_rows('QUERY_RECORDINGS Delete', 2);

  # Returned in date order, desc.  So reverse it to make the oldest
  # ones come first.
  foreach my $row (reverse @{$rows{'rows'}}) {
    my $show;
    {
      # MythTV::Program currently has a slightly broken line with a numeric
      # comparision.
      local($^W) = undef;
      $show = new MythTV::Program(@$row);
    }

    # Who cares about LiveTV recordings?
    next if $show->{'progflags'} eq 'LiveTV';

    my %vars = (
      'title'     => $show->{'parentid'},
      'startTime' => strftime("%FT%T", localtime($show->{'starttime'})),
      'NODE_TEXT' => $show->{'description'},
      'subTitle'  => $show->{'subtitle'},
      'channelName' => $show->{'callsign'},
      'inputId'   => $show->{'inputid'},
      'chanNum'   => $show->{'chanid'},
    );

    my $str = substitute_vars($block, \%vars);
    push @lines, $str
      if defined $str;
  }

  return join("\n", @lines);
}

sub substitute_vars {
  my $block = shift;
  my $vars  = shift;

  my %commify = map { $_ => 1 } @{ $block->{'commify'} }
    if defined $block->{'commify'};

  my $template = $block->{'template'};
  my $skip = undef;
  my ($key, $value);
  while (($key, $value) = (each %{ $vars })) {
    if (! defined $value) {
      warn "Unable to find any value for $key while looking at $block->{'name'}\n";
      next;
    }

    $value = wrap('  ', '  ', $value)
      if $key eq 'NODE_TEXT';

    $value =~ s/\s+$//;
    $value = 'Unknown'
      if $value eq '';

    $skip = 1
      if defined $block->{'filter'}{$key} &&
        &{ $block->{'filter'}{$key} }($value, $vars);

    if (defined $block->{'rewrite'}{"/$key/"}) {
      my ($search, $replace);
      while (($search, $replace) = each %{ $block->{'rewrite'}{"/$key/"} } ) {
        $value =~ s/$search/$replace/g;
      }
    }

    if (defined $block->{'rewrite'}{"&$key"}) {
      $value = &{ $block->{'rewrite'}{"&$key"} }($value, $vars);
    }

    $value = commify($value)
      if defined $commify{$key};

    $template =~ s/__${key}__/$value/g;
  }

  my ($name, $sub);
  while (($name, $sub) =  each %{ $block->{'subs'} }) {
    $value = &$sub($vars);

    $template =~ s/__${name}__/$value/g
      if defined $value;
  }

  return defined $skip ? undef : $template;
}

# Work out the disk space percentage, possibly setting a flag that we should
# raise an alert.
sub calc_disk_space_percentage {
  my ($used, $total) = @_;

  if (! (defined $used && defined $total && $total > 0) ){
    warn "Something is wrong calculating the disk space percentage.\n";
    return 'unknown';
  }

  my $percent = sprintf("%.1f",
    $used / $total * 100);

  if ($percent >= $disk_space_warn) {
    push @alerts, "DISK SPACE";
    return "$warning$percent\%$normal";
  } else {
    return "$safe$percent\%$normal";
  }
}

# Beautify numbers by sticking commas in.
sub commify {
  my ($num) = shift;

  $num = reverse $num;
  $num =~ s<(\d\d\d)(?=\d)(?!\d*\.)><$1,>g;
  return reverse $num;
}

sub print_version {
  print "mythtv-status, version $VERSION.\n";
  print "Written by Andrew Ruthven <andrew\@etc.gen.nz>\n";
  print "\n";
  exit;
}

=head1 NAME

mythtv-status - Display the status of a MythTV backend

=head1 SYNOPSIS

 mythtv-status [options]

=head1 DESCRIPTION

This script queries a MythTV backend and reports on the status of it,
any upcoming recordings and any which are happening right now.

The intention is to warn you if there is a program being recorded or
about to be recorded.

=head1 OPTIONS

=over

=item B<-c, --colour>

Use colour when showing the status of the encoder(s).

=item B<-d, --description>

Display the description for the scheduled recordings.

=item B<--disk-space-warn>

The threshold (in percent) of used disk space that we should show
the disk space in red (if using colour) or send an email if we're
in email mode with email only on warnings.

=item B<--encoder-details>

Display the input ID and channel name against the recording details.

=item B<-e, --episode>

Display the episode (subtitle) for the scheduled recordings.

=item B<< --email <address>[ --email <address> ...] >>

Send the output to the listed email addresses.  By default the encoder status,
currently recording shows and time till next recording is surpressed from
the email.

To turn the additional blocks on you can use B<--encoders>, B<--recording-now>
and/or B<--next-recording>.

=item B<--email-only-on-alert>

Only send an email out (if --email is present) if there is an alert
(i.e., schedule conflict or low disk space).

=item B<-?, --help>

Display help.

=item B<< --file <file> >>

Load XML from the file specified instead of querying a MythTV backend.
Handy for debugging things.

=item B<< --guide-data-warn <days> >>

Warn if the number of days of guide data present is equal to or below
this level.  Default is 2 days.

=item B<-h HOST, --host=HOST>

The host to check, defaults to localhost.

=item B<--nostatus>, B<--noencoders>, B<--norecording-now>, B<--noscheduled-recordings>, B<--noschedule-conflicts>, B<--nonext-recording>, B<--nototal-disk-space>, B<--nodisk-space>, B<--noguide-data>, B<--noauto-expire>

Suppress displaying blocks of the output if they would normally be displayed.

=item B<-p PORT, --port=PORT>

The port to use when connecting to MythTV, defaults to 6544.

=item B<--auto-expire>

Display the shows due to auto expire (output is normally suppressed).

=item B<--auto-expire-count>

How many of the auto expire shows to display, defaults to 10.

=item B<--verbose>

Have slightly more verbose output.  This includes any warnings that might
be generated while parsing the XML.

=item B<-v, --version>

Show the version of mythtv-status and then exit.

=back

=head1 OUTPUT

The output of this script is broken up into several chunks they are:

=over

=item Status

Some general info about the backend, currently just the timestamp of when
this program was run.

=item Guide Data

The number of days of guide data is present.  By default it is only shown
if the number of days is below the warning level.  To show it regardless
of the warning level use --guide-data.

=item Encoders

Each encoder that the backend knows about are listed, with the hostname
they are on, the encoder ID (in brackets) and the current status.

=item Recording Now

Any programs which are being recorded right now.

=item Scheduled Recordings

Up to 10 programs which are scheduled to be recorded today and tomorrow.

=item Schedule Conflicts

Any upcoming schedule conflicts (not just limited to today or tomorrow).

=item Shows due to Auto Expire

The shows which will be deleted and the order they'll be deleted if the
auto expirer kicks in.

=item Total Disk Space

The amount of disk space in total, and used by MythTV.

=item Next Recording In

If there are no recordings currently happening, then the amount of time until
the next recording is displayed.

=item Disk Space

Details about each storage group that MythTV knows about.  By default this
only shows storage groups that are above the warning level.  Use
B<--disk-space> to turn on display of all storage groups.

=back

=head1 AUTHOR

Andrew Ruthven, andrew@etc.gen.nz

=head1 LICENSE

Copyright (c) 2007 Andrew Ruthven <andrew@etc.gen.nz>
This code is hereby licensed for public consumption under the GNU GPL v2.

=cut

