#!/bin/sh
# useful functions
# 2013-09-26 11:00 created by niujiuru
# 2026-01-05 13:40 updated by niujiuru
# 2026-03-10 09:20 updated by niujiuru

# export envionment variables
export_env()
{
  PATH="/bin:/sbin:/usr/bin:/usr/sbin"
  CWD=$(cd "$(dirname "$0")" && pwd) || exit 1
  export PATH CWD
}

# load module by name
# pass THIS_MODULE to module
# $1: module name (no path, no '..')
load_module()
{
  local module="$1"

  [ -n "$module" ] || return 1

  case "$module" in
    */*|*..*) return 1 ;;
  esac

  THIS_MODULE="${module}"
  [ -f "${CWD}/modules/${module}" ] || return 1

  . "${CWD}/modules/${module}"
}

# load module & call module's method
# $1: module name
# $2: method name (function defined in module)
# $...: method params
call_module()
{
  [ $# -ge 2 ] || return 1

  local module="$1"
  local method="$2"

  shift 2
  load_module "$module" || return 1
  type "$method" >/dev/null 2>&1 || return 1
  "$method" "$@"
 
  return $?
}

# check if the program is currently running
# $1: program's executable path
# return 0 if running, 1 otherwise
is_running()
{
  local target="$1" pid exe

  [ -n "$target" ] || return 1

  case "$target" in
    /*) ;; # 必须是绝对路径
    *) return 1 ;;
  esac

  for pid in /proc/[0-9]*; do
    exe=$(readlink "$pid/exe" 2>/dev/null) || continue
    exe="${exe% (deleted)}"
    [ "$exe" = "$target" ] && return 0
  done

  return 1
}

# kill program(s) by path
# $1: program's executable path
# $2: signal to send (default: 15)
kill_by_path()
{
  local target="$1" pid exe
  local sig=${2:-15}

  [ -n "$target" ] || return 1

  case "$sig" in
    ''|*[!0-9]*) return 1 ;;
  esac

  [ "$sig" -ge 1 ] && [ "$sig" -le 64 ] 2>/dev/null || return 1

  for pid in /proc/[0-9]*; do
    exe=$(readlink "$pid/exe" 2>/dev/null) || continue
    exe="${exe% (deleted)}"
    if [ "$exe" = "$target" ]; then
      kill -$sig "${pid##*/}" 2>/dev/null
    fi
  done
}

# gracefully terminate program by executable path
# send SIGTERM and wait, then SIGKILL if timeout
# $1: program's executable path
# $2: timeout in seconds (default: 5)
# return 0 if terminated gracefully
# return 1 function failed
# return 2 if force killed after timeout
graceful_kill() {
  local target="$1" timeout="${2:-5}"

  [ -n "$target" ] || return 1

  kill_by_path "$target" 15 || return 1

  if ! wait_for_exit "$target" "$timeout"; then
    kill_by_path "$target" 9
    return 2
  fi

  return 0
}

# wait until a program exits
# $1: program's executable path
# $2: timeout in seconds (default: 5)
# return 0 if the program exited within timeout
# return 1 if still running after timeout
wait_for_exit()
{
  local target="$1" timeout="${2:-5}" waited=0

  while is_running "$target" && [ "$waited" -lt "$timeout" ]; do
    sleep 1
    waited=$((waited + 1))
  done

  is_running "$target" && return 1
  return 0
}
