#!/bin/ash
# Fatdog64 Drive Mounter (pmount replacement)
# Copyright (C) James Budiono 2012, 2015, 2019
#
# 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
#
# 131026 internationalisation by L18L

# 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 Drive Mounter')"
BLKID="busybox blkid"
PIDFILE=/tmp/drive-mounter.$USER.$XSESSION_ID

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

### runtime variables 
GDK_TYPE_PIXBUF= MAIN_WINDOW= TREEVIEW= LISTSTOTRE=
SELECTED_ITER= SELECTED_DEVNAME=
FALSE=0

# tree model
T_DEV=0 T_LABEL=1 T_FS=2 T_SIZE=3 T_MOUNTED=4

#---------------------------------------------------------- Global Inits
### global variables for proper GTK-server
GTK= NULL="NULL"
PIPE=/tmp/gtk.bash.$$
trap 'stop-gtk-server -fifo $PIPE; rm -f $PIDFILE; 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_LONG NULL
	gtk gtk_server_define gtk_tree_view_set_cursor NONE NONE 4 WIDGET WIDGET WIDGET BOOL
	
	# 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
# works with gtk-server 2.3.1 and 2.4.5
# $1-tree $2-iter $3-index $4-result name
tree_model_get_string() {
	local PSTR # pointer to string
	gtk gtk_server_define gtk_tree_model_get NONE NONE 5 WIDGET WIDGET INT PTR_LONG INT # ensure correct definition
	define PSTR gtk gtk_tree_model_get $1 $2 $3 NULL -1 # PSTR --> *buf
	
	if [ $PSTR -ne 0 ]; then
		# with newer gtk-server 2.4.4 onwards you can use this
		#define $4 gtk gtk_server_string_from_pointer $PSTR 
		define $4 gtk strcat $PSTR \"\" # get string point by address --> **buf
		gtk free $PSTR # this was dynamically alloc-ed by gtk_tree_model_get, now free it
	else
		eval $4=\"\"
	fi
}

###################### helpers #####################

single_instance() {
	local ppid
	if [ -e $PIDFILE ]; then
		read ppid < $PIDFILE
		# if process still exist, don't launch another instance
		[ $ppid ] && kill -0 $ppid && exit
	fi
	echo $$ > $PIDFILE
}

# returns SELECTED_DEVNAME, SELECTED_ITER
get_selected_device() {
	local SELECTED_PATH	
	[ -z $SELECTED_ITER ] && define SELECTED_ITER gtk gtk_server_opaque
	
	SELECTED_DEVNAME=""
	define SELECTED_PATH gtk gtk_tree_view_get_cursor $TREEVIEW NULL
	if [ $SELECTED_PATH -ne 0 ]; then
		gtk gtk_tree_model_get_iter $LISTSTORE $SELECTED_ITER $SELECTED_PATH	
		gtk gtk_tree_path_free $SELECTED_PATH

		# $1-tree $2-iter $3-index $4-result name
		tree_model_get_string $LISTSTORE $SELECTED_ITER $T_DEV SELECTED_DEVNAME
	fi
}

### simple, stupid, but quick way to determine drive type 
# $1-drivename $2-devpath returns DRIVE_TYPE ("primary-secondary")
guess_drive_type() {
	DRIVE_TYPE=any
	case $1 in 
		fd*) DRIVE_TYPE=floppy ;;
		sr*) DRIVE_TYPE=optical ;;
		mmc*) DRIVE_TYPE=card ;;
	esac
	case $2 in
		*usb*) DRIVE_TYPE="$DRIVE_TYPE-usb"
	esac
}

###################### signal handler #####################


### signal-handler: refresh device list
refresh_device_list() {
	Xdialog --title "$(gettext 'Working...')" --no-buttons --infobox "$(gettext 'Refreshing device list, please wait ...')" 0 0 10000000 &
	XPID=$!

	define ITER gtk gtk_server_opaque

	gtk gtk_server_define gtk_list_store_set NONE NONE 13 WIDGET WIDGET INT STRING INT STRING INT STRING INT STRING INT INT INT
	gtk gtk_list_store_clear $LISTSTORE
	$BLKID | grep -vE "loop.*:|ram.*:|nbd.*:|/dev/mapper/.*:" | sort -V -t : -k 1 | while read -r line; do
		LABEL="" TYPE=""
		dev=${line%%:*} dev=${dev#/dev/}
		attr=${line#*:}	
		eval $attr # we only use $TYPE and $LABEL
		MOUNTED=0
		dmdev=$(ls -l /dev/mapper/* 2> /dev/null | awk -v dmdev=${dev##*-} '$6 == dmdev {print $10; exit; }')
		[ "$dmdev" ] || dmdev=$(ls -l /dev/mapper/* 2> /dev/null | awk -v dmdev="/${dev##*/}$" '$NF ~ dmdev { print $(NF-2); exit; }')		
		grep -Eqm 1 "^/dev/$dev |^$dmdev " /proc/mounts && MOUNTED=1

		if [ "$(guess_fstype "/dev/$dev")" = 'crypto_LUKS' ] &&
			[ "$(find /sys/devices/virtual/block/dm-*/slaves -type l -name "$dev" 2>/dev/null | grep -wo 'dm-[0-9]\+')" != "" ]; then
			MOUNTED=1
		fi

		SIZE=$(( ( $(cat /sys/class/block/$dev/size) + (1024) ) / (2*1024) ))
		if [ $SIZE -gt 2048 ]; then
			SIZE="$(( ( $(cat /sys/class/block/$dev/size) + (1024*1024) ) / (2*1024*1024) )) GB"
		else
			SIZE="$SIZE MB"
		fi

		gtk gtk_list_store_append $LISTSTORE $ITER NULL	
		gtk gtk_list_store_set $LISTSTORE $ITER $T_DEV $dev $T_LABEL \""$LABEL"\" $T_FS \""$TYPE"\" $T_SIZE \""$SIZE"\" $T_MOUNTED $MOUNTED -1	
	done
	gtk free $ITER
	
	# set cursor on first item	
	define XXX gtk gtk_tree_path_new_from_string "0"
	gtk gtk_tree_view_set_cursor $TREEVIEW $XXX NULL $FALSE
	gtk gtk_tree_path_free $XXX
	
	kill $XPID
}

### signal-handler: update buttons when cursor is changed
# $1-DEVNAME $2-ITER(without /dev)
update_state() {
	[ -z $1 -o -z $2 ] && return
	guess_drive_type $1 $(readlink /sys/class/block/$1)

	# set mounted status
	gtk gtk_server_define gtk_list_store_set NONE NONE 5 WIDGET WIDGET INT INT INT
	MOUNTED=0
	dmdev=$(ls -l /dev/mapper/* 2> /dev/null | awk -v dmdev=${1##*-} '$6 == dmdev {print $10; exit; }')
	[ "$dmdev" ] || dmdev=$(ls -l /dev/mapper/* 2> /dev/null | awk -v dmdev="/${1##*/}$" '$NF ~ dmdev { print $(NF-2); exit; }')	
	grep -Eqm 1 "^/dev/$1 |^$dmdev " /proc/mounts && MOUNTED=1

	if [ "$(guess_fstype "/dev/$1")" = 'crypto_LUKS' ] &&
		[ "$(find /sys/devices/virtual/block/dm-*/slaves -type l -name "$1" 2>/dev/null | grep -wo 'dm-[0-9]\+')" != "" ]; then
		MOUNTED=1
	fi
	
	gtk gtk_list_store_set $LISTSTORE $2 $T_MOUNTED $MOUNTED -1

	# if optical, enable play
	# if not optical && not mounted, enable check integrity
	gtk gtk_widget_hide $PLAY_BUTTON
	gtk gtk_widget_hide $EJECT_BUTTON
	gtk gtk_widget_hide $CHECK_BUTTON
	case $DRIVE_TYPE in
		optical*) gtk gtk_widget_show $PLAY_BUTTON
				  gtk gtk_widget_show $EJECT_BUTTON ;;
		*) [ $MOUNTED = 0 ] && gtk gtk_widget_show $CHECK_BUTTON ;;
	esac

	# if usb && not mounted, enable remove
	gtk gtk_widget_hide $REMOVE_BUTTON
	case $DRIVE_TYPE in
		*usb*) [ $MOUNTED = 0 ] && gtk gtk_widget_show $REMOVE_BUTTON ;;
	esac
}

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

single_instance

### 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 LISTSTORE gtk gtk_builder_get_object $BUILDER devlist
define TREEVIEW gtk gtk_builder_get_object $BUILDER devview

define QUIT_BUTTON gtk gtk_builder_get_object $BUILDER quit
define REFRESH_BUTTON gtk gtk_builder_get_object $BUILDER refresh
define MOUNT_BUTTON gtk gtk_builder_get_object $BUILDER mount
define UMOUNT_BUTTON gtk gtk_builder_get_object $BUILDER umount
define REMOVE_BUTTON gtk gtk_builder_get_object $BUILDER remove
define EJECT_BUTTON gtk gtk_builder_get_object $BUILDER eject
define PLAY_BUTTON gtk gtk_builder_get_object $BUILDER play
define CHECK_BUTTON gtk gtk_builder_get_object $BUILDER check

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

gtk gtk_server_connect $TREEVIEW cursor-changed cursor-changed

gtk gtk_server_connect $REFRESH_BUTTON clicked refresh
gtk gtk_server_connect $MOUNT_BUTTON clicked mount
gtk gtk_server_connect $UMOUNT_BUTTON clicked umount
gtk gtk_server_connect $REMOVE_BUTTON clicked remove
gtk gtk_server_connect $EJECT_BUTTON clicked eject
gtk gtk_server_connect $PLAY_BUTTON clicked play
gtk gtk_server_connect $CHECK_BUTTON clicked check

# load devices
refresh_device_list
#gtk gtk_tree_sortable_set_sort_column_id $LISTSTORE 0 0

### gtk main loop
gtk gtk_widget_show_all $MAIN_WINDOW
while true; do 
	define EVENT gtk gtk_server_callback wait
	case $EVENT in
		quit)			break ;;
		refresh) 		refresh_device_list; continue ;;
		cursor-changed) get_selected_device ;;
		
		mount)		fatdog-drive-icon-action-handler.sh click /dev/$SELECTED_DEVNAME ;;
		umount)		fatdog-drive-icon-action-handler.sh umount /dev/$SELECTED_DEVNAME ;;
		play) 		fatdog-drive-icon-action-handler.sh defaultmediaplayer /dev/$SELECTED_DEVNAME ;;
		check)		fatdog-drive-icon-action-handler.sh fsck /dev/$SELECTED_DEVNAME ;;
		
		remove)		fatdog-drive-icon-action-handler.sh safe-remove /dev/$SELECTED_DEVNAME
					refresh_device_list; continue ;;
		eject)		fatdog-drive-icon-action-handler.sh eject /dev/$SELECTED_DEVNAME
					refresh_device_list; continue ;;
	esac
	update_state $SELECTED_DEVNAME $SELECTED_ITER
done
gtk gtk_server_exit 
rm -f $PIDFILE
