From 793a1cd38a3b69ebdfb568d2850be09c1188cb64 Mon Sep 17 00:00:00 2001 From: lew Date: Thu, 2 Apr 2026 19:39:04 +0100 Subject: [PATCH] feat: check skips snoozed alarms and sweeps expired snooze entries --- nag | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ test/check.bats | 73 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/nag b/nag index 49b34f0..45ddba0 100755 --- a/nag +++ b/nag @@ -654,6 +654,79 @@ _is_muted() { return 1 } +# Usage: +# _is_snoozed +# +# Description: +# Check whether an alarm ID is currently snoozed. If the entry has an +# expiry timestamp, it is only considered snoozed when the expiry is +# still in the future. +# +# Exit / Error Status: +# 0 (success, true) If the alarm is snoozed. +# 1 (error, false) If the alarm is not snoozed. +_is_snoozed() { + local _id="${1}" + [[ -f "${_SNOOZED_FILE}" ]] || return 1 + + local _now _entry + _now="$(date +%s)" + + while IFS= read -r _entry || [[ -n "${_entry}" ]] + do + [[ -n "${_entry}" ]] || continue + local _entry_key="${_entry%%$'\t'*}" + + [[ "${_entry_key}" == "${_id}" ]] || continue + + # Check expiry if present. + if [[ "${_entry}" == *$'\t'* ]] + then + local _expiry="${_entry#*$'\t'}" + (( _expiry > _now )) && return 0 + else + return 0 + fi + done < "${_SNOOZED_FILE}" + + return 1 +} + +# Usage: +# _sweep_expired_snoozes +# +# Description: +# Remove expired snooze entries (those whose expiry timestamp is in the +# past). Indefinite entries (no expiry) are kept. If all entries are +# removed, the snoozed file is deleted. +_sweep_expired_snoozes() { + [[ -f "${_SNOOZED_FILE}" ]] || return 0 + + local _now + _now="$(date +%s)" + local -a _keep=() + local _entry + + while IFS= read -r _entry || [[ -n "${_entry}" ]] + do + [[ -n "${_entry}" ]] || continue + if [[ "${_entry}" == *$'\t'* ]] + then + local _expiry="${_entry#*$'\t'}" + (( _expiry <= _now )) && continue + fi + _keep+=("${_entry}") + done < "${_SNOOZED_FILE}" + + if (( ${#_keep[@]} == 0 )) + then + rm -f "${_SNOOZED_FILE}" + else + printf "%s\n" "${_keep[@]}" > "${_SNOOZED_FILE}.tmp" + mv -f "${_SNOOZED_FILE}.tmp" "${_SNOOZED_FILE}" + fi +} + # Usage: # _remove_snoozed_entry # @@ -1746,6 +1819,7 @@ check() { _acquire_lock _read_alarms + _sweep_expired_snoozes local -a _new_alarms=() local _now @@ -1763,6 +1837,12 @@ check() { _rule="$(_get_alarm_field "${_line}" 4)" _message="$(_get_alarm_field "${_line}" 5)" + if _is_snoozed "${_id}" + then + _new_alarms+=("${_line}") + continue + fi + if (( _timestamp <= _now )) then local _age=$(( _now - _timestamp )) diff --git a/test/check.bats b/test/check.bats index e6ff6e3..74fb6b1 100644 --- a/test/check.bats +++ b/test/check.bats @@ -124,3 +124,76 @@ SCRIPT _new_ts="$(cut -f3 "${NAG_DIR}/alarms" | head -1)" (( _new_ts > $(date +%s) )) } + +@test "check skips snoozed alarm and does not fire it" { + local _past_ts=$(( $(date +%s) - 60 )) + write_alarm "$(printf "1\t\t%s\t\tsnoozed alarm" "${_past_ts}")" + printf "1\\n" > "${NAG_DIR}/snoozed" + + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + [ ! -f "${_fired}" ] + [ -s "${NAG_DIR}/alarms" ] + grep -q "snoozed alarm" "${NAG_DIR}/alarms" +} + +@test "check skips alarm snoozed with expiry" { + local _past_ts=$(( $(date +%s) - 60 )) + local _future_expiry=$(( $(date +%s) + 3600 )) + write_alarm "$(printf "1\t\t%s\t\tsnoozed alarm" "${_past_ts}")" + printf "1\t%s\\n" "${_future_expiry}" > "${NAG_DIR}/snoozed" + + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + [ ! -f "${_fired}" ] + [ -s "${NAG_DIR}/alarms" ] +} + +@test "check sweeps expired snooze entries" { + local _future_ts=$(( $(date +%s) + 3600 )) + write_alarm "$(printf "1\t\t%s\t\talarm" "${_future_ts}")" + local _past_expiry=$(( $(date +%s) - 60 )) + printf "1\t%s\\n" "${_past_expiry}" > "${NAG_DIR}/snoozed" + + run_nag check + [ "${status}" -eq 0 ] + + if [ -f "${NAG_DIR}/snoozed" ]; then + [ ! -s "${NAG_DIR}/snoozed" ] || ! grep -q "^1" "${NAG_DIR}/snoozed" + fi +} + +@test "check fires alarm after snooze expires" { + local _past_ts=$(( $(date +%s) - 60 )) + local _past_expiry=$(( $(date +%s) - 120 )) + write_alarm "$(printf "1\t\t%s\t\texpired snooze" "${_past_ts}")" + printf "1\t%s\\n" "${_past_expiry}" > "${NAG_DIR}/snoozed" + + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + grep -q "expired snooze" "${_fired}" +}