#!/bin/bash

## Generates the applications.conf simple rules ##
## See scripts/cron.daily/99ulatencyd           ##

# BUGS: doess not handle whitespaces in paths to .desktop files

DEPENDECIES_SATISFIED=1

which file >/dev/null || { echo "The 'file' command not found." >&2 && DEPENDECIES_SATISFIED=0; }
which debtags >/dev/null || { echo "The 'debtags' command not found." >&2 && DEPENDECIES_SATISFIED=0; }
if [ $DEPENDECIES_SATISFIED = 0 ]; then
	echo "$0: Dependecies not satisfied, aborting." >&2
	exit 1
fi

timeout=60	# flag timeout

flag_params="instant=1 inherit=1 timeout=$timeout"

output=${1:-/etc/ulatencyd/simple.d/applications.conf}

# XDG_DATA_DIRS to search for desktop files
_XDG_DATA_DIRS="/usr/local/share:/usr/share:/usr/share/gnome"

## additions to _XDG_DATA_DIRS

# add desktop files generated from Debian menu files
ADD_DEBIAN_MENUS="yes"
# add desktop files with xsessions
ADD_XSESSIONS="yes"
# add user desktop files
# SECURITY WARNING: generated rules may contain user's private executables!
ADD_USERS="yes"

[ "$ADD_DEBIAN_MENUS" = "yes" ] && _XDG_DATA_DIRS="$_XDG_DATA_DIRS:/var/lib/menu-xdg"
[ "$ADD_USERS" = "yes" ] && _XDG_DATA_DIRS="$_XDG_DATA_DIRS:$(egrep '^([^:]*:){2}[0-9]{4,}' /etc/passwd | cut -d: -f 6 | while read line; do echo -n "$line/.local/share:"; done)"
DESKTOP_DIRS="$(echo "$_XDG_DATA_DIRS" | sed 's/:/\/applications:/g;s/:$//;//d')"
[ "$ADD_XSESSIONS" = "yes" ] && DESKTOP_DIRS="$DESKTOP_DIRS:$(echo "$_XDG_DATA_DIRS" | sed 's/:/\/xsessions:/g;s/:$//;//d')"


EXEC_PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
FILE_CMD="file --brief -e apptype -e ascii -e encoding -e cdf -e compress -e tar -e elf --mime-type"
# include only executables of following types (extended regex without surrounding braces)
BINARY_TYPES_RE="application/x-executable|application/x-sharedlib"
SCRIPT_TYPES_RE="application/octet-stream"

flag_name="application.starting"

# trim usage: var=" sdf fd  "; trim var
trim() {
    read -rd '' $1 <<<"${!1}"
}

# get interpreters, desktop files that execute an interpreter will be ignored
INTERPRETERS=$(tempfile)
debtags search '(interface::shell || devel::interpreter) && !x11::application' | cut -d' ' -f1 | 
xargs dpkg-query --listfiles 2>/dev/null | sort -u | 
while read file; do 
	trim file
	[ -z "$file" ] && continue
	[ -x "$file" ] || continue; 
	real="$(readlink -e "$file")"; 
	[ "$?" != 0 ] && continue; 
	[ -z "$real" ] && continue;
	[ -x "$real" ] || continue; 
	if $FILE_CMD "$real" | egrep -xq "($BINARY_TYPES_RE|$SCRIPT_TYPES_RE)" 2>/dev/null; then
		echo "$real"
	fi
done | sort -u >$INTERPRETERS

COMMENTED=$(tempfile)
COMMENTED_APPLIED=$(tempfile)

sed -n "/#[^[:blank:]].*$flag_name\b/p" "$output" | sed "s/ $flag_name\b.*$/ $flag_name /" | sort -u >$COMMENTED

exec >"$output"

echo "# desktop applications boosted when starting"
echo "# Automatically generated by $0 on $(date --rfc-3339=seconds)"
echo "# "
echo "# To exclude some application comment the line with #. Such commented lines are preserved."
echo "# To include custom applications put them in own simple rules file(s)."
echo

# set DEBUG="yes" to log debug messages into DEBUG_LOG file
DEBUG="no"
DEBUG="yes"
DEBUG_LOG=/tmp/log

[ "$DEBUG" = "yes" ] && echo -e "GENERATING STARTED ($(date --rfc-2822))\n" >$DEBUG_LOG

SIMPLE_BINS=$(tempfile)
SIMPLE_SCRIPTS=$(tempfile)
PARAMETRIZED_BINS=$(tempfile)
PARAMETRIZED_SCRIPTS=$(tempfile)

echo -n "$DESKTOP_DIRS" | xargs -d: -n1 -I '{}' find "{}" -type f -name '*.desktop' 2>/dev/null |
	xargs grep -ilF 'Type=Application' |
	xargs grep -iLF 'Terminal=true' |
	xargs grep -iLF 'NoDisplay=true' |
	xargs grep -iLF 'OnlyShowIn=X-' |
	xargs |
	while read files; do
		grep -h '^Exec=' $files | cut -b6-
		grep -h '^StartupWMClass=' $files | cut -b16-
	done |
		#cut -f1 -d' ' |
		sort -u |
		while read exec; do
			[ -z "$exec" ] && continue
			trim exec
			exec_prog="${exec%% *}"
			exec_params=
			if [ "$exec_prog" != "$exec" ]; then
				# exec with parameters
				exec_params="${exec#*[[:blank:]]}"
			fi
			real_exec="$(PATH=$EXEC_PATH which "$exec_prog" 2>/dev/null)"
			if [ -z "$real_exec" ]; then
				[ "$DEBUG" = "yes" ] && echo "IGNORED (exec file not found): exec: '$exec'" >>$DEBUG_LOG
				continue
			fi
			chased_exec="$(readlink -e "$real_exec" 2>/dev/null)"
			if [ -z "$chased_exec" ]; then
				[ "$DEBUG" = "yes" ] && echo "IGNORED (exec is broken symbolic link?): real exec: '$real_exec' exec: '$exec'" >>$DEBUG_LOG
				continue
			fi

			if grep -Fxq "$chased_exec" $INTERPRETERS; then
				# interpreter
				#cmd_match="cmd:$hashbang_value\ $real_exec"
				[ "$DEBUG" = "yes" ] && echo "IGNORED (called with interpreter): '$chased_exec' exec: '$exec'" >>$DEBUG_LOG
				continue
			else
				exec_type="$($FILE_CMD "$chased_exec" 2>/dev/null)"
				if [ -z "$exec_type" ]; then
					[ "$DEBUG" = "yes" ] && echo "IGNORED (cannot get filetype): '$chased_exec' exec: '$exec'" >>$DEBUG_LOG
					continue
				fi
				if echo "$exec_type" | egrep -xq "($BINARY_TYPES_RE)"; then
					match="$(printf "%-30s $flag_name" "\"$chased_exec\"")"
					# disable if commented in old output version
					if grep -qFx "#$match " $COMMENTED; then
						echo "#$match " >>$COMMENTED_APPLIED
						match="#$match"
					fi
					echo "$match $flag_params"
					if [ -n "$exec_params" ]; then
						echo "$chased_exec $exec" >>$PARAMETRIZED_BINS
					else
						echo "$chased_exec" >>$SIMPLE_BINS
					fi
				elif echo "$exec_type" | egrep -xq "($SCRIPT_TYPES_RE)"; then
					hashbang_line="$(head -n1 "$chased_exec")"
					hashbang_value="${hashbang_line#\#\!}"
					trim hashbang_value
					if [ -z "$hashbang_value" ]; then
						[ "$DEBUG" = "yes" ] && echo "IGNORED (script: hashbang problem): '$chased_exec' exec: '$exec'" >>$DEBUG_LOG
						continue
					fi
					hashbang_value="${hashbang_value// /\\ }" # escape spaces

					## version without arguments
					match="$(printf "%-30s $flag_name" "cmd:$hashbang_value\ $real_exec")"
					# disable if commented in old output version
					if grep -qFx "#$match " $COMMENTED; then
						echo "#$match " >>$COMMENTED_APPLIED
						match="#$match"
					fi
					echo "$match $flag_params"

					## version with arbitrary argument count
					match="$(printf "%-30s $flag_name" "cmd:$hashbang_value\ $real_exec\ *")"
					# disable if commented in old output version
					if grep -qFx "#$match " $COMMENTED; then
						echo "#$match " >>$COMMENTED_APPLIED
						match="#$match"
					fi
					echo "$match $flag_params"

					if [ -n "$exec_params" ]; then
						echo "$chased_exec $exec" >>$PARAMETRIZED_SCRIPTS
					else
						echo "$chased_exec" >>$SIMPLE_SCRIPTS
					fi
				else
					echo "IGNORED (unsupported filetype): '$chased_exec' exec: '$exec', filetype: '$exec_type'" >>$DEBUG_LOG 
					continue
				fi
			fi
		done |
sort -u

COMMENTED_NOT_APPLIED=$(tempfile)
(sort -u $COMMENTED_APPLIED; cat $COMMENTED) | sort | uniq -u > $COMMENTED_NOT_APPLIED
if [ -s $COMMENTED_NOT_APPLIED ]; then
	echo
	echo "# commented by user in the past:"
	cat $COMMENTED_NOT_APPLIED
fi

rm -f $COMMENTED $COMMENTED_APPLIED $COMMENTED_NOT_APPLIED $INTERPRETERS

while read -r chased_exec exec; do
	grep -Fxq "$chased_exec" $SIMPLE_BINS && continue || true
	[ "$DEBUG" = "yes" ] && echo "WARNING (binary exec with params will be boosted with any params, double check this): '$chased_exec' exec: '$exec'" >>$DEBUG_LOG
done <$PARAMETRIZED_BINS
rm -f $SIMPLE_BINS $PARAMETRIZED_BINS

while read -r chased_exec exec; do
	grep -Fxq "$chased_exec" $SIMPLE_SCRIPTS && continue || true
	[ "$DEBUG" = "yes" ] && echo "WARNING (script exec with params will be boosted with any params, double check this): '$chased_exec' exec: '$exec'" >>$DEBUG_LOG
done <$PARAMETRIZED_SCRIPTS
rm -f $SIMPLE_SCRIPTS $PARAMETRIZED_SCRIPTS
