#!/bin/ash
# Fatdog64 Service Manager
# Copyright (C) James Budiono 2012, 2014
#
# License: GNU GPL Version 3 or later
#
# Note: the bits in Global Inits, which is part of GTK-server bash init code
#       is (C) Peter van Eerten. Everything else is (C) James Budiono
#
#
# L18L internationalisation

# std localisation stanza
export TEXTDOMAIN=fatdog
. gettext.sh
# performance tweak - use "C" if there is no localisation
! [ -e $TEXTDOMAINDIR/${LANG%.*}/LC_MESSAGES/$TEXTDOMAIN.mo ] &&
! [ -e $TEXTDOMAINDIR/${LANG%_*}/LC_MESSAGES/$TEXTDOMAIN.mo ] && LANG=C

### configuration
APPTITLE="$(gettext 'Fatdog64 Service Manager')"
SERVICE_DIR=/etc/init.d
RESERVED=":S01logging:S40network:README.md:"
[ -e /etc/servicemanager ] && . /etc/servicemanager	# override default settings

[ $(id -u) -ne 0 ] && exec gtksu "$APPTITLE" "$0"

### runtime variables
GDK_TYPE_PIXBUF=
MAIN_WINDOW=
SELECTED_ITER=
SELECTED_SERVICE=

# servicestore model
T_NAME=0 T_FILE=1 T_ENABLED=2 T_RUNNING=3

#---------------------------------------------------------- Global Inits
### global variables for proper GTK-server
GTK= NULL="NULL"
PIPE=/tmp/gtk.bash.$$
trap 'stop-gtk-server -fifo $PIPE; exit' HUP TERM INT # stop GTK-server in case of an error

### start GTK-server
gtk-server -fifo=$PIPE &
while [ ! -p $PIPE ]; do sleep 1; continue; done

### GTK-server helper functions
# Assignment function
define() { $2 $3 $4 $5 $6 $7 $8 $9; eval $1=\'"$GTK"\'; }

# Communicate with GTK-server
gtk()
{
/bin/echo "$@" > $PIPE
read GTK < $PIPE
}

#---------------------------------------------------------- GUI realization

# add GTK+ API not defined in gtk-server.cfg
add_missing_gtk_api() {
	gtk gtk_server_define gdk_pixbuf_get_type NONE INT 0
	gtk gtk_server_define gtk_label_set_markup NONE NONE 2 WIDGET STRING
	
	# add GTK+ API not defined in gtk-server.cfg
	gtk gtk_server_define gtk_builder_new NONE WIDGET 0
	gtk gtk_server_define gtk_builder_add_from_file NONE INT 3 WIDGET STRING NULL
	gtk gtk_server_define gtk_builder_add_from_string NONE INT 4 WIDGET STRING INT NULL
	gtk gtk_server_define gtk_builder_get_object NONE WIDGET 2 WIDGET STRING
	gtk gtk_server_define gtk_builder_connect_signals NONE NONE 2 WIDGET NULL	
	
	gtk gtk_server_define gtk_tree_view_get_cursor NONE NONE 3 WIDGET PTR_WIDGET NULL
	
	# hack for getting unlimited string length and leak-free operation with gtk_tree_get_model
	# see below
	gtk gtk_server_define strcat NONE STRING 2 POINTER STRING
	gtk gtk_server_define memcpy NONE NONE 3 PTR_LONG POINTER INT			
}

### helper - leak-free, unlimited string length from gtk_tree_model_get with unpatched gtk-server
# $1-tree $2-iter $3-index $4-result name
# static variable to speed up stuff
STATIC_BUF=
STATIC_ADDRESS=
tree_model_get_string() {
	[ -z "$STATIC_BUF" ] && define STATIC_BUF gtk malloc 32 # gtk-server MAX_DIG is 32 bytes
	
	gtk gtk_server_define gtk_tree_model_get NONE NONE 5 WIDGET WIDGET INT POINTER INT # ensure correct definition
	gtk gtk_tree_model_get $1 $2 $3 $STATIC_BUF -1
	
	gtk memcpy NULL $STATIC_BUF 32	# print address stored in $BUF --> *buf
	STATIC_ADDRESS=$GTK
	if [ $STATIC_ADDRESS -ne 0 ]; then
		gtk strcat $STATIC_ADDRESS \"\" # get string point by address --> **buf	
		eval $4=\'"$GTK"\'				# this will be our return value	
		gtk free $STATIC_ADDRESS		# this was dynamically alloc-ed by gtk_tree_model_get, now free it
	else
		eval $4=\"\"
	fi
}

### helper - display progress
# $1-text
display_progress() {
	gtk gtk_label_set_markup $PROGRESS_LABEL \""$1"\"
	gtk gtk_widget_show_all $PROGRESS_WINDOW
	flush_gtk
}
hide_progress() {
	gtk gtk_widget_hide $PROGRESS_WINDOW
	flush_gtk
}
flush_gtk() {
	gtk gtk_events_pending
	while [ $GTK -eq 1 ]; do
		gtk gtk_main_iteration
		gtk gtk_events_pending
	done	
}

### check for reserved services which we don't want to show
# $1-service file
is_reserved() {
	local p=${1##*/}
	case "$RESERVED" in 
		*:${p}:*) return 0
	esac
	return 1
}

### helper
#$1-service-file,$2-service-name, output DESC and HELP
get_service_description() {
	local p
	DESC="" HELP=""
	
	# attempt to find description and help from service file itself
	while read -r p; do
		case "$p" in
			*[Dd]escription:*) DESC=${p#*: } ;;
			*[Hh]elp*) HELP=${p#*: } ;;
		esac
	done << EOF
$(grep -iE '^#.*(description:|help:)' $SERVICE_DIR/$1 2>/dev/null)
EOF

	# legacy way of getting description and help
	if [ -z "$DESC" ]; then
		OIFS=$IFS; IFS=";"
		for p in $SERVICE_DESC; do
			if [ $2 = "${p%%|*}" ]; then
				DESC=${p##*|}
				break
			fi
		done
		
		for p in $SERVICE_HELP; do
			if [ $2 = "${p%%|*}" ]; then
				HELP=${p##*|}
				break
			fi
		done
		IFS=$OIFS
	fi
}

### get actual service status
# $1-service-file returns SNAME RUNNING ENABLED
get_service_status() {
	case $1 in
		??-*) SNAME=${1#*-} ;;
		S??*) SNAME=${1#S??} ;;
		rc.*) SNAME=${1#rc.} ;;
		*) SNAME=${1} ;;
	esac
	
	ENABLED=0
	[ -x $SERVICE_DIR/$1 ] && ENABLED=1

	RUNNING=0
	sh $SERVICE_DIR/$1 status | sed 's/not running/stopped/'| grep -q running && RUNNING=1	
}

### helper - refresh the content of userview by loading it from /etc/group
refresh_serviceview() {
	local ITER p sfile SNAME ENABLED RUNNING
	define ITER gtk gtk_server_opaque
	
	gtk gtk_list_store_clear $SERVICESTORE
	gtk gtk_server_define gtk_list_store_set NONE NONE 11 WIDGET WIDGET INT STRING INT STRING INT INT INT INT INT
	
	for p in $SERVICE_DIR/*; do
		sfile=${p#$SERVICE_DIR/}
		is_reserved $sfile && continue 
		
		get_service_status $sfile
		gtk gtk_list_store_append $SERVICESTORE $ITER NULL		
		gtk gtk_list_store_set $SERVICESTORE $ITER $T_NAME $SNAME $T_FILE $sfile $T_ENABLED $ENABLED $T_RUNNING $RUNNING -1
	done
	
	gtk free $ITER
}

### helper - get the currently selected userid
# returns SELECTED_SERVICE SELECTED_ITER (must be freed)
get_selected_service() {
	local SELECTED_PATH
	define SELECTED_ITER gtk gtk_server_opaque
	
	define SELECTED_PATH gtk gtk_tree_view_get_cursor $SERVICEVIEW NULL
	if [ $SELECTED_PATH -ne 0 ]; then
		gtk gtk_tree_model_get_iter $SERVICESTORE $SELECTED_ITER $SELECTED_PATH	
		gtk gtk_tree_path_free $SELECTED_PATH

		# $1-tree $2-iter $3-index $4-result name
		tree_model_get_string $SERVICESTORE $SELECTED_ITER $T_FILE SELECTED_SERVICE
	fi
}


### signal handler - update and display service details info
# global SELECTED_SERVICE & SELECTED_ITER
update_service_details() {
	get_service_status $SELECTED_SERVICE
	get_service_description $SELECTED_SERVICE $SNAME 
	msg="$DESC

$(gettext 'Start at boot:') $([ $ENABLED -eq 1 ] && echo yes || echo no)
$(gettext 'Currently running:') $([ $RUNNING -eq 1 ] && echo yes || echo no)
$(gettext 'Control file:') $SELECTED_SERVICE
"
	gtk gtk_label_set_markup $SERVICE_DETAILS_FRAME_LABEL \""<b>$SNAME</b>"\"
	gtk gtk_label_set_markup $SERVICE_DETAILS_LABEL \""$msg"\"
	gtk gtk_widget_show $SERVICE_DETAILS_FRAME	
	
	gtk gtk_server_define gtk_list_store_set NONE NONE 7 WIDGET WIDGET  INT INT INT INT INT	
	gtk gtk_list_store_set $SERVICESTORE $SELECTED_ITER $T_ENABLED $ENABLED $T_RUNNING $RUNNING -1	
}

################### main ###################
### gtk-server localisation stanza
gtk gtk_server_define setlocale NONE STRING 2 INT STRING
gtk gtk_server_define bindtextdomain NONE STRING 2 STRING STRING
gtk gtk_server_define textdomain NONE STRING 1 STRING
gtk setlocale 6 \"\" # 6 corresponds to LC_ALL
gtk bindtextdomain $TEXTDOMAIN $TEXTDOMAINDIR
gtk textdomain $TEXTDOMAIN

### init gui
add_missing_gtk_api
gtk gtk_init
define GDK_TYPE_PIXBUF gtk gdk_pixbuf_get_type

define BUILDER gtk gtk_builder_new
define RESULT gtk gtk_builder_add_from_file $BUILDER "${0}.xml"
gtk gtk_builder_connect_signals $BUILDER

define MAIN_WINDOW gtk gtk_builder_get_object $BUILDER main_window
define CLOSE_BUTTON gtk gtk_builder_get_object $BUILDER close-button

define SERVICE_DETAILS_FRAME gtk gtk_builder_get_object $BUILDER service-details-frame
define SERVICE_DETAILS_LABEL gtk gtk_builder_get_object $BUILDER service-details-label
define SERVICE_DETAILS_FRAME_LABEL gtk gtk_builder_get_object $BUILDER service-details-frame-label

define START_BUTTON gtk gtk_builder_get_object $BUILDER start-button
define STOP_BUTTON gtk gtk_builder_get_object $BUILDER stop-button
define ENABLE_BUTTON gtk gtk_builder_get_object $BUILDER enable-button
define DISABLE_BUTTON gtk gtk_builder_get_object $BUILDER disable-button
define HELP_BUTTON gtk gtk_builder_get_object $BUILDER help-button

define SERVICEVIEW gtk gtk_builder_get_object $BUILDER serviceview
define SERVICESTORE gtk gtk_builder_get_object $BUILDER servicestore

define PROGRESS_WINDOW gtk gtk_builder_get_object $BUILDER progress_window
define PROGRESS_LABEL gtk gtk_builder_get_object $BUILDER progress-label

# connect signals
gtk gtk_server_connect $MAIN_WINDOW delete-event quit
gtk gtk_server_connect $CLOSE_BUTTON clicked quit

gtk gtk_server_connect $SERVICEVIEW cursor-changed cursor-changed

gtk gtk_server_connect $START_BUTTON clicked start-service
gtk gtk_server_connect $STOP_BUTTON clicked stop-service
gtk gtk_server_connect $ENABLE_BUTTON clicked enable-service
gtk gtk_server_connect $DISABLE_BUTTON clicked disable-service
gtk gtk_server_connect $HELP_BUTTON clicked service-help

# preparation stuff
refresh_serviceview

### gtk main loop
gtk gtk_widget_show_all $MAIN_WINDOW
gtk gtk_widget_hide $SERVICE_DETAILS_FRAME
while true; do 
	define EVENT gtk gtk_server_callback wait
	case $EVENT in
		quit)
			break ;;
			
		cursor-changed)
			[ "$SELECTED_ITER" ] && gtk free $SELECTED_ITER
			get_selected_service
			update_service_details ;;
		
		start-service)
			display_progress "$(eval_gettext 'Starting $SELECTED_SERVICE ...')"		
			if [ $RUNNING -eq 1 ]; then		
				sh $SERVICE_DIR/$SELECTED_SERVICE stop
				sleep 1	
			fi
			sh $SERVICE_DIR/$SELECTED_SERVICE start
			sleep 1 # allow time to start up gracefully			
			update_service_details ;;
			
		stop-service)
			if [ $RUNNING -eq 1 ]; then
				display_progress "$(eval_gettext 'Stopping $SELECTED_SERVICE ...')"
				sleep 1	# allow time to shutdown gracefully
				sh $SERVICE_DIR/$SELECTED_SERVICE stop
				update_service_details
			fi ;;
			
		enable-service)
			if [ $ENABLED -eq 0 ]; then
				chmod +x $SERVICE_DIR/$SELECTED_SERVICE
				update_service_details
			fi ;;
			
		disable-service)
			if [ $ENABLED -eq 1 ]; then	
				chmod -x $SERVICE_DIR/$SELECTED_SERVICE
				update_service_details
			fi ;;
			
		service-help)
			if [ "$HELP" ]; then
				$HELP &
			else 
				Xdialog --title "$(gettext 'Sorry')" --infobox "$(eval_gettext 'No information available for $SNAME.')" 0 0 10000
			fi ;;
			
	esac
	hide_progress
done
gtk gtk_server_exit 
