#!/bin/sh
# Created by Alex Simeonov, 08.03.2024, Barix AG
# USB Sound cards detect service. Called by UDEV on plugging/unplugging
# any USB sound card. They get detected by the usb_cards_detect LUA script,
# and their audio control properties being exported to a JSON file in RAM.
# This service does:
# - Calls alsactl store/restore for the USB cards
# - Sets the UCI parameters when changed from the webUI
# - creates the UCI settings for new cards
# - if needed (based on the instreamer.general.use_usb_card UCI setting)
#   restarts the Multicoder Flexa app by communicating with its UDEV Command Proxy
#   via a predefined named pipe to avoid blocking the UDEV for long
#

UCI_PACKAGE="usb_cards"
USB_CARDS_DESC=$(/usr/bin/uci -q get usb_cards.general.json_file_path)
USE_USB_CARD=$(/usr/bin/uci -q get instreamer.general.use_usb_card)
ASOUND_CONF_FILE="/etc/asound.conf"
PLUG_EVENT=""
PROXY_PIPE=/var/run/udevpipe
declare -a CARD_NAMES
declare -a CARD_IDS

print_msg() {
	echo "alsa-usb-detect: $@"
	logger "alsa-usb-detect: $@"
}

start() {
	usb_check
	usb_restore
	uci_check
	# in Flexa FW we use another asound.conf, so
	# we do not need to set the default card
	# set_default_card
}

plug_event() {
  # called by udev when plugging/removing the card
  # we need to definitely stop the app if it is set to use the USB card by default:
  PLUG_EVENT="$1"
  echo "USB CARD PLUG EVENT: $PLUG_EVENT"
  # echo "PLUG_EVENT=$PLUG_EVENT" >> /var/run/udev.log
  # echo "USE_USB_CARD=$USE_USB_CARD"  >> /var/run/udev.log
  if [ "$USE_USB_CARD" == "true" ] && [ -p "$PROXY_PIPE" ]; then
    # write to pipe in order not to block the udev
    echo "/etc/init.d/run-flexa stop" > $PROXY_PIPE
    APP_STOPPED=1
  fi
  #do detect USB card properties (calls the LUA script)
  start
  # restart the app if we have stopped it
  if [ "$USE_USB_CARD" == "true" ] && [ "$APP_STOPPED" -gt 0 ] && [ -p "$PROXY_PIPE" ]; then
    #call the instreamer script, it will call run-flexa
    #/etc/init.d/run-flexa start
    echo "/etc/init.d/instreamer force_start" > $PROXY_PIPE
  fi
}

usb_check() {
	# call the LUA script to enumerate all USB cards
	# called on add/remove UDEV events
	/usr/bin/lua5.1 /usr/local/sbin/usb_cards_detect.lua
}

usb_restore() {
	# creates alsa restore files for the newly plugged usb cards (if not existing)
	oldIFS="$IFS"
	IFS=' '
	# read, remove trailing CR,LF and replace all non alphanumeric chars with "_"	
	readarray -t CARD_NAMES <<< $(cat "$USB_CARDS_DESC" | jq -r '.usb_cards[].name')
	readarray -t CARD_IDS <<< $(cat "$USB_CARDS_DESC" | jq -r '.usb_cards[].id')
	IFS="$oldIFS"

	#echo "Detected cards:"
	#echo "CARD_NAMES[${#CARD_NAMES[*]}]=${CARD_NAMES[0]}, ${CARD_NAMES[1]}, ${CARD_NAMES[2]}"
	#echo "CARD_IDS[${#CARD_IDS[*]}]=${CARD_IDS[0]}, ${CARD_IDS[1]}, ${CARD_IDS[2]}"

	for (( i=0; i<${#CARD_IDS[*]}; i++ )); do
		local CID=${CARD_IDS[$i]}
		local CNAME=${CARD_NAMES[$i]}
		local STATE_FILE=""
		echo "Processing card No: $CID, NAME: $CNAME"
		if [ "$CID" != "" ]; then
			STATE_FILE="/etc/$CNAME.state"
			echo "Restoring $CNAME (USB Card$CID) settings" 
			if [ -f "$STATE_FILE" ]; then
				# file exists, load it
				/usr/sbin/alsactl --file "$STATE_FILE" restore "$CID"
			else
				#create it
				/usr/sbin/alsactl --file "$STATE_FILE" store "$CID"
			fi
		fi
	done
}

usb_store(){
  # Stores the state of the plugged usb card(s). This function is to be called when the USB card settings are changed
  # from the webUI to avoid colling again the alsa-detect function (which should be done only on plug event anyway)
  oldIFS="$IFS"
  IFS=' '
  # read, remove trailing CR,LF and replace all non alphanumeric chars with "_"
  readarray -t CARD_NAMES <<< $(cat "$USB_CARDS_DESC" | jq -r '.usb_cards[].name')
  readarray -t CARD_IDS <<< $(cat "$USB_CARDS_DESC" | jq -r '.usb_cards[].id')
  IFS="$oldIFS"

  # call uci_check to update the ALSA state with values from UCI settings
  uci_check
}

uci_check() {
	# check for available USB sound cards, and create the corresponding
	# UCI sections in the usb_cards UCI file. Separate options 
	# will be created by the webUI
		
	for (( i=0; i<${#CARD_IDS[*]}; i++ )); do
		#check for UCI settings, and do apply them
		local CID=${CARD_IDS[$i]}
		local CNAME=${CARD_NAMES[$i]}
		local STATE_FILE="/etc/$CNAME.state"
		# clear the options array
		#unset UCI_OPTIONS
		if [ "$CID" != "" ]; then
			# check if section does exist
			UCI_SECTION=$(uci -q get "$UCI_PACKAGE.$CNAME")
			if [ -z "$UCI_SECTION" ]; then
				msg="Creating UCI section for card $CNAME"
				print_msg $msg
				/usr/bin/uci set "$UCI_PACKAGE"."$CNAME=section"
			else
		    msg="UCI config section card $CNAME already exists!"
		    print_msg $msg
			fi

      msg="Checking/populating the card $CNAME UCI options ..."
      print_msg $msg
      UCI_OPTIONS=$(uci -q show "$UCI_PACKAGE.$CNAME" | grep "$CNAME\." | cut -d '.' -f3- | cut -d '=' -f1)
      # if the section is not empty, check for options, and apply them with amixer command
      if ! [ -z "$UCI_OPTIONS" ]; then
        echo "$UCI_OPTIONS" | while read uci_line; do
          MIXER_VALUE=$(uci -q get "$UCI_PACKAGE.$CNAME.$uci_line")
          NUMID_VALUE=${uci_line#"numid"}
          MIXER_CMD="/usr/bin/amixer -q -c$CID cset numid=$NUMID_VALUE $MIXER_VALUE"
          eval $MIXER_CMD
        done
        # store the new settings
        /usr/sbin/alsactl --file "$STATE_FILE" store "$CID"
      fi
		fi
	done
}

set_alsa_option() {
	# changes a line with an option, or appends it at the end of file
	# example set_alsa_option option_value file_name_to_process
    name=${1//\//\\/}
    value=${2//\//\\/}
    sed -i \
        -e '/^#\?\(\s*'"${name}"'\s* \s*\).*/{s//\1'"${value}"'/;:a;n;ba;q}' \
        -e '$a'"${name}"' '"${value}" $3
}

set_default_card() {
	DEFAULT_INPUT_CARD=$(/usr/bin/uci -q get usb_cards.general.default_input)
	DEFAULT_OUTPUT_CARD=$(/usr/bin/uci -q get usb_cards.general.default_output)
	DEFAULT_INPUT_AVAILABLE="defaults.pcm.card"
	DEFAULT_OUTPUT_AVAILABLE="defaults.pcm.card"
	echo "DEFAULT_INPUT_CARD=$DEFAULT_INPUT_CARD"
	echo "DEFAULT_OUTPUT_CARD=$DEFAULT_OUTPUT_CARD"
	
	for (( i=0; i<${#CARD_IDS[*]}; i++ )); do
		local CID=${CARD_IDS[$i]}
		if [ "$CID" != "" ]; then
			if [ "$CID" == "$DEFAULT_INPUT_CARD" ]; then DEFAULT_INPUT_AVAILABLE="\'$CID\'"; fi
			if [ "$CID" == "$DEFAULT_OUTPUT_CARD" ]; then DEFAULT_OUTPUT_AVAILABLE="\'$CID\'"; fi
		fi
	done

	# modify the asound.conf file accordingly
	echo "defaults.pcm.dmix.card $DEFAULT_OUTPUT_AVAILABLE $ASOUND_CONF_FILE"
	set_alsa_option defaults.pcm.dmix.card  "$DEFAULT_OUTPUT_AVAILABLE" "$ASOUND_CONF_FILE"
	set_alsa_option defaults.pcm.dsnoop.card  "$DEFAULT_INPUT_AVAILABLE" "$ASOUND_CONF_FILE"
}

restart() {
	start
}

case "$1" in
  start)
	start
	;;
  stop)
	# no action
	;;
  plugged)
	plug_event add
	;;
  unplugged)
	plug_event remove
	;;
  restart|reload)
	restart
	;;
  usb_restore)
	usb_restore
	;;
  usb_store)
	usb_store
	;;
  *)
	echo "Usage: $0 {start|stop|restart|plugged|unplugged}"
	exit 1
esac

exit $?

