#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

#set -x
set -e
set -o pipefail
set -o errtrace
#trap 'sleep 1' DEBUG

error_handler() {
   local last_exit_code="$?"
   trap - ERR
   if [ ! "$1" = "" ]; then
      error_text="$1"
   else
      error_text="$BASH_COMMAND"
   fi

   local msg="<p>
<br></br>$0 script bug.
<br></br>
<br></br>No panic. Nothing is broken. Just some rare condition has been hit.
<br></br>Try again later. There is likely a solution for this problem.
<br></br>Please see News, Blog and User Help Forum.
<br></br>Please report this bug!
<br></br>
<br></br>msgdispatcher_identifier: '<code>$msgdispatcher_identifier</code>'
<br></br>msgdispatcher_appendix: '<code>$msgdispatcher_appendix</code>'
<br></br>
<br></br>error_text: '<code>$error_text</code>'
<br></br>last_exit_code: '<code>$last_exit_code</code>'
</p>"

   local stripped_msg
   stripped_msg="$(sanitize-string -- nolimit "$msg")"
   printf '%s\n' "$stripped_msg"

   if [ ! -d ~/".msgcollector" ]; then
      mkdir --parents -- ~/".msgcollector"
   fi
   echo "$0: BASH_COMMAND: $BASH_COMMAND | exit_code: $last_exit_code" | sponge -a -- ~/".msgcollector/msgdispatcher-error.log" >/dev/null

   ## Popup window with the message above.
   ## /usr/share/icons/icon-pack-dist/whonix.ico might not exist, but
   ## fortunately msgdispatcher_dispatch_x works anyway.
   /usr/libexec/msgcollector/msgdispatcher_dispatch_x "warning" "$0" "$msg" "0" "/usr/share/icons/icon-pack-dist/whonix.ico" &

   true
}

trap "error_handler" ERR

parse_cmd_options() {
   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   while true; do
       case $1 in
           --verbose)
               set -x
               verbose="1"
               shift
               ;;
           --)
               shift
               break
               ;;
           -*)
               echo "$0: unknown option: $1" >&2
               exit 1
               ;;
           *)
               break
               ;;
       esac
   done
}

preparation() {
   ## Sanity test.
   command -v flock >/dev/null 2>/dev/null

   true "XDG_SESSION_TYPE: $XDG_SESSION_TYPE"
   true "DISPLAY: $DISPLAY"
   true "WAYLAND_DISPLAY: $WAYLAND_DISPLAY"
   if [ "$XDG_SESSION_TYPE" = "x11" ]; then
      gui=1
      cli=0
   elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
      gui=1
      cli=0
   elif [ "$XDG_CURRENT_DESKTOP" = "X-QUBES" ]; then
      gui=1
      cli=0
   elif [ ! "$DISPLAY" = "" ]; then
      gui=1
      cli=0
   elif [ ! "$WAYLAND_DISPLAY" = "" ]; then
      gui=1
      cli=0
   elif [ "$XDG_SESSION_TYPE" = "tty" ]; then
      cli=1
      ## Do this only in /dev/tty1.
      if [ ! "$(tty)" = "/dev/tty1" ]; then
         echo "$0: INFO: Skip, because not running in /dev/tty1."
         exit 0
      fi
   else
      echo "$0: INFO: XDG_SESSION_TYPE is neither x11 nor wayland nor tty, DISPLAY and WAYLAND_DISPLAY are not set either, exiting."
      exit 0
   fi

   if [ "$cli" = "1" ]; then
      inotifywait_subshell_fifo="${msgcollector_run_dir}/msgdispatcher_cli_subshell_fifo"
   elif [ "$gui" = "1" ]; then
      inotifywait_subshell_fifo="${msgcollector_run_dir}/msgdispatcher_x_subshell_fifo"
   else
      echo "$$" | sponge -- "${msgcollector_run_dir}/msgdispatcher_piderror" >/dev/null
      exit 3
   fi

   inotifywait_folder="${msgcollector_run_dir}"
   test -d "$inotifywait_folder"
}

## {{ Small wrapper to use either 'kdialog', 'notify-send' or nothing.
passive_popup_tool() {
   local time title text
   time="$1"
   title="$2"
   text="$3"

   ## Fallback.
   ## 'notify-send' does not work if $title is unset.
   if [ "$title" = "" ]; then
      title="$msgdispatcher_identifier"
   fi

   ## 'notify-send' timeout in milliseconds.

   if command -v "qubesdb-read" >/dev/null 2>&1 ; then
      if command -v "notify-send" >/dev/null 2>&1 ; then
         notify-send --expire-time "${time}000" -- "$title" "$text"
         return 0
      fi
   fi

   ## check if 'kdialog', 'notify-send' or no passive popup tool is installed
   ## - that is not the case for CLI Custom-Workstation users
   ## - that may not be the case for Gnome users
   if command -v "kdialog" >/dev/null 2>&1 ; then
      ## 'kdialog' does not support end-of-options.
      kdialog --title "$title" --passivepopup "$text" "$time"
   elif command -v "notify-send" >/dev/null 2>&1 ; then
      notify-send --expire-time "${time}000" -- "$title" "$text"
   fi
}
## }}

dispatch_cli() {
   local msg
   msg="$1"

   if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_parenttty" ]; then
      local parenttty
      parenttty="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_parenttty")" || true
      if [ "$parenttty" = "/dev/tty1" ]; then
         ## When for example 'systemcheck' was run in 'tty1', then messages were
         ## already echoed by 'msgcollector'. No need to dispatch them again.
         true "Skipping, because parenttty is /dev/tty1."
         return 0
      fi
   fi

   echo "$msg"
}

dispatch_x_active() {
   local icon
   if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_icon" ]; then
      icon="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_icon")" || true
   else
      ## Fallback.
      if [ -f "/usr/share/icons/gnome/24x24/status/info.png" ]; then
         icon="/usr/share/icons/gnome/24x24/status/info.png"
      else
         icon=""
      fi
   fi

   ## Fallback.
   if [ "$type" = "" ]; then
      type="info"
   fi

   ## Nov 23 06:43:10 work-main msgdispatcher[362757]: /usr/libexec/msgcollector/msgdispatcher: line 203: /usr/libexec/msgcollector/msgdispatcher_dispatch_x: Argument list too long
   ## Avoid "Argument list too long" by truncating overly long messages.
   ## This is character-based, not exact bytes, but good enough and simple.
   local max_msg_chars=32768
   if ((${#msg} > max_msg_chars)); then
      msg="${msg:0:max_msg_chars}"
      msg="${msg} [truncated - try command line interface (CLI) version for complete output]"
   fi

   if [ "$verbose" = "1" ]; then
      /usr/libexec/msgcollector/msgdispatcher_dispatch_x "$type" "$title" "$msg" "0" "$icon"
   else
      ## Launching into background, so it doesn't block msgdispatcher until
      ## msgdispatcher_dispatch_x exits.
      /usr/libexec/msgcollector/msgdispatcher_dispatch_x "$type" "$title" "$msg" "0" "$icon" &
   fi
}

dispatch_x_passive() {
   passive_popup_tool "20" "$title" "$msg"
}

inotifywait_setup() {
   safe-rm --force -- "$inotifywait_subshell_fifo"
   mkfifo -- "$inotifywait_subshell_fifo"
   ## Start inotifywait in a sub process to continuously monitor the directory.
   inotifywait --quiet --monitor --event close_write --format "%w%f" -- "$inotifywait_folder" > "$inotifywait_subshell_fifo" &
   inotifywait_main_pid="$!"
}

parse_existing_files() {
   for file_name in "$inotifywait_folder/"*; do
      true "parse_existing_files: file_name: $file_name"
      msgdispatcher_handler || true
   done
}

msgdispatcher_handler() {
   true "msgdispatcher_handler: file_name: $file_name"
   file_extension="${file_name##*_}"
   if [ ! "$file_extension" = "done" ]; then
      true "msgdispatcher_handler: Not a done file. Stop processing, ok."
      true "----------"
      return 0
   fi
   true "msgdispatcher_handler: Done file. Continue processing..."

   ## Remove everything after the identifier.
   temp_item="${file_name%%_*}"
   ## Remove "${msgcollector_run_dir}/".
   msgdispatcher_identifier="${temp_item##*/}"

   ## Validate early (before using in file paths). Use conditional to avoid
   ## triggering error_handler in this long-running daemon.
   if ! check_is_alpha_numeric "msgdispatcher_identifier" 2>/dev/null; then
      true "msgdispatcher_handler: identifier is not alphanumeric, skipping."
      true "----------"
      return 0
   fi

   if [ "$gui" = "1" ]; then
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagex_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagex" ]; then
            true "INFO: messagex file exists."
            msg="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagex")" || true
            title="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_titlex")" || true
            type="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_typex")" || true
            dispatch_x_active "$type" "$msg"
            msgdispatcher_delete_wrapper "messagex_done"
            msgdispatcher_delete_wrapper "titlex"
            msgdispatcher_delete_wrapper "messagex"
            msgdispatcher_delete_wrapper "typex"
         else
            true "INFO: messagex file does NOT exist."
            ## Not using rm outside the if, to prevent race conditions.
            ## Not always using rm, without if to prevent forking.
            msgdispatcher_delete_wrapper "messagex_done"
         fi
         true "----------"
         return 0
      fi
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuex_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuex" ]; then
            msg="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuex")" || true
            title="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuextitle")" || true
            ## TODO: do not use "_typex" to avoid conflicts with "messagex_done".
            #type="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_typex")" || true
            type="info"
            if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_forceactive" ]; then
               ## TODO: --forceactive not yet implemented.
               #dispatch_x_active "$type" "$msg"
               dispatch_x_passive "$type" "$title" "$msg" || true
            else
               dispatch_x_passive "$type" "$title" "$msg" || true
            fi
            msgdispatcher_delete_wrapper "forceactive"
            msgdispatcher_delete_wrapper "passivepopupqueuex_done"
            msgdispatcher_delete_wrapper "passivepopupqueuex"
            msgdispatcher_delete_wrapper "passivepopupqueuextitle"
         else
            msgdispatcher_delete_wrapper "passivepopupqueuex_done"
         fi
         true "----------"
         return 0
      fi

#       last_two="${file_name#*_*_}"
#       if [ "$last_two" = "progressbarx_done" ]; then
#          first_two="${file_name%_*_*}"
#          ## first_two example:
#          ## /run/msgcollector/systemcheck/systemcheck_2b3916d6-3b3f-4490-bc85-b97da494a55d
#          progressbaridx=${first_two#*_}
#          ## progressbaridx example:
#          ## 2b3916d6-3b3f-4490-bc85-b97da494a55d
#          if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx" ]; then
#             if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx_animate" ]; then
#                msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx_animate"
#                animate="--animate"
#             else
#                unset animate
#             fi
#             if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbartitlex" ]; then
#                local progressbartitlex
#                progressbartitlex="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbartitlex")" || true
#             fi
#             if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx" ]; then
#                progressbarx="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx")" || true
#             fi
#
#             unset animate
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx_done"
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbartitlex"
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx"
#          else
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx_done"
#          fi
#          true "----------"
#          return 0
#       fi

   elif [ "$cli" = "1" ]; then
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_waitmessagecli_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_waitmessagecli" ]; then
            true "INFO: waitmessagecli file exists."
            msg="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_waitmessagecli")" || true
            type="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_typecli")" || true
            dispatch_cli "$msg"
            msgdispatcher_delete_wrapper "waitmessagecli_done"
            msgdispatcher_delete_wrapper "waitmessagecli"
            msgdispatcher_delete_wrapper "typecli"
         else
            true "INFO: waitmessagecli file does NOT exist."
            msgdispatcher_delete_wrapper "waitmessagecli_done"
         fi
         true "----------"
         return 0
      fi
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagecli_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagecli" ]; then
            msg="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagecli")" || true
            type="$(cat -- "${msgcollector_run_dir}/${msgdispatcher_identifier}_typecli")" || true
            dispatch_cli "$msg"
            msgdispatcher_delete_wrapper "messagecli_done"
            msgdispatcher_delete_wrapper "messagecli"
            msgdispatcher_delete_wrapper "typecli"
         else
            msgdispatcher_delete_wrapper "messagecli_done"
         fi
         true "----------"
         return 0
      fi
   else
      true "----------"
      exit 1
   fi
}

msgdispatcher_delete_wrapper() {
   local file_name full_path

   msgdispatcher_appendix="$1"

   check_is_alpha_numeric "msgdispatcher_identifier"
   check_is_alpha_numeric "msgdispatcher_appendix"

   file_name="${msgdispatcher_identifier}_${msgdispatcher_appendix}"
   validate_safe_filename "file_name"

   full_path="${msgcollector_run_dir}/${msgdispatcher_identifier}_${msgdispatcher_appendix}"
   check_is_not_empty_and_only_one_line "full_path"

   if test -f "$full_path" ; then
      safe-rm -f -- "$full_path"
   else
      true "INFO: file_name $full_path does not exist."
   fi

   ## In case above errors, do not additionally error out from this function.
   true
}

inotifywait_loop() {
   true "$0: Starting loop."

   ## Launching inotifywait_loop into the background.
   while read -r -- file_name; do
      true "file_name: $file_name"
      msgdispatcher_handler || true
   done < "$inotifywait_subshell_fifo" &
   inotifywait_subshell_pid="$!"
}

msgdispatcher_loop() {
   preparation
   fallbacks ## provided by /usr/libexec/msgcollector/msgfallbacks
   inotifywait_setup
   parse_existing_files

   ## Wait for system to be ready so 'systemd-notify' can be used.
   ## Wait until systemctl at least reports 'degraded'.
   ## But overwrite with '|| true' because msgdispatcher should still start.
   ## This might lead to an infinite wait because 'msgcollector-gui.service'
   ## is a systemd user unit.
   #systemctl --user --wait is-system-running &>/dev/null || true

   ## systemd-notify sometimes exists non-zero. Unknown how to reproduce.
   while ! "${systemd_notify[@]}" --pid="$$" --ready &>/dev/null; do
      light_sleep 1
   done
   inotifywait_loop

   while true; do
      "${systemd_notify[@]}" --pid="$$" WATCHDOG=1 &>/dev/null || true
      light_sleep 10
      ## Check that pids are still running.
      kill -0 -- "$inotifywait_main_pid"
      kill -0 -- "$inotifywait_subshell_pid"
   done
}

source /usr/libexec/helper-scripts/strings.bsh

source /usr/libexec/helper-scripts/light_sleep.bsh

## sets: systemd_notify
source /usr/libexec/helper-scripts/systemd-notify.bsh

## sets: ${msgcollector_run_dir}
# shellcheck source=./usr/libexec/msgcollector/msgcollector_shared
source /usr/libexec/msgcollector/msgcollector_shared
folder_init

# shellcheck source=./usr/libexec/msgcollector/msgfallbacks
source /usr/libexec/msgcollector/msgfallbacks

parse_cmd_options "$@"
msgdispatcher_loop
