#!/usr/bin/bash
#
# toolbox-export - export application starters or binaries to host
#
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2023 Joachim Katzer
#
# Toolbox-export is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Toolbox-export is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toolbox-export.  If not, see <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------
# The official toolbox-export website is located at:
# https://github.com/joka63/toolbox-export
#
# The sources are partially derived from the distrobox project under
# the terms of the GPL-3.0-only license:
# https://github.com/89luca89/distrobox
#
# Author:
# Joachim Katzer <joka63@gmx.de>
# ---------------------------------------------------------------------

# POSIX
# Expected env variables:
#	HOME
#	USER
#	TOOLBOX_PATH
#	TOOLBOX_HOST_HOME
#	XDG_DATA_HOME

# Defaults
dest_path="${HOME}/.local/bin"
action=""
export_action=""
exported_app=""
exported_app_label=""
exported_bin=""
exported_delete=0
extra_flags=""
exec_opt=""

# Use TOOLBOX_HOST_HOME if defined, else fallback to HOME
#	TOOLBOX_HOST_HOME is set in case container is created
#	with custom --home directory
host_home="${TOOLBOX_HOST_HOME:-"${HOME}"}"
start_shell=""
is_login=0
is_sudo=0
verbose=0
version="0.3.1"

CONTAINER_ID=${CONTAINER_ID-""}
XDG_DATA_HOME=${XDG_DATA_HOME-"$HOME/.local/share"}
TOOLBOX_EXPORT_DATA_DIR="$XDG_DATA_HOME/toolbox-export"
SQL_TMP_FILE="${TOOLBOX_EXPORT_DATA_DIR}/tmp-actions.sql"
TOOLBOX_EXPORT_DB_PATH="${TOOLBOX_EXPORT_DATA_DIR}/exported.db"

# We depend on some commands, let's be sure we have them
base_dependencies="basename grep sed find"
for dep in ${base_dependencies}; do
	if ! command -v "${dep}" > /dev/null; then
		printf >&2 "Missing dependency: %s\n" "${dep}"
		exit 127
	fi
done

# Print usage to stdout.
# Arguments:
#   None
# Outputs:
#   print usage with examples.
show_help() {
	cat << EOF
toolbox-export version: ${version}

Usage:
	toolbox-export [-c <CONTAINER_ID>] --app mpv [--extra-flags "flags"] [--delete] [--sudo]
	toolbox-export [-c <CONTAINER_ID>] --bin /path/to/bin --export-path ~/.local/bin [--extra-flags "flags"] [--delete] [--sudo]
	toolbox-export [-c <CONTAINER_ID>] {--purge|--list|--update}

Options:

	--app/-a:		name of the application to export
	--bin/-b:		absolute path of the binary to export
	--container/-c:		the toolbox's container name
	--delete/-d:		delete exported application or binary
	--export-label/-el:	label to add to exported application name.
				Defaults to (on \$container_name)
	--export-path/-ep:	path where to export the binary
	--extra-flags/-ef:	extra flags to add to the command
	--login/-l		run the exported item in a login shell
	--sudo/-S:		specify if the exported item should be run as sudo inside toolbox
	--update/-u:		update exported starters and binaries to link to current toolbox
	--help/-h:		show this message
	--verbose/-v:		show more verbosity
	--version/-V:		show version
	--purge:		remove all exported app starters and icons not belonging to a toolbox
	--list:			list all exported app starters and icons 
EOF
}

setup_toolbox_export() {
	mkdir -p "$TOOLBOX_EXPORT_DATA_DIR"
	sqlite3 -separator '\t' "$TOOLBOX_EXPORT_DB_PATH" << ________END
	CREATE TABLE IF NOT EXISTS about (
		version TEXT PRIMARY KEY
  	);
	CREATE TABLE IF NOT EXISTS binaries (
		path TEXT,
		bin TEXT NOT NULL,
		container TEXT NOT NULL,
		cksum INTEGER,
		UNIQUE(path,bin,container)
	);
	CREATE INDEX idx_binaries_path ON binaries(path);
	CREATE TABLE IF NOT EXISTS apps (
		path TEXT,
		app TEXT NOT NULL,
		container TEXT NOT NULL,
		cksum INTEGER,
		UNIQUE(path,app,container)
	);
	CREATE INDEX idx_apps_path ON apps(path);
	INSERT OR IGNORE INTO about VALUES ('$version');
________END
}

cleanup_toolbox_export() {
	rm -f "${TOOLBOX_EXPORT_DB_PATH}" "${SQL_TMP_FILE}"
	rmdir "$TOOLBOX_EXPORT_DATA_DIR"
}

list_toolboxes() {
	toolbox list -c | tail -n +2 | awk '{print $2}'
}

list_exported() {
if [ -n "$CONTAINER_ID" ] ; then
sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << END
.mode box
SELECT DISTINCT app,path FROM apps WHERE path LIKE '%.desktop' AND container = '$CONTAINER_ID';
SELECT DISTINCT bin,path FROM binaries WHERE container = '$CONTAINER_ID'
END
elif [ -n "$exported_app" ] ; then
sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << END
SELECT DISTINCT path FROM apps WHERE app = '$exported_app';
END
elif [ -n "$exported_bin" ] ; then
sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << END
SELECT DISTINCT path FROM binaries WHERE bin = '$exported_bin';
END
else
sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << END
.mode box
SELECT DISTINCT app,container,path FROM apps WHERE path LIKE '%.desktop';
SELECT DISTINCT bin,container,path FROM binaries 
END
fi
}

joinByChar() {
  local IFS="$1"
  shift
  echo "$*"
}

joinByString() {
  local separator="$1"
  shift
  local first="$1"
  shift
  printf "%s" "$first" "${@/#/$separator}"
}

sql_escape() {
  # ' -> '' (SQL-Escape)
  local s="$1"
  printf "%s" "${s//\'/\'\'}"
}

update_export_db() {
	rm -f "$SQL_TMP_FILE"
	app_path="${host_home}/.local/share/applications"
	find ${dest_path} -type f -exec grep -H 'exec toolbox run -c' {} \; |\
	       	sed -e 's/:\(\s*exec\)/\1/' |\
	       	grep -E -v '(grep)' |\
	       	awk '{printf "INSERT OR IGNORE INTO binaries VALUES ('"'"'%s'"'"', '"'"'%s'"'"', '"'"'%s'"'"', %s);\n",$1,$7,$6,0;}' >> "$SQL_TMP_FILE"
	find ${app_path} -type f -exec grep -H '^Exec=toolbox run -c' {} \; |\
		sed -e 's/:Exec=toolbox run -c//' |\
		grep -E -v '(grep)' |\
		awk '{X=match($1,"^.*/applications/(.*)[.]desktop",A); P=A[1]; sub($2"-","",P); printf "INSERT OR IGNORE INTO apps VALUES ('"'"'%s'"'"', '"'"'%s'"'"', '"'"'%s'"'"', %s);\n",$1,P,$2,0;}' >> "$SQL_TMP_FILE"
	if [ -z "$exec_opt" ] ; then
	  sqlite3 "$TOOLBOX_EXPORT_DB_PATH" < "$SQL_TMP_FILE"
	else
	  echo "Would update $TOOLBOX_EXPORT_DB_PATH with following commands"
	  cat "$SQL_TMP_FILE"
	  echo
	fi
}

list_files_to_be_removed() {
sep_current_toolboxes=$(joinByString "', '" $(list_toolboxes))
sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << END
SELECT DISTINCT path from binaries 
WHERE container NOT IN ('$sep_current_toolboxes') AND path NOT IN 
        ( SELECT DISTINCT path from binaries WHERE container IN ('$sep_current_toolboxes') ) ;
SELECT DISTINCT path from apps 
WHERE container NOT IN ('$sep_current_toolboxes') AND path NOT IN 
        ( SELECT DISTINCT path from apps WHERE container IN ('$sep_current_toolboxes') ) ;
END
}

purge_files_in_db() {
if [ "$exec_opt" = "echo" ] ; then
  SqlCmdA="SELECT DISTINCT container,app"
  SqlCmdB="SELECT DISTINCT container,bin"
else
  SqlCmdA="DELETE"
  SqlCmdB="DELETE"
fi
sep_current_toolboxes=$(joinByString "', '" $(list_toolboxes))
sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << END
.mode tabs
$SqlCmdB FROM binaries WHERE container NOT IN ('$sep_current_toolboxes') ;
$SqlCmdA FROM apps WHERE container NOT IN ('$sep_current_toolboxes') ;
$SqlCmdB FROM binaries WHERE path = '-exec' AND bin = 'grep' ;
END
echo "Cleaned up $TOOLBOX_EXPORT_DB_PATH"
}

purge_non_existing_files_in_db() {
  if [ "$exec_opt" = "echo" ] ; then
    SqlCmdA="SELECT DISTINCT path"
    SqlCmdB="SELECT DISTINCT path"
  else
    SqlCmdA="DELETE"
    SqlCmdB="DELETE"
  fi
  mapfile -t list_bin_files < <(sqlite3  "$TOOLBOX_EXPORT_DB_PATH" <<'__END'
  SELECT DISTINCT path from binaries ;
__END
  )
  for f in "${list_bin_files[@]}" ; do
  if [ ! -f "$f" ] ; then
    sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << ____END
.mode csv
      $SqlCmdB FROM binaries WHERE path = '$(sql_escape "$f")' ;
____END
  fi
  done
  mapfile -t list_app_files < <(sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << __END
  SELECT DISTINCT path from apps ;
__END
  )
  for f in "${list_app_files[@]}" ; do
  if [ ! -f "$f" ] ; then
    sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << ____END
.mode csv
      $SqlCmdA FROM apps WHERE path = '$(sql_escape "$f")' ;
____END
  fi
  done
}

purge() {
	echo "Removing starters and exported binaries not belonging to a current toolbox:"
	update_export_db
	Files=$(list_files_to_be_removed)
	for f in $Files ; do
	  $exec_opt rm -i "$f"
	done
	purge_files_in_db
}

update() {
  echo "Cleaning up starters and exported binaries in DB whose files don't exist anymore:"
  purge_non_existing_files_in_db
  echo
  echo "Updating starters and exported binaries to ${container_name}.."
  Containers=$(sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << __END
  SELECT DISTINCT container FROM apps WHERE container != '$(sql_escape "${container_name}")' 
  UNION
  SELECT DISTINCT container FROM binaries WHERE container != '$(sql_escape "${container_name}")' ;
__END
)
  for c in $Containers ; do
    Apps=$(sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << ____END
    SELECT DISTINCT app FROM apps WHERE container = '$(sql_escape "${c}")' ;
____END
  )
    for a in $Apps ; do
      echo "Updating starter $a from container $c"
      $exec_opt toolbox-export -c "$c" -a "$a" -d \
	&& $exec_opt toolbox-export -c "${container_name}" -a "$a" \
	|| { echo >&2 "Failed updating '$a'" && $exec_opt toolbox-export -c "$c" -a "$a" && echo >&2 "Restored starter from '$c'" ; }
    done

    Bins=$(sqlite3  "$TOOLBOX_EXPORT_DB_PATH" << ____END
    SELECT DISTINCT bin FROM binaries WHERE container = '$(sql_escape ${c})' ;
____END
  )
    for b in $Bins ; do
      echo "Updating binary $b from container $c"
      $exec_opt toolbox-export -c "$c" -b "$b" -d \
	&& $exec_opt toolbox-export -c "${container_name}" -b "$b" \
	|| { echo >&2 "Failed updating '$b'" && $exec_opt toolbox-export -c "$c" -b "$b" && echo >&2 "Restored starter from '$c'" ; }
    done
  done
  exit 0
}

# Parse arguments
TEMP=$(getopt -o hvnVc:a:b:lSdu \
	--long help,verbose,dry-run,version,container:,app:,bin:,login,sudo,delete,update,export-label:,export-path:,extra-flags:,list,purge,setup,cleanup \
        -n 'toolbox-export' -- "$@")
if [ $? != 0 ] ; then exit 1 ; fi
eval set -- "$TEMP"

while :; do
	case $1 in
		-h | --help)
			# Call a "show_help" function to display a synopsis, then exit.
			show_help
			exit 0
			;;
		-v | --verbose)
			shift
			verbose=1
			;;
		-n | --dry-run)
			# Currently undocumented. Applicable only to --update option
			shift
			exec_opt="echo"
			;;
		-V | --version)
			printf "toolbox-export: %s\n" "${version}"
			exit 0
			;;
		-c | --container)
			CONTAINER_ID="$2"
			shift 2
			;;
		-a | --app)
			if [ -n "$2" ]; then
				export_action="app"
				exported_app="$2"
				shift
				shift
			fi
			;;
		-b | --bin)
			if [ -n "$2" ]; then
				export_action="bin"
				exported_bin="$2"
				shift
				shift
			fi
			;;
		-l | --login)
			is_login=1
			shift
			;;
		-S | --sudo)
			is_sudo=1
			shift
			;;
		--export-label)
			if [ -n "$2" ]; then
				exported_app_label="$2"
				shift
				shift
			fi
			;;
		--export-path)
			if [ -n "$2" ]; then
				dest_path="$2"
				shift
				shift
			fi
			;;
		--extra-flags)
			if [ -n "$2" ]; then
				extra_flags="$2"
				shift
				shift
			fi
			;;
		-d | --delete)
			exported_delete=1
			shift
			;;
		--list)
			action="list"
			shift
			;;
		--purge)
			action="purge"
			shift
			;;
		-u | --update)
			action="update"
			shift
			;;
		--setup)
			action="setup"
			shift
			;;
		--cleanup)
			action="cleanup"
			shift
			;;
		*) # Default case: If no more options then break out of the loop.
			break ;;
	esac
done

set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
	set -o xtrace
fi

# Check if sqlite3 is installed
SQLITE3=$(which sqlite3)
if [[ -z "$SQLITE3" ]] ; then
	echo >&2 "WARNING: Without sqlite3, the options --list and --purge are not available"
fi
if [[ "${exported_bin##*/}" = "sqlite3" ]] ; then
	## If sqlite3 itself gets exported (or export removed), don't use it:
	SQLITE3=""
fi

set -o errexit
# Check we're running inside a container and not on the host.
if [ -f /run/.containerenv ] ; then
	container_name=$(grep "name=" /run/.containerenv | cut -d'=' -f2- | tr -d '"')
fi

# We're working with HOME, so we must run as USER, not as root.
if [ "$(id -u)" -eq 0 ]; then
	printf >&2 "You must not run %s as root!\n" " $(basename "$0")"
	exit 1
fi

# Determine used toolbox
declare -a ToolboxRunOptions
if  [ -n "$CONTAINER_ID" ]; then
	container_name="${CONTAINER_ID}"
	ToolboxRunOptions+=("-c" "${CONTAINER_ID}")
elif [ -f /run/.containerenv ] ; then
	# Inside a toolbox: use its container name
	container_name=$(grep "name=" /run/.containerenv | cut -d'=' -f2- | tr -d '"')
	ToolboxRunOptions+=("-c" "${container_name}")
else
	# Use the default toolbox and retrieve its container name
	container_name=$(
	${TOOLBOX_PATH:-"toolbox"} run bash << ________END
	grep "name=" /run/.containerenv | cut -d'=' -f2- | tr -d '"'
________END
	)
	if [ $? -ne 0 ] ; then
		printf >&2 "Error: Default toolbox not running.\n"
		exit 2
	fi
fi

# Handle special actions here
case "${action}" in
	setup)
		setup_toolbox_export
		exit 0
		;;
	list)
		list_exported
		exit 0
		;;
	purge)
		purge
		exit 0
		;;
	update)
		update
		exit 0
		;;
	cleanup)
		cleanup_toolbox_export
		exit 0
		;;
esac

# Ensure export.db exists
if [ -n "$SQLITE3" -a ! -f "$TOOLBOX_EXPORT_DB_PATH" ] ; then
	setup_toolbox_export
fi

# Ensure the foundamental variables are set and not empty, we will not proceed
# if they are not all set.
if [ -z "${exported_app}" ] &&
	[ -z "${exported_bin}" ]; then
	printf >&2 "Error: Invalid arguments.\n"
	printf >&2 "Error: missing export target. Run\n"
	printf >&2 "\ttoolbox-export --help\n"
	printf >&2 "for more information.\n"
	exit 2
fi
# Ensure we're not receiving more than one action at time.
if [ -n "${exported_app}" ] && [ -n "${exported_bin}" ]; then
	printf >&2 "Error: Invalid arguments, choose only one action below.\n"
	printf >&2 "Error: You can only export one thing at time.\n"
	exit 2
fi
# Ensure we have the export-path set when exporting a binary.
if [ -n "${exported_bin}" ] && [ -z "${dest_path}" ]; then
	printf >&2 "Error: Missing argument export-path.\n"
	exit 2
fi
# Ensure the argument of --app or --bin is not another option like --delete
case "${exported_app}" in
  -*) 
	printf >&2 "Error: Missing or invalid application name: $exported_app\n"
	exit 2
	;;
esac
case "${exported_bin}" in
  -*) 
	printf >&2 "Error: Missing or invalid binary name: $exported_bin\n"
	exit 2
	;;
esac

#
if [ "${is_login}" -ne 0 ] && [ "${is_sudo}" -ne 0 ]; then
	start_shell="sudo -i"
elif [ "${is_login}" -ne 0 ]; then
	start_shell="sudo -u ${USER} -i"
else
	start_shell=""
fi

# Prefix to add to an existing command to work through the container
container_command_prefix="${TOOLBOX_PATH:-"toolbox"} run -c ${container_name} ${start_shell} "
if [ -z "${exported_app_label}" ]; then
	exported_app_label=" (on ${container_name})"
fi

check_if_toolbox_exists() {
	Available_Toolboxes=$(list_toolboxes)
	#loop through the array
	for i in ${Available_Toolboxes}
	do
	    #check if the element matches the search value
	    if [ "$i" == "$container_name" ]
	    then
		return 0
	    fi
	done
	printf >&2 "No toolbox with name '${container_name}' found"
	return 1
}
check_if_toolbox_exists || exit 2

# Export binary to destination directory.
# the following function will use generate_script to create a shell script in
# dest_path that will execute the exported binary in the selected toolbox.
# This script will be executed in the container and is therefore written as here-script.
#
# Arguments:
#	none it will use the ones set up globally
# Outputs:
#	a generated_script in dest_path
#	or error code.
export_binary() {
    ${TOOLBOX_PATH:-"toolbox"} ${ToolboxRunOptions[@]} run bash << ________END
	# Log generated or removed files as SQL commands
	log_binaries() {
		path="\$4"
		bin="\$2"
		container="\$3"
		case "\$1" in 
		start )
			rm -f "$SQL_TMP_FILE"
			;;
		insert )
			cksum=\$(cksum "\$path" | awk '{print \$1}')
			cat >> "$SQL_TMP_FILE" << ________________________EOF
			INSERT INTO binaries VALUES ('\$path', '\$bin', '\$container', \$cksum);
________________________EOF
		;;
		delete )
			cat >> "$SQL_TMP_FILE" << ________________________EOF
			DELETE FROM binaries 
			WHERE path = '\$path' AND bin = '\$bin' AND container = '\$container' ;
________________________EOF
		;;
		esac
        }

	# Print generated script from template
	# Arguments:
	#	none it will use the ones set up globally
	# Outputs:
	#	print generated script.
	generate_script() {
		cat << EOF
#!/bin/bash
# toolbox_binary
# toolbox name: ${container_name}
if [ ! -f /run/.containerenv ] ; then
	if [ "\\\$(id -u)" -eq 0 ]; then
		printf >&2 "You must not run %s as root in a toolbox container!\n" " \$(basename "${exported_bin}")"
		exit 17
	fi
	exec ${TOOLBOX_PATH:-"toolbox"} run -c ${container_name} \
		${start_shell} ${exported_bin} ${extra_flags} "\\\$@"
else
	exec ${exported_bin} "\\\$@"
fi
EOF
		return \$?
	}

	# Here begins export_binary
	log_binaries start
	if [ "${verbose}" -ne 0 ]; then
		set -o xtrace
	fi
	# Ensure the binary we're exporting is installed
	if [ ! -f "${exported_bin}" ]; then
		printf >&2 "Error: cannot find %s.\n" "${exported_bin}"
		exit 11
	fi
	# generate dest_file path
	dest_file="${dest_path}/\$(basename "${exported_bin}")"

	# If we're deleting it, just do it and exit
	if [ "${exported_delete}" -ne 0 ] &&
		# ensure it's a toolbox exported binary
		grep -q "toolbox_binary" "\${dest_file}"; then

		if rm -f "\${dest_file}"; then
			log_binaries delete "${exported_bin}" "${container_name}" "\${dest_file}"
			printf "%s from %s removed successfully from %s.\nOK!\n" \
				"${exported_bin}" "${container_name}" "${dest_path}"
			exit 0
		fi
	fi

	# test if we have writing rights on the file
	if ! touch "\${dest_file}"; then
		printf >&2 "Error: cannot create destination file %s.\n" "\${dest_file}"
		exit 1
	fi

	# create the script from template and write to file
	if generate_script > "\${dest_file}"; then
		chmod +x "\${dest_file}"
		log_binaries insert "${exported_bin}" "${container_name}" "\${dest_file}"
		printf "%s from %s exported successfully to %s.\nOK!\n" \
			"${exported_bin}" "${container_name}" "${dest_path}"
		exit 0
	fi
	# Unknown error.
	exit 3
________END
	[[ $? -eq 0 ]] && [[ -n "$SQLITE3" ]] && sqlite3 "$TOOLBOX_EXPORT_DB_PATH" < "$SQL_TMP_FILE" || return $?
}

# Export graphical application to the host.
# the following function will scan the toolbox for desktop and icon files for
# the selected application. It will then put the needed icons in the host's icons
# directory and create a new .desktop file that will execute the selected application
# in the toolbox.
# This script will be executed in the container and is therefore written as here-script.
#
# Arguments:
#	none it will use the ones set up globally
# Outputs:
#	needed icons in $host_home/.local/share/icons
#	needed desktop files in $host_home/.local/share/applications
#	or error code.
export_application() {
    toolbox ${ToolboxRunOptions[@]} run bash << ________END
	# Log generated or removed files as SQL commands
	log_applications() {
		path="\$4"
		app="\$2"
		container="\$3"
		case "\$1" in 
		start )
			rm -f "$SQL_TMP_FILE"
			;;
		insert )
			cksum=\$(cksum "\$path" | awk '{print \$1}')
			cat >> "$SQL_TMP_FILE" << ________________________EOF
			INSERT OR IGNORE INTO apps VALUES ('\$path', '\$app', '\$container', \$cksum);
________________________EOF
		;;
		delete )
			cat >> "$SQL_TMP_FILE" << ________________________EOF
			DELETE FROM apps 
			WHERE path = '\$path' AND app = '\$app' AND container = '\$container' ;
________________________EOF
		;;
		esac
        }

	log_applications start
	if [ "${verbose}" -ne 0 ]; then
		set -o xtrace
	fi
	canon_dirs=""
	[ -d /usr/share/applications ] && canon_dirs="/usr/share/applications"
	[ -d /usr/local/share/applications ] && canon_dirs="\${canon_dirs} /usr/local/share/applications"
	[ -d /var/lib/flatpak/exports/share/applications ] &&
		canon_dirs="\${canon_dirs} /var/lib/flatpak/exports/share/applications"
	[ -d "\${HOME}/.local/share/applications" ] && canon_dirs="\${canon_dirs} \${HOME}/.local/share/applications"

	# In this phase we search for applications to export.
	# First find command will grep through all files in the canonical directories
	# and only list files that contain the $exported_app, excluding those that
	# already contains a toolbox-run command. So skipping already exported apps.
	# Second find will list all files that contain the name specified, so that
	# it is possible to export an app not only by its executable name but also
	# by its launcher name.
	desktop_files=\$(
		# shellcheck disable=SC2086
		find \${canon_dirs} \
			-type f \
			-exec grep -qle "Exec=.*${exported_app}.*" {} \; \
			-exec grep -Le "Exec=.*${TOOLBOX_PATH:-"toolbox"} run.*" {} \;
		# shellcheck disable=SC2086
		find \${canon_dirs} \
			-name "${exported_app}*"
	)

	# Ensure the app we're exporting is installed
	# Check that we found some desktop files first.
	if [ -z "\${desktop_files}" ]; then
		printf >&2 "Error: cannot find any desktop files.\n"
		printf >&2 "Error: trying to export a non-installed application.\n"
		exit 11
	fi

	# Find icons by usinc the Icon= specification. If it's only a name, we'll
	# search for the file, if it's already a path, just grab it.
	icon_files=""
	for desktop_file in \${desktop_files}; do
		icon_name="\$(grep Icon= "\${desktop_file}" | cut -d'=' -f2-)"

		# In case it's an hard path, conserve it and continue
		if [ -e "\${icon_name}" ]; then
			icon_files="\${icon_files} \${icon_name}"
			continue
		fi

		# If it's not an hard path, find all files in the canonical paths.
		icon_files="\${icon_files} \$(find \
			/usr/share/icons \
			/usr/share/pixmaps \
			/var/lib/flatpak/exports/share/icons -iname "*\${icon_name}*" 2> /dev/null || :)"
	done

	# create applications dir if not existing
	mkdir -p "${host_home}/.local/share/applications"

	# copy icons in home directory
	icon_file_absolute_path=""
	for icon_file in \${icon_files}; do

		# replace canonical paths with equivalent paths in HOME
		icon_home_directory="\$(dirname "\${icon_file}" |
			sed "s|/usr/share/|${host_home}/.local/share/|g" |
			sed "s|/var/lib/flatpak/exports/share|${host_home}/.local/share/|g" |
			sed "s|pixmaps|icons|g")"

		# check if we're exporting an icon which is not in a canonical path
		if [ "\${icon_home_directory}" = "\$(dirname "\${icon_file}")" ]; then
			icon_home_directory="${host_home}/.local/share/icons/"
			icon_file_absolute_path="\${icon_home_directory}\$(basename "\${icon_file}")"
		fi

		# check if we're exporting or deleting
		if [ "${exported_delete}" -ne 0 ]; then
			# we need to remove, not export
			dest_file="\${icon_home_directory:?}/\$(basename "\${icon_file:?}")"
			rm -rf "\${dest_file}"
			log_applications delete "${exported_app}" "${container_name}" "\${dest_file}"
			continue
		fi

		# we wanto to export the application's icons
		mkdir -p "\${icon_home_directory}"
		cp -r "\$(realpath "\${icon_file}")" "\${icon_home_directory}"
		dest_file="\${icon_home_directory:?}/\$(basename "\${icon_file:?}")"
		log_applications insert "${exported_app}" "${container_name}" "\${dest_file}"
	done

	# create desktop files for the toolbox
	for desktop_file in \${desktop_files}; do
		desktop_original_file="\$(basename "\${desktop_file}")"
		desktop_home_file="${container_name}-\$(basename "\${desktop_file}")"

		# check if we're exporting or deleting
		if [ "${exported_delete}" -ne 0 ]; then
			rm -f "${host_home}/.local/share/applications/\${desktop_original_file}"
			dest_file="${host_home}/.local/share/applications/\${desktop_home_file}"
			rm -f "\${dest_file}"
			log_applications delete "${exported_app}" "${container_name}" "\${dest_file}"
			# we're done, go to next
			continue
		fi

		# Add command_prefix
		# Add extra flags
		# Add closing quote
		# If a TryExec is present, we have to fake it as it will not work
		# through the container separation
		dest_file="${host_home}/.local/share/applications/\${desktop_home_file}"
		sed "s|^Exec=|Exec=${container_command_prefix} |g" "\${desktop_file}" |
			sed "s|\(%.*\)|${extra_flags} \1|g" |
			sed "/^TryExec=.*/d" |
			sed "/^DBusActivatable=true/d" |
			sed "s|Name.*|& ${exported_app_label}|g" \
				> "\${dest_file}"
		# in the end we add the final quote we've opened in the "container_command_prefix"

		if ! grep -q "StartupWMClass" "${host_home}/.local/share/applications/\${desktop_home_file}"; then
			printf "StartupWMClass=%s\n" "${exported_app}" >> "\${dest_file}"
		fi
		# In case of an icon in a non canonical path, we need to replace the path
		# in the desktop file.
		if [ -n "\${icon_file_absolute_path}" ]; then
			sed -i "s|Icon=.*|Icon=\${icon_file_absolute_path}|g" \
				"\${dest_file}"
			# we're done, go to next
			continue
		fi

		# In case of an icon in a canonical path, but specified as an absolute
		# we need to replace the path in the desktop file.
		sed -i "s|Icon=/usr/share/|Icon=${host_home}/.local/share/|g" "\${dest_file}"
		sed -i "s|pixmaps|icons|g" "\${dest_file}"
		log_applications insert "${exported_app}" "${container_name}" "\${dest_file}"
	done

	if [ "${exported_delete}" -ne 0 ]; then
		printf "Application %s successfully un-exported.\nOK!\n" "${exported_app}"
		printf "%s will disappear from your applications list in a few seconds.\n" "${exported_app}"
	else
		printf "Application %s successfully exported.\nOK!\n" "${exported_app}"
		printf "%s will appear in your applications list in a few seconds.\n" "${exported_app}"
	fi
________END
	[[ $? -eq 0 ]] && [[ -n "$SQLITE3" ]] && sqlite3 "$TOOLBOX_EXPORT_DB_PATH" < "$SQL_TMP_FILE" || return $?
}

# Main routine 
case "${export_action}" in
	app)
		export_application
		;;
	bin)
		export_binary
		;;
	setup)
		setup_toolbox_export
		;;
	cleanup)
		cleanup_toolbox_export
		;;
	*)
		printf >&2 "Invalid arguments, choose an action below.\n"
		show_help
		exit 2
		;;
esac
