#!/bin/bash

# cups_hold_new_jobs [<printer>]

# Resume a cups printer, set all queued and newly incoming jobs on
# hold, pause printer and exit.

# Runs as long as there are jobs in the print queue. Start from cron.

DEBUG=false


# --------- do not edit below ---------
VERSION=0.3
VERSION_INFO="(c) 2024 by Adrian Zaugg under GNU General Public License Version 3."

# initiate job list
declare -a JOBS

# set defaults
LOCK="/run/$(basename "$0")"
LOCKED=false
OWNPIDPPID="$$	$PPID	"


# get job list
function get_job_list() {
	JOBS=($(lpstat -o "$PRINTER" | sed -e 's/^\('"$PRINTER"'-[0-9]\+\).*$/\1/g'))
}


# DEBUG variable must be set to true or false
if [[ "$DEBUG" != "true" && "$DEBUG" != "false" ]]; then
	DEBUG=false
fi

# cups must be running
if [ $(lpstat -r | grep -c "is running") -eq 0 ]; then
	$DEBUG && echo "[INFO] CUPS is not running. Nothing to do." >&2
	exit 0
fi

# get printer (or class?)
PRINTER="$1"

# get default printer if none was given as an argument or check given printer name
if [ -z "$PRINTER" ]; then
	PRINTER="$(lpstat -p -d 2>/dev/null | grep "^system default destination: " | sed -e 's/^system default destination: //')"
	# check
	if [ -z "$PRINTER" ]; then
		echo "[ERROR] No printer given on command line and unable to detect default printer." >&2
		exit 1
	else
		$DEBUG && echo "No printer given on command line, using default printer: $PRINTER" >&2
	fi
else
	# no space, hash, slash or newline in printer names allowed
	PRINTER="$(echo "$PRINTER" | head -1 | sed -e "s%/.*$%%" -e "s/#.*$//" -e "s/ .*$//")"
	if [ ${#PRINTER} -lt ${#1} ]; then
		echo "[ERROR] Disallowed characters in printer name found."
		exit 1
	fi

	# check given printer exists
	ERR="$(lpstat -p "$PRINTER" 2>&1)"
	if [ $? -ne 0 ]; then
		# printer does not exist or other error
		echo "[ERROR] No printer printer \"$PRINTER\" found." >&2
		exit 1
	else
		$DEBUG && echo "Using printer: $PRINTER" >&2
	fi
fi

# lock
while ! $LOCKED; do
        mkdir "$LOCK" 2>/dev/null
        if [ $? -eq 0 ]; then
                LOCKED=true
                break;
        elif [ $(ps -e -o pid,ppid,command | sed -n "s/\([0-9]\{1,\}\)[^0-9]\{1,\}\([0-9]\{1,\}\)[^0-9]\(.*\)$/\1\t\2\t\3/p" | \
                        grep -v "$OWNPIDPPID" | grep -cE 'bash .*/'"$(basename "${0}")"'$') -gt 1 ]; then
                # process already started
                $DEBUG && echo "Another instance of this script is already running." >&2
                exit 0
        else
                # stale lock, remove it
                rmdir "$LOCK" 2>/dev/null
                if [ $? -ne 0 ]; then
                        echo "[ERROR] Removing stale lock $LOCK failed." >&2
                        exit 1
		else
			$DEBUG && echo "Removed stale lock." >&2
                fi
        fi
done

# pause printer
if [ $(lpstat -p "$PRINTER" | grep -c -m 1 "$PRINTER disabled") -eq 0 ]; then
	cupsdisable "$PRINTER"
	$DEBUG && echo "Printer paused." >&2
fi
PRINTER_PAUSED=true

# loop as long as there are jobs in the queue
get_job_list
until [ ${#JOBS[@]} -eq 0 ]; do

	# find index of last held job in new job list
	last_held_job_index=0
	unset job_id
	while [[ "${JOBS[$last_held_job_index]}" != "$last_held_job_id" && $last_held_job_index -le ${#JOBS[@]} ]]; do
		last_held_job_index=$(($last_held_job_index+1))
	done
	if [ $last_held_job_index -eq ${#JOBS[@]} ]; then
		# job not found
		last_held_job_index=-1
	fi

	# set newly queued jobs on hold
	i=0
	for job_id in ${JOBS[@]}; do
		# compare index to marker of last held index
		if [ $i -gt $last_held_job_index ]; then
			# set job on hold
			ERR="$(lp -i "$job_id" -H hold 2>&1)"
			if [ $? -eq 0 ]; then
				echo -e "[INFO] holding job $job_id" >&2
			else
				echo "[ERROR] Failed to hold job \"$job_id\": $ERR" >&2
			fi
			# remember last index
			last_held_job_index=$i
		fi
		i=$(($i+1))
	done
	last_held_job_id="${JOBS[$last_held_job_index]}"

	# wait a bit
	sleep 1

	# reread jobs in print queue
	get_job_list

	# enable printer now
	if $PRINTER_PAUSED; then
		cupsenable "$PRINTER"
		PRINTER_PAUSED=false
	fi
done

# queue is now empty disable printer
if $PRINTER_PAUSED; then
	$DEBUG && echo "Queue is empty." >&2
else
	cupsdisable "$PRINTER"
	echo "[INFO] Queue is empty now, pausing printer $PRINTER." >&2
fi

# unlock
if $LOCKED; then rmdir "$LOCK"; fi

exit 0
