#!/usr/bin/perl
#
# mythstatus.pl
# Seth D. Galitzer <sgsax@ksu.edu>
# version 0.1, 11/16/2010
#
# This script is intended as a "quick and dirty" replacement for mythtv-status
#   by Andrew Ruthven, which does not seem to work with mythtv 0.23.  It takes
#   advantage of the XML status report provided by mythbackend and formats it
#   nicely to fit in an 80-col terminal.  There are no commandline args, no
#   runtime configuration, not even any embedded perldocs.  Very few perl
#   libraries are used.  It does not need to run on your mythtvbackend host,
#   just a host that can contact it and its status port.  The idea here is to
#   generate a simple text report from the raw XML returned by mythtvbackend,
#   nothing more complex than that.
# There is a great deal more data provided in the raw report, but I have taken
#   what I consider to be the most useful information and included it here.
#   Please feel free to uncomment the indicated debugging lines and see if
#   there is anything you would like to add for yourself.  I use this to query
#   my mythtvbackend, which only has local tuners.
# I've taken a rather brute-force approach, but it produces functional results
#   and should prove to be maintainable for future use.  I'm sure there is much
#   here that would make a hardcore perl coder cringe.  You won't find any
#   clever perl tricks here.  Because the hash structure generated by the XML
#   parser is so complex, I save the data to variables before using them in the
#   print statments.  This is done for the sole purpose of making the code more
#   readable.  I use hashrefs and arrayrefs throughout becaue I find them easier
#   to work with than their unreferenced counterparts.

use strict;
use XML::Simple;
use LWP::Simple;
use Number::Format;

# modify these for your own setup
my $host = "mythtv-be";
my $port = 6544;

my $url = "http://$host:$port/xml";

my $status_raw = get($url);

my $xml = new XML::Simple;
my $num = new Number::Format;

my $status = $xml->XMLin($status_raw);

# Uncomment the following two lines for debugging
#use Data::Dumper;
#print Dumper($status);

my $timestamp = "$$status{date}, $$status{time}";         # runtime of this script request

# calculate disk usage
my $GB = 1048576;	# 1024^2, for calculating GB of storage
my $free = $num->format_bytes($$status{MachineInfo}{Storage}{Group}{total}{free}*$GB);
my $used = $num->format_bytes($$status{MachineInfo}{Storage}{Group}{total}{used}*$GB);
my $pctused = $num->format_number($$status{MachineInfo}{Storage}{Group}{total}{used}/$$status{MachineInfo}{Storage}{Group}{total}{total}*100, 0) . "%";
my $total = $num->format_bytes($$status{MachineInfo}{Storage}{Group}{total}{total}*$GB);

# calculate current load (1 min, 5 min, 15 min averages)
my $load1 = $num->format_number($$status{MachineInfo}{Load}{avg1}, 2);
my $load5 = $num->format_number($$status{MachineInfo}{Load}{avg2}, 2);
my $load15 = $num->format_number($$status{MachineInfo}{Load}{avg3}, 2);

# get guide data end date
my $guideData = $$status{MachineInfo}{Guide}{guideThru};
$guideData =~ s/T.*$//;
$guideData = formatDate($guideData);

# generate output
print "\n";
print "MythTV Backend status for $host\n";
print "-------------------------------------------------------------------------------\n";
print "Timestamp: $timestamp\n";
print "\n";
print "System State:\n";
print "---------------------------------------\n";
print "Guide data through: $guideData\n";
print "Disk Space:\n";
print "\tfree: $free\n";
print "\tused: $used ($pctused)\n";
print "\ttotal: $total\n";
print "Load: $load1, $load5, $load15\n";
print "\n";
print "Encoders:\n";
print "---------------------------------------\n";
formatEncoderState($$status{Encoders});
print "\n";
print "Job Queue:\n";
print "---------------------------------------\n";
formatJobQueue($$status{JobQueue});
print "\n";
print "Scheduled Recordings:\n";
print "---------------------------------------\n";
formatScheduledRecordings($$status{Scheduled});
print "\n";

# all done

###############################################################################
# worker functions

sub formatScheduledRecordings {
# collect data for scheduled upcoming recordings and print it to the screen
    my $programs = shift;

    for (my $i=0; $i<$$programs{count}; $i++) {
	my $title = $$programs{Program}[$i]{title};
	my $subTitle = $$programs{Program}[$i]{subTitle};
	my $date = $$programs{Program}[$i]{startTime};
	$date =~ s/T.*$//;
	my $dateStr = formatDate($date);
	my $time = $$programs{Program}[$i]{startTime};
	$time =~ s/^.*T//;
	my $timeStr = formatTime($time);
	my $chan = $$programs{Program}[$i]{Channel}{chanNum};
	my $chanName = $$programs{Program}[$i]{Channel}{channelName};

	print "$dateStr, $timeStr - $title";
	if ($subTitle ne "") { print ": $subTitle"; } # not all shows have sub-titles (episode names)
	print " - $chan, $chanName\n";
    }
}

sub formatJobQueue {
# collect data from the job queue and print it to the screen
    my $jobQueue = shift;

    my $jobs = $$jobQueue{Job};

    foreach my $key (keys %$jobs) {
	my $state = $$jobQueue{Job}{$key}{status};
	my $stateStr = parseJobQueueState($state);
	my $type = $$jobQueue{Job}{$key}{type};
	my $typeStr = parseJobQueueType($type);
	my $title = $$jobQueue{Job}{$key}{Program}{title};
	my $subTitle = $$jobQueue{Job}{$key}{Program}{subTitle};
	my $date = $$jobQueue{Job}{$key}{Program}{Recording}{recStartTs};
	$date =~ s/T.*$//;
	my $dateStr = formatDate($date);
	my $time = $$jobQueue{Job}{$key}{Program}{Recording}{recStartTs};
	$time =~ s/^.*T//;
	my $timeStr = formatTime($time);
	my $status =  $$jobQueue{Job}{$key}{content};

	print "$dateStr, $timeStr - $title";
	if ($subTitle ne "") { print ": $subTitle"; } # not all shows have sub-titles (episode names)
	print "\n";
	print "\t$typeStr, $stateStr - $status\n";   # wrap this to a newline to fit in 80 char width
    }
}

sub formatEncoderState {
# collect data about what the encoders (tuners) are doing and print it to the screen
    my $encoders = shift;

    for (my $i=1; $i<=$$encoders{count}; $i++) {
	my $state = $$encoders{Encoder}{$i}{state};
	my $stateStr = parseEncoderState($state);
	print "Encoder $i: $stateStr ($state)";
	if ($state ne 0) {   # state 0 is "idle"
	    my $title = $$encoders{Encoder}{$i}{Program}{title};
	    my $subTitle = $$encoders{Encoder}{$i}{Program}{subTitle};
	    my $chan = $$encoders{Encoder}{$i}{Program}{Channel}{chanNum};
	    my $chanName = $$encoders{Encoder}{$i}{Program}{Channel}{channelName};
	    print " - $title";
	    if ($subTitle ne "") { print ": $subTitle"; } # not all shows have sub-titles (episode names)
	    print " - $chan, $chanName";
	}
	print "\n";
    };
}
sub parseEncoderState {
# given the encoder state code, return a useful string
# see libs/libmythtv/tv.h for encoder (tuner) states
    my $state = shift;

    # arrays are easy, put the valid state strings in the proper order here
    my $ret = [ "Idle", "Watching LiveTV", "Watching Pre-Recorded",
		"Watching Video", "Watching DVD", "Watching BD",
		"Recording", "Recording"];

    if ($state eq -1) {   # "error" state is special
	return "Error";
    } else {
	return $$ret[$state];
    }
}

sub parseJobQueueState {
# given the job state code, return a useful string
# see libs/libmythtv/jobqueue.h for job states
    my $state = shift;
    
    # arrays are easy, put the valid state strings in the proper order here
    my $status       = [ "Unknown", "Queued", "Pending",
			 "Starting", "Running", "Stopping",
			 "Paused", "Retrying", "Erroring",
			 "Aborting" ];
    # mythtv devs use some fancy bitmapped flags for these additional "done" states, handle those here
    $$status[0x0100] = "Done (Invalid status!)";	# 0x0100, 256
    $$status[0x0110] = "Finished";			# 0x0110, 272
    $$status[0x0120] = "Aborted";			# 0x0120, 288
    $$status[0x0130] = "Errored";			# 0x0130, 304
    $$status[0x0140] = "Cancelled";			# 0x0140, 320

    return $$status[$state];
}

sub parseJobQueueType {
# given a job type code, return a useful string
# see libs/libmythtv/jobqueue.h for job queue types
    my $type = shift;

    # arrays are easy, put the valid state strings in the proper order here
    my $jobtypes       = [ "None", "Transcode", "Flag Commercials" ];
    # mythtv devs use some fancy bitmapped flags for these additional job types, handle those here
    $$jobtypes[0x00ff] = "System Job";		# 0x00ff, 255
    $$jobtypes[0xff00] = "User Job";		# 0xff00, 65280
    $$jobtypes[0x0100] = "User Job 1";		# 0x0100, 256
    $$jobtypes[0x0200] = "User Job 2";		# 0x0200, 512
    $$jobtypes[0x0400] = "User Job 3";		# 0x0400, 1024
    $$jobtypes[0x0800] = "User Job 4";		# 0x0800, 2048
    
    return $$jobtypes[$type];
}

sub formatDate {
# convert date from db standard to US/Western standard M/D/YYYY
    my $date = shift;

    my @adate = split(/-/, $date);
	
    return "$adate[1]/$adate[2]/$adate[0]";
}

sub formatTime {
# convert time from db standard to US/Western standard, 12-hr AM/PM
    my $time = shift;

    my @atime = split(/:/, $time);
    my $m;   # as in aM or pM

    # convert 24-hr to 12-hr clock
    if ($atime[0] ge 12) {
	$m = "PM";
	if ($atime[0] ne 12) { $atime[0] -= 12; }
    } else {
	$m = "AM";
	if ($atime[0] eq 0) { $atime[0] = 12; }
    }
	
    $atime[0] =~ s/^0//;    # trim leading 0's as necessary

    return "$atime[0]:$atime[1] $m";
}
