#!/bin/bash
#
# headless_pianobar
#
# Copyright (c) 2012 Daniel Thau
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
### Description/instructions:
#
# This script to allow pianobar to run headlessly as well as re-connect to
# pianobar's UI.
#
# When this script is launched, if pianobar isn't running, it will launch
# pianobar (in the background) and then immediately connect to pianobar's UI to
# allow the user to do things such as log in.  If pianobar is running, this
# script will simply connect to pianobar's UI.
#
# If you close this script with ctrl-c or by closing the terminal, pianobar
# should continue running in the background.
#
# To close pianobar, press the key mapped to close pianobar ('q' by default).
# This script should detect that pianobar died and close as well.
#
### Known issues/limitations:
#
# When the user is at a text input field - such as login or naming a station -
# mappings to quit pianobar ('q' by default) will not quit pianobar.  Moreover,
# techniques such as ctrl-c or closing the terminal will not quit pianobar
# either, as they only quit this script and leave pianobar running headlessly.
# To quit pianobar from this situation, one must either complete the text entry
# and get pianobar to a state where it will accept the quit mapping, or one
# must kill pianobar from outside of this script through commands such as
# `kill`.
#
# This script was initially intended to be bourne-shell compatible and
# portable, however one major limitation was found: the bourne shell does not
# seem to have a good way to read a single character at a time such as can be
# done with bash's "-n" flag.  Thus this script is dependent on less portable
# techniques and currently requires bash.


# Set default config options, as defined in the pianobar man page. Only set for variables actually used.
# They can all be overridden by the main pianobar config file.
# headless_pianobar options:
# check_period: sets how frequently the script checks if pianobar is running
# output_lines: sets how many lines of pianobar output to print when reconnecting
CHECK_PERIOD=${CHECK_PERIOD:-"1"}
OUTPUT_LINES=${OUTPUT_LINES:-"30"}
ACT_SONGPLAY=P
ACT_SONGPAUSETOGGLE=p
ACT_SONGPAUSETOGGLE2=' ' # space
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
FIFO=$XDG_CONFIG_HOME/pianobar/ctl
OUT=$XDG_CONFIG_HOME/pianobar/out
CONFIG=$XDG_CONFIG_HOME/pianobar/config


# Load the pianobar config file as bash variables
# also strip password info for security reasons
# ex: act_upcoming becomes ACT_UPCOMING
# ex: user becomes PIANOBAR_USER (in order to avoid conflicts with the user environment variable)
load_config(){
        if [[ -r $CONFIG ]]
        then
                source <(sed -n \
                        -e 's/password.*//' \
                        -e 's/user/pianobar_user/' \
                        -e 's/\([0-9A-Za-z_]*\) = \(.*\)/\U\1\E="\2"/p' < $CONFIG)
        else
                echo "Couldn't load config file $CONFIG, using defaults"
                false
        fi
}

is_running(){
        ps -u $(id -u) -o comm | grep -q "^pianobar$"
}

# Launch pianobar if it is not already running.
launch(){
        if ! is_running
        then
                ensure_fifo true
                echo "Pianobar not running, launching Pianobar"
                nohup pianobar &>$OUT &disown
                sleep 1
        else
                ensure_fifo
        fi
}

# Check on a regular basis that pianobar is still running.
# If pianobar stops running, stop this script as well.
running(){
        while is_running
        do
        	sleep $CHECK_PERIOD
        done
        echo "Pianobar died, quitting"
        quit
}

# Function to cleanly quit.  Ensures that the two backgrounded processes (the
# output, tail, and the check, running()) both exit along with the parent (this
# script itself)
quit(){
	# tail and running() might both die when the parent dies as there was no
	# nohup, but double-check just to make sure.  Don't want to leave a mess
	# behind.
	if [ ! -z $TAILPID ]
	then
		kill $TAILPID 2>/dev/null
	fi
	if [ ! -z $RUNNINGPID ]
	then
		kill $RUNNINGPID 2>/dev/null
	fi
	trap - HUP INT TERM
	kill $$
	exit 0
}

# Print pianobar's output.
output(){
        # Sanity check: ensure pianobar's output can be read.
        if [ ! -f $OUT ]
        then
                echo "pianobar does not seem to be outputting to $OUT, try killing it and starting $0 again"
                exit 2
        fi

        tail -n$OUTPUT_LINES -f $OUT &
        TAILPID=$!
}

# Get input from user, character by character, and feed it to pianobar's ctl
# fifo.  Note that no newline character is given with `read`.  Rather, one
# simply gets an empty variable back.  Detect this situation and pass a newline
# along to pianobar.  Otherwise, send the character read from input to
# pianobar. If ^D or EOF is given, quit
input(){
        IFS=""
        while /bin/true
        do
                read -n1 -s INPUT
                if [[ $? == "1" || "$INPUT" == $'\004' ]]
                then
                        quit
                elif [ "$INPUT" == "" ]
                then
                        send_input "\n"
                else
                        send_input "$INPUT"
                fi
        done
}

# send the first argument to the control fifo
send_input(){
        echo -ne "$1" > $FIFO
}

# Ensure the ctrl fifo exists. Arg1: create if it doesn't exist
ensure_fifo(){
        if [ ! -p $FIFO ]
        then
                if [[ $1 ]]
                then
                        echo "Pianobar ctl fifo not present at $FIFO, creating"
                        mkfifo $FIFO
                        if [ ! -p $FIFO ]
                        then
                                echo "Failed to create fifo, aborting"
                                exit 1
                        fi
                else
                        echo "Pianobar ctl fifo not present at $FIFO, aborting"
                        exit 1
                fi
        fi
}

# Ensure quit() is called to clean up when exiting
trap quit HUP INT TERM

mkdir -p $XDG_CONFIG_HOME/pianobar

# load the main pianobar config file
# Don't use $XDG_CONFIG_HOME or $HOME after this because it may have changed if set in the config file.
load_config

# if no arguments, launch pianobar if not running, then connect to the pianobar ui
if [[ $# == 0 ]]
then
        launch

        # Run running() in the background to detect when pianobar closes.
        running &
        RUNNINGPID=$!

        output

        input
else
        if ! is_running
        then
# if there are arguments, and pianobar is not running, start pianobar if it makes sense to
                case $1 in
                        $ACT_SONGPLAY | $ACT_SONGPAUSETOGGLE | $ACT_SONGPAUSETOGGLE2)
                                launch
                                shift ;;
                        *)
                                echo "Pianobar not running, nothing executed"
                                exit 0
                                ;;
                esac
        fi
# if there are arguments and pianobar is running, send arguments to pianobar, then exit
        ensure_fifo
        while [[ $# > 0 ]]; do
                send_input "$1"
                shift
        done
fi