#!/bin/dash
# Fatdog64 Samba Manager - add/remove samba shares
# (C) James Budiono 2019
# License: GPLv3

################### LIBRARY SECTION ####################

# run-time variables
SMB_CONF=       # location of smb.conf, setup by init_smb_lib
KEEP_WORKDIR=   # flag to whether or not we remove workdir when done
WORKDIR=        # place where the lib will create its working files
SPLIT_CONF_DIR= # where the split config files will be stored
IDX_MAX=0       # max index file as returned by split_smb_conf, may be adjusted later


############### startup/stop library ##########################3
#
cleanup_smb_lib() {
	[ -z "$KEEP_WORKDIR" ] && [ -d "$WORKDIR" ] && rm -r $WORKDIR
}

# $1-location of SMB_CONF (optional), $2-workdir location (optional), $3-preserve workdir (optional)
init_smb_lib() {
	SMB_CONF="${1:-/etc/samba/smb.conf}"
	WORKDIR="$2" KEEP_WORKDIR="$3"

	IDX_MAX=0
	if [ "$WORKDIR" ]; then
		mkdir -p "$WORKDIR"
	else
		WORKDIR=$(mktemp -dp /tmp smbconf-XXXXXX)
	fi
	SPLIT_CONF_DIR="$WORKDIR"/conf
	split_smb_conf "$SMB_CONF"
	trap 'cleanup_smb_lib; exit;' INT HUP TERM 0
}


###################### core library functions ######################

# $1-location of smb.conf, resets IDX_MAX
split_smb_conf() {
	local p fn="" idx=0
	mkdir -p "$SPLIT_CONF_DIR"
	[ -e "$1" ] || return
	while read -r p; do
		case "$p" in
			\[*\]) fn=$(printf "%0.4d" $idx); idx=$((idx + 1));
			       echo "$p" > "$SPLIT_CONF_DIR/$fn" ;;  # start new file
			*)     [ "$fn" ] && echo "$p" >> "$SPLIT_CONF_DIR/$fn" ;; # store contents to file
		esac
	done < "$1"
	IDX_MAX=$idx
}

# #1-where to put combined smb.conf
join_smb_conf() {
	local out=${1:-$SMB_CONF}
	cat "$SPLIT_CONF_DIR"/* > "$out"
}

# returns all numbered index of sections
get_sections() {
	ls "$SPLIT_CONF_DIR"
}

# $1-index, true/false exit
is_section_exist() {
	test -e "$SPLIT_CONF_DIR"/$1 
}

# $1-index, returns section name
get_section_name() {
	local p
	is_section_exist $1 || return 1
	read -r p < "$SPLIT_CONF_DIR"/$1
	p=${p#\[}; p=${p%\]}
	echo $p
}

# $1-index, return path to section file
get_section_path() {
	is_section_exist $1 || return 1
	echo "$SPLIT_CONF_DIR"/$1
}

# $1-name, returns index of a section with a given name, or blank if not exist
# find the first found name
get_section_by_name() {
	( cd "$SPLIT_CONF_DIR"; grep -Fil "[$1]" * | head -n1)
}

# $1-name
get_section_path_by_name() {
	local p
	p="$(get_section_by_name "$1")"
	test -z "$p" && return 1
	p="$(get_section_path $p)" || return 1
	echo "$p"
}

# $1-index (to overwrite) or "new" to use IDX_MAX
# $2-share name, $3-path, $4-user $5-private
create_new_section() {
	local idx=$1 share="$2" path="$3" user="${4:-root}" private=$5
	if [ "$idx" = "new" ]; then
		idx=$(printf "%0.4d" $IDX_MAX)
		IDX_MAX=$((IDX_MAX + 1))
	fi
	if [ -z "$private" ]; then
		# public share
		cat > "$SPLIT_CONF_DIR/$idx" << EOF
[$share]
path = $path
writable = yes
read only = no
force user = $user
guest ok = yes
guest only = yes

EOF
	else
		# private share
		cat > "$SPLIT_CONF_DIR/$idx" << EOF
[$share]
path = $path
writable = yes
valid users = $user
public = no
printable = no

EOF
	fi
}

# $1-index
remove_section() {
	rm -f "$SPLIT_CONF_DIR"/$1
}


######## no SPLIT_CONF_DIR after this line ########


# $1-index, true/false if section is a printer
is_printer_section() {
	local p
	p="$(get_section_path $1)" || return 1
	grep -qi "printable.*=.*yes" "$p"
}

# $1-index, true/false if section is a global section
is_global_section() {
	local p
	p="$(get_section_name $1)"
	[ -z "$p" ] && return 1
	case "$p" in
		[Gg][Ll][Oo][Bb][Aa][Ll]) return 0;
	esac
	return 1
}

# $1-index, true/false if section is a global section
is_share_section() {
	! is_global_section $1 && ! is_printer_section $1 && return 0
	return 1
}

# $1-index, returns type
get_section_type() {
	if ! is_section_exist $1; then
		echo NONE
	elif is_global_section $1; then
		echo GLOBAL
	elif is_printer_section $1; then
		echo PRINTER
	elif is_share_section $1; then
		echo SHARE
	else
		echo UNKNOWN
	fi
}

# $1-index, $2-new name (renaming to "blank" means deleting share)
rename_section_name() {
	local p
	p="$(get_section_path $1)" || return 1
	if [ -z "$2" ]; then
		rm "$p"
	else
		sed -i -e '1 s|.*|['"$2"']|' "$p"
	fi
}

# $1-old name, $2-new name (renaming to "blank" means deleting share)
rename_share() {
	local p
	p="$(get_section_path_by_name "$1")" || return 1
	if [ -z "$2" ]; then
		rm "$p"
	else
		sed -i -e '1 s|.*|['"$2"']|' "$p"
	fi
}

# $1-index
get_section_shared_dir() {
	local p
	p="$(get_section_path "$1")" || return 1
	sed '/^path.*=/!d; s|.*=[ \t]*||' "$p"
}

# $1-name
get_shared_dir_by_name() {
	local p
	p="$(get_section_by_name "$1")" || return 1
	get_section_shared_dir $p
}

# $1-index, $2 path
update_section_shared_dir() {
	local p
	p="$(get_section_path "$1")" || return 1
	sed -i -e '/^path.*=/ s|=.*|= '"$2"'|' "$p"
}

# $1-share name, $2-path
update_shared_dir_by_name() {
	local p
	p="$(get_section_by_name "$1")" || return 1
	update_section_shared_dir $p "$2"
}


################## High-level library functions ###################

# output: index|type|name|path
get_all_shares() {
	local p pt pn pp
	for p in $(get_sections); do
		pt=$(get_section_type $p)
		pn="$(get_section_name $p)"
		pp="$(get_section_shared_dir $p)"
		echo "$p|$pt|$pn|$pp"
	done	
}

# $1-dir, output share name (possibly duplicates) if yes
is_directory_shared() {
	get_all_shares | awk -v dir="$1" -F"|" '$2 == "SHARE" && $4 == dir { print $3 }'
}

# $1-share name, $2-path, $3-user $4-private
create_or_update_share() {
	local share="$1" path="$2" user=$3 private=$4
	local idx=$(get_section_by_name "$1")

	if [ "$idx" ]; then
		update_section_shared_dir $idx "$path"
	else
		create_new_section new "$share" "$path" "$user" "$private"
	fi
}

# $1-share name
remove_share_by_name() {
	rename_share "$1" ""
}

# $1-shared dir - this will remove all instanced of shared path
remove_share_by_shared_dir() {
	local p OIFS="$IFS" dir="$1"
	test -z "$dir" && return
	for p in $(get_all_shares); do
		IFS="|"
		set -- $p # index|type|name|path
		IFS="$OIFS"
		case "$4" in
			"$dir") remove_section $1 ;;
		esac
	done
	IFS="$OIFS"
}

COMMIT() {
	join_smb_conf # /tmp/smbconf.xx/smb.conf ###DEBUG
	# if samba is running, must restart
	if service samba status | grep -q running; then
		service samba restart
	fi
}

### make sure we run as root - input $@
run_as_root() {
	if [ $(id -u) -ne 0 ]; then
		if [ $DISPLAY ]; then
			exec gtksu "$APPTITLE" "$0" "$@"
		else
			exec su -c "$0 $*"
		fi
	fi
}

######################### END LIBRARY ########################




############################## GUI ##################################

### configurations
APPTITLE="Fatdog64 Samba Share Manager"
APPVERSION=1.0
PREV_SELECTION=. # for ssm_add


################# SHARE-FOLDER GUI ################


### This is the main UI for share-folder functionality
# $1-dir
share_folder_ui() {
	local dir=$(pwd) shared sharename
	dir=${1:-$dir}

	shared="$(is_directory_shared "$dir")"
	if [ "$shared" ]; then
		# dir already shared, how details and asked if want to unshare
		Xdialog --title "$APPTITLE" --ok-label "Keep Share" --cancel-label "Remove Share" --yesno \
"\"$dir\"
is already shared as
\"$shared\"

Keep sharing it?
" 0 0
		if [ $? = 1 ]; then
			remove_share_by_shared_dir "$dir"
			COMMIT
		fi
	else
		# dir not shared, so ask if want to share. If  yes, ask for share name
		Xdialog --title "$APPTITLE" --ok-label "Share" --cancel-label "Cancel" --yesno \
"\"$dir\"
is currently not shared.

Do you want to share it?
" 0 0
		if [ $? = 0 ]; then
			if sharename=$(Xdialog --title "$APPTITLE" --stdout --inputbox "Please enter share name" 0 0); then
				if [ "$sharename" ]; then
					create_or_update_share "$sharename" "$dir" # $user $private
					Xdialog --title "$APPTITLE" --infobox \
"$dir
is now shared as
$sharename.

To make it work, the File Sharing service (aka Samba service)
must also be enabled/running.
" 0 0 10000
					COMMIT
				fi
			fi
		fi
	fi
}


################# SHARE-MANAGER GUI ################

### view handler
ssm_view() {
	local shares p OIFS="$IFS" sharing
	shares=$(
	for p in $(get_all_shares); do
		IFS="|"
		set -- $p # index|type|name|path
		IFS="$OIFS"
		case $2 in
			SHARE) echo "\"$3\"" "\"$4\"" ;;
		esac
	done
	IFS="$OIFS"
	)

	if service samba status | grep -q running; then
		sharing='"File Sharing Service is RUNNING"'
	else
		sharing='"File Sharing Service is STOPPED"'
	fi

	eval Xdialog --title '"$APPTITLE"' --ok-label '"Back to main menu"' --no-cancel \
	--backtitle $sharing --menubox '"Existing shares"' 15 40 0 $shares
}

### add handler
ssm_add() {
	local dir share
	if dir="$(Xdialog --title "$APPTITLE" --stdout --backtitle "Choose folder to share" --dselect "$PREV_SELECTION" 0 0)";
	then
		dir=${dir%/}; test -z "$dir" && dir="/"
		PREV_SELECTION="$dir"
		if share="$(Xdialog --title "$APPTITLE" --stdout --inputbox "Please enter share name" 0 0)"; then
			create_or_update_share "$share" "$dir" # $user $private
			COMMIT
			Xdialog --title "$APPTITLE" --infobox \
"\"$dir\"
is now shared as
\"$share\"
" 0 0 10000
		fi
	fi
}

### del handler
ssm_del() {
	local shares p OIFS="$IFS" idx share
	shares=$(
	for p in $(get_all_shares); do
		IFS="|"
		set -- $p # index|type|name|path
		IFS="$OIFS"
		case $2 in
			SHARE) echo "\"$1\"" "\"$3 ($4)\"" ;;
		esac
	done
	IFS="$OIFS"
	)
	
	if idx=$(eval Xdialog --title '"$APPTITLE"' --ok-label '"Remove"' \
	--no-tags --stdout  --menubox '"Existing shares"' 15 40 0 $shares); then
		share="$(get_section_name $idx) ($(get_section_shared_dir $idx))"
		if Xdialog --title "$APPTITLE" --yesno \
"Are you sure you want to delete
$share?
" 0 0;
		then
			remove_section $idx
			Xdialog --title "$APPTITLE" --infobox \
"$share
has been deleted.
" 0 0 10000

		fi
	fi
}

### start service
ssm_start() {
	if service samba status | grep -q running; then
		Xdialog --title "$APPTITLE" --infobox "File Sharing Services is already running." 0 0 10000
	else
		service samba start
		Xdialog --title "$APPTITLE" --infobox "File Sharing Services is started." 0 0 10000
	fi
}

### stop service
ssm_stop() {
	if ! service samba status | grep -q running; then
		Xdialog --title "$APPTITLE" --infobox "File Sharing Services is already stopped." 0 0 10000
	else
		service samba stop
		Xdialog --title "$APPTITLE" --infobox "File Sharing Services is stopped." 0 0 10000
	fi
}

### This is the main UI for share-manager functionality
share_manager_ui() {
	local action
	while true; do
	action=$(Xdialog --title "$APPTITLE" --stdout --no-cancel --ok-label "Run action" --no-tags \
		--menubox "Choose action:" 15 40 0 \
		view "View Shares" add "Add New Share" del "Remove Existings Share" \
		start "Start File Sharing Service" stop "Stop File Sharing Service" exit "Exit")
		case "$action" in
			view)  ssm_view  ;;
			add)   ssm_add   ;;
			del)   ssm_del   ;;
			start) ssm_start ;;
			stop)  ssm_stop  ;;
			exit) return ;;
		esac
	done
}



################# CLI interface to the library ################

usage() {
	cat << EOF
$APPTITLE $APPVERSION
Usage:
 - ${0##*/} folder: run folder sharing GUI
 - ${0##*/} manager: run sharing manager GUI
 - ${0##*/} list: list all existing shares
 - ${0##*/} add path sharename: add a new share
 - ${0##*/} del sharename: delete an existing sharename
 - ${0##*/} deldir path: remove path from sharing
 - ${0##*/} start|stop : start/stop file sharing service
EOF
}

cli_list() {
	get_all_shares | awk -F"|" '
BEGIN {print "name|path"}
$2=="SHARE" { print $3 "|" $4; }'

	if service samba status | grep -q running; then
		echo "File Sharing Service: RUNNING."
	else
		service samba start_loopback
		echo "File Sharing Service: STOPPED"
	fi
}

# $1-dir $2-name
cli_add() {
	create_or_update_share "$2" "$1" # $user $private
	echo "$1 now shared as $2"
	COMMIT
}

# $1-name
cli_del() {
	remove_share_by_name "$1" &&
	echo "$1 share is now deleted" ||
	echo "$1 not found"
	COMMIT
}

# $1-dir
cli_deldir() {
	remove_share_by_shared_dir "$1" &&
	echo "$1 no longer shared."
	COMMIT
}

cli_start_svc() {
	if service samba status | grep -q running; then
		echo "File Sharing Service already running."
	else
		service samba start
		echo "File Sharing Service is started"
	fi
}

cli_stop_svc() {
	if ! service samba status | grep -q running; then
		echo "File Sharing Service already stopped."
	else
		service samba stop
		echo "File Sharing Service is stopped"
	fi
}



################# MAIN ################

run_as_root "$@"
init_smb_lib # "" "/tmp/smbconf.xx" yes ###DEBUG

case "${0##*/}" in
	fatdog-share-folder*)  [ "$DISPLAY" ] && share_folder_ui "$1" ;;
	fatdog-share-manager*) [ "$DISPLAY" ] && share_manager_ui ;;
	*) case "$1" in
		folder)  [ "$DISPLAY" ] && share_folder_ui "$2" ;;
		manager) [ "$DISPLAY" ] && share_manager_ui ;;

		# CLI interfaces
		list)   cli_list ;;
		add)    cli_add "$2" "$3" ;; # add shares $1-dir $2-name
		del)    cli_del "$2"    ;;   # del shares by $1-name 
		deldir) cli_deldir "$2" ;;   # del share by $1-dir
		start)  cli_start_svc   ;;
		stop)   cli_stop_svc    ;;
		-help|--help|-h|"") usage; exit ;;
	   esac ;;
esac
exit
