#!/bin/dash
# drive icon display daemon
# Copyright (C) James Budiono 2013, 2015, 2017
# License: GNU GPL Version 3 or later
#
# Version 1 - kick-start drive icon display and then quit
# Version 2 - this one runs as a daemon and manages the drive icons
# Version 3 - rox icons now fully managed here, not by udev-handler anymore
# Version 4 - single instance for same user on same $DISPLAY
# Version 5 - add support for mountmon
#

TEXTDOMAIN=fatdog
. gettext.sh

### configurations

# the following must be identical with those in udev-handler
UDEV_DRIVE_ICON_ROOT=/tmp/udev-fatdog-drive-icon
UDEV_DRIVE_ICON_EVENTS=$UDEV_DRIVE_ICON_ROOT/events
UDEV_DRIVE_ICON_DEVICES=$UDEV_DRIVE_ICON_ROOT/devices
UDEV_EVENT_COUNTER=$UDEV_DRIVE_ICON_ROOT/counter
COUNTER_MAX=100  # max number of queued events

UDEV_MAX_WAIT=30 # in seconds, wait time for udev-handler to get ready
PINBOARD_FILE=$HOME/.config/rox.sourceforge.net/ROX-Filer/PuppyPin
DRIVE_ICON_ROOT=/tmp/fatdog-drive-icons.$USER.$XSESSION_ID
FIFO_FILE=$DRIVE_ICON_ROOT/fifo

grep -q sandbox /etc/shinit && IN_SANDBOX=1
DEBUG_FILE=$DRIVE_ICON_ROOT/log
DEBUG=

# get the rox functions
. /usr/sbin/fatdog-drive-icon-roxlib.sh # create_rox_app, rox_add_icon, rox_remove_icon, get_free_coord
. $EVENTMANAGER_CONFIG	# override default settings in get_free_coord

# output $@ to debug file
log_debug() {
	[ "$DEBUG" ] && echo "$@" >> $DEBUG_FILE
}

### action handlers

# $1 dev (without /dev)
action_add() {
	if [ -e $UDEV_DRIVE_ICON_DEVICES/$1 ]; then
		. $UDEV_DRIVE_ICON_DEVICES/$1 # DRIVENAME, DRIVE_TYPE, DEVNAME, SUMMARY, VOLABEL		
		mkdir -p "$DRIVE_ICON_ROOT/$DRIVENAME"
		create_rox_app "$DRIVE_ICON_ROOT/$DRIVENAME" "$DRIVE_TYPE" "$DEVNAME" "$SUMMARY"
		get_free_coord $PINBOARD_FILE $ICON_ALIGNMENT # returnx COORD_X, COORD_Y
		rox_add_icon "$DRIVE_ICON_ROOT/$DRIVENAME" "$COORD_X" "$COORD_Y" "$(get_icon_label $DRIVENAME "$VOLABEL")" "click $DEVNAME"
		fatdog-drive-icon-refresh-icon.sh $DEVNAME		
	fi
}

# $1 dev (without /dev)
action_remove() {
	rox_remove_icon $DRIVE_ICON_ROOT/$1
	rm -rf $DRIVE_ICON_ROOT/$1
}

### the daemon, proper
rox_icon_daemon() {
	local p next current action dev

	read next < $UDEV_EVENT_COUNTER
	while true; do
		! [ -e $FIFO_FILE ] && break
		read p  # pause until we've got events to process
		[ "$p" = quit ] && break

		. $EVENTMANAGER_CONFIG	# reload, in case things have changed.
		read current < $UDEV_EVENT_COUNTER
		while [ "$next" -ne "$current" ]; do
			: $((next = next + 1))
			[ $next -gt $COUNTER_MAX ] && next=0
			
			read action dev < $UDEV_DRIVE_ICON_EVENTS/$next
			log_debug $action $dev
			case $action in
				add|change)	action_remove $dev; action_add $dev ;;
				remove)	action_remove $dev ;;
			esac
		done
	done < $FIFO_FILE

	rm -rf $DRIVE_ICON_ROOT
	echo Stop frontend
}

### the daemon monitor
rox_icon_daemon_monitor() {
	local xpid
	inotifyd - $UDEV_EVENT_COUNTER:c > $FIFO_FILE &
	xpid=$!
	
	echo run > $FIFO_FILE # kick start
	echo $xpid > $DRIVE_ICON_ROOT/pid
	inotifyd - $DRIVE_ICON_ROOT/pid:x > /dev/null
	echo quit > $FIFO_FILE
	kill $xpid
	echo Stop frontend monitor
}

### kill processes run by the same username and DISPLAY
stop_previous_instance() {
	local p pid

	for p in ${DRIVE_ICON_ROOT%.*}.*; do
		! [ -e $p ] && continue
		if [ $(stat -c %u $p) -eq $(id -u) ]; then
			if [ -e $p/pid ]; then
				read pid < $p/pid				
				# check if process is still up
				if kill -0 $pid 2>/dev/null; then
					disp="$(tr '\0' '\n' < /proc/$pid/environ | sed '/DISPLAY=/!d;s/DISPLAY=//')"
					[ $disp = $DISPLAY ] && rm -f $p/pid # same display, kill it
				else
					[ $IN_SANDBOX ] || rm -rf $p # process already dead, remove its ghosts
				fi
			else 
				rm -rf $p # process already dead, remove its ghosts
			fi
		fi
	done
	# wait until it is clean to start
	while [ -e $DRIVE_ICON_ROOT ]; do sleep 1; done
}


### mountmon stuff

# $1 - device (without /dev) $2-need delay
redraw_icon() {
	local XPID="" dev=$1
	if [ "$2" ]; then
		sleep 0.5	# needed due to small delay between umount and 12th field getting non-zero
		while [ $(awk -v dev=$dev '$3==dev {print $12}' /proc/diskstats) -ne 0 ]; do
			if [ -z $XPID ]; then			
				Xdialog --title "$(gettext 'WAIT')" --no-buttons \
						--infobox "$(eval_gettext 'Please wait, flushing $dev\nDO NOT REMOVE YOUR DISK ...')" 0 0 1000000 &
				XPID=$!
			fi		
			sleep 1
		done
		[ $XPID ] && kill $XPID
	fi
	
	action_remove "$1"
	action_add "$1"
}

mountmon_daemon() {
	# because exiting X does not kill it ...
	local pid
	for pid in $(pidof mountmon); do 
		if [ $(stat -c %u /proc/$pid) -eq $(id -u) ]; then
			disp=$(xargs -n1 -0 < /proc/$pid/environ  |  sed -n '/DISPLAY=/ {s/DISPLAY=//;p}')
			[ "$disp" = "$DISPLAY" ] && kill $pid
		fi
	done

	# daemon proper
	mountmon |
	while read -r EVENT; do
		set -- $EVENT
		ACTION=$1
		DEV=$2
		#MNT="${3}"
		case "$DEV" in
			/dev/loop*) continue ;; # ignore loop mounts
			/dev/*) ;;
			*) continue ;; # ignore none-device mounts
		esac
		DEV="${DEV##/dev/}"
		case "$DEV" in
			'mapper'*)
			DEV="dm-$(dmsetup ls | sed -n 's/^'${DEV#*mapper/}'\t.*:\(.*\)).*/\1/p')"
			;;
		esac
		case $ACTION in
			DEL:)	redraw_icon $DEV wait & ;;
			*)		redraw_icon $DEV ;;
		esac
	done
}

################# main ###################

# 1. kill previous instance, if any
stop_previous_instance

# 2. wait for udev to get ready
while ! [ -e $UDEV_EVENT_COUNTER ] && [ $UDEV_MAX_WAIT -gt 0 ]; do
	sleep 1
	UDEV_MAX_WAIT=$(( $UDEV_MAX_WAIT - 1 ))
done
# if still not ready after timeout expires, just leave
! [ -e $UDEV_EVENT_COUNTER ] && echo "udev not ready, bailing out" && exit 

# 3. prepare daemon
mkdir -p $DRIVE_ICON_ROOT; mkfifo $FIFO_FILE
rox_icon_daemon &

# 4. displaying what udev has processed so far
ls -v $UDEV_DRIVE_ICON_DEVICES | while read -r p; do action_add $p; done

# 5. run daemon and its monitor
rox_icon_daemon_monitor &

# 6. mountmon
mountmon_daemon &
