#!/bin/sh -
#
#   worldwatch -- Script to watch the progress on "make buildworld".
#
#   Copyright (C) 2007 by Oliver Fromme  <olli@fromme.com>  <olli@secnetix.de>
#   All rights reserved.
#
#   Usage:  Just type "worldwatch" instead of "make buildworld".
#           You must be in the /usr/src directory, of course.
#           Options are passed directly to make(1), so you can
#           type "worldwatch -j 4", for example.
#

LOGDIR="/tmp"		# Log output from buildworld here.
STTDIR="/var/db"	# Record statistics data here.

ME="${0##*/}"

read win_rows win_cols <<daer
$(
	stty -a |
	awk '{
		if (!rows && / row/) {
			rows = $0
			sub(/ *row.*$/, "", rows)
			sub(/^.*[^0-9]/, "", rows)
		}
		if (!cols && / col/) {
			cols = $0
			sub(/ *col.*$/, "", cols)
			sub(/^.*[^0-9]/, "", cols)
		}
		if (rows && cols) {
			print rows, cols
			exit
		}
	}'
)
daer

case "${win_rows}:${win_cols}" in
	:*|*:|*[!0-9:]*|*:*:*)
		echo "${ME}:  Cannot determine number of rows and columns!" >&2
		exit 1
		;;
esac

max_width=$(( $win_cols - 1 ))
if [ $max_width -gt 79 ]; then
	max_width=79
fi

if [ "_${WORLDWATCH_ACTIVE}" = _yes ]; then
	#
	#   OK, we're running as a shell process inside window(1).
	#   Next we need to find out whether we're running in the
	#   progress window (that's just two lines at the top) or
	#   in the buildworld window.  To do that, we simply look
	#   at the number of rows we have.
	#
	export SHELL=/bin/sh
	if [ $win_rows -eq 2 ]; then
		#
		#   We're in the progress window.  Just keep
		#   reading lines from the FIFO and display
		#   them appropriately.
		#
		export TC_HOME="$(tput ho)"
		awk '

		BEGIN {
			prog = stage = ""
			tc_home = ENVIRON["TC_HOME"]
		}

		{
			if (/^>/) {
				stage = "  " $0
				printf "%-'$max_width's%s", prog stage, tc_home
				fflush()
			}
			else if (/^=/) {
				printf "\n%-'$max_width's%s", $0, tc_home
				fflush()
			}
			else {
				prog = $0 "  "
				printf "%s%s", prog, tc_home
				fflush()
			}
		}

		' "$FIFO"
	else
		#
		#   We're in the buildworld window.  Run the
		#   actual buildworld command and filter the
		#   output while feeding the FIFO.
		#
		#   When done (or interrupted), send a SIGHUP
		#   to our parent which is the window(1) process.
		#
		my_pid=$$
		parent=$(ps -alx | awk '($2=="'$my_pid'"){print $3;exit}')

		Cancel()
		{
			echo "C" > "${FIFO%/*}/exitcode"
			kill -HUP $parent
			sleep 0.5
			exit 0
		}

		trap Cancel 1 2 3 15

		{
			make $MAKE_ARGS < /dev/null 2>&1
			echo "$?" >> "${FIFO%/*}/exitcode"
		} \
		| awk '

		function hms (s) {
			if (s < 3600)
				return sprintf("%d:%02d", s/60, s%60)
			_h = int(s/3600)
			s %= 3600
			return sprintf("%d:%02d:%02d", _h, s/60, s%60)
		}

		BEGIN {
			eta = 0
			last = 0
			perc = 0
			fifo = "'"$FIFO"'"
			data = "'"$DATA"'.new"
			prev = "'"$DATA"'"
			full = "'"$FULL"'"
			start = '"$SYSTIME"'

			while ((getline < prev) > 0) {
				oldmax = $1 + 0
				tag = $2 " " $3
				if (tm[tag]) {
					for (i = 2; tm[tag i]; i++) ;
					tag = tag i
				}
				tm[tag] = oldmax
			}
			close (prev)
		}

		{
			print
			print > full
			if (/^(===|>>)> /) {
				now = '"$SYSTIME"'
				if (/^=/) {
					offs = now-start
					print offs, $2, $3 > data
					tag = $2 " " $3
					if (seen[tag]) {
						for (i = 2; seen[tag i]; i++) ;
						tag = tag i
					}
					seen[tag] = 1
					oldoffs = tm[tag]
					if (oldoffs > 0) {
						perc = (oldoffs * 100) / oldmax
						eta = (oldmax * offs) \
						    / oldoffs - offs
					}
				}
				if (now != last || /^>/) {
					last = now
					print $0 > fifo
					printf "%s (%.1f%%), %s to go.\n", \
					    hms(now-start), perc, hms(eta) \
					    > fifo
				}
			}
		}

		END {
			dur = '"$SYSTIME"' - start
			print "Total duration:", dur, "seconds =", hms(dur)
		}

		'

		sleep 0.1
		trap 1 2 3 15
		kill -HUP $parent
	fi
	#
	#   We cannot exit right away here, because window(1)
	#   prompts for a command when a process exits.  We
	#   do not want that.  Therefore we wait in a small
	#   loop.  We will either get killed when window(1)
	#   terminates, or -- just to be safe -- we exit when
	#   the FIFO doesn't exist any longer.
	#
	#   Actually the time window is very small, so the
	#   loop body probably won't be executed more than
	#   once.  Therefore this kind of "polling" isn't as 
	#   bad as it might look.  ;-)
	#
	while :; do
		if [ ! -e "$FIFO" ]; then
			exit 0
		fi
		sleep 1
	done
fi

#
#   At this point we're running as the "master" process
#   which will call window(1) for spawning the above "slaves".
#   First try to parse the command line.
#

MAKE_ARGS="$*"
export MAKE_ARGS

#
#   Look for the make target.  Take the last parameter that
#   doesn't look like a variable assignment.  If not found,
#   the default is "buildworld".
#

while [ $# -gt 0 ]; do
	case "$1" in
		-[ABeiknPqrSstvX])
			;;
		-[CDdEfIjmVx]?*)
			;;
		-[CDdEfIjmVx])
			shift
			;;
		-*)
			echo "Cannot parse argument: \"$1\"" >&2
			exit 1
			;;
		*=*)
			;;
		*)
			WW_TARGET="$i"
			;;
	esac
	shift
done
if [ -z "$WW_TARGET" ]; then
	WW_TARGET="buildworld"
	MAKE_ARGS="$MAKE_ARGS buildworld"
fi

#
#   Now check the terminal size, then create the FIFO.
#

if [ $win_rows -lt 10 ]; then
	echo "${ME}:  Terminal is too small, need at least 10 rows!" >&2
	exit 1
fi

#
#   FreeBSD 4 has gnu awk, FreeBSD 6 has bsd awk.  Unfortunately
#   the latter is crippled and doesn't support systime().  We try
#   to find out if systime() is supported.  If not, we use srand()
#   instead ...  it's a dirty hack, but it works most of the time.
#

case "$(awk 'BEGIN{print systime()}' 2>/dev/null)" in
	""|*[!0-9]*)
		SYSTIME="srand()"
		;;
	*)
		SYSTIME="systime()"
		;;
esac
export SYSTIME

#
#   Create a FIFO for communication between the slave processes.
#

fifo_dir=$(mktemp -d "$LOGDIR/${ME}.tmp.$$.XXXXXX")

Cleanup()
{
	rm -rf "$fifo_dir"
	exit 1
}

trap Cleanup 1 2 3 15
FIFO="$fifo_dir/fifo"
export FIFO
mkfifo "$FIFO"
DATA="$STTDIR/${ME}.stat"
export DATA
FULL="$LOGDIR/${ME}.log"
export FULL

#
#   Now we're ready for action.
#

export WORLDWATCH_ACTIVE=yes
SHELL="$0" window -t -f -c 'cursormodes(0);window(0,0,2);window(3,0)'
exit_code=$(head -1 "$fifo_dir/exitcode")

#
#   Clean up and exit.
#

rm -rf "$fifo_dir"

if [ "$exit_code" = C ]; then
	echo "${ME} cancelled."
	exit_code=1
elif [ "${exit_code:-0}" = 0 ]; then
	echo "${ME} finished successfully."
	mv -f "$DATA".new "$DATA"
else
	echo "${ME} terminated with exit code ${exit_code}."
fi

exit ${exit_code:-0}

#-- 
