fix: cleans up expired and orphaned metadata on alarm removal

This commit is contained in:
Lewis Wynne 2026-04-02 20:36:42 +01:00
parent 6a588aaa9c
commit 5d48fb9c9c
4 changed files with 95 additions and 4 deletions

38
nag
View file

@ -704,9 +704,9 @@ _is_snoozed() {
# _sweep_expired_snoozes # _sweep_expired_snoozes
# #
# Description: # Description:
# Remove expired snooze entries (those whose expiry timestamp is in the # Remove stale snooze entries: expired (past expiry timestamp) and
# past). Indefinite entries (no expiry) are kept. If all entries are # orphaned (alarm ID no longer exists in _ALARMS). Expects _ALARMS
# removed, the snoozed file is deleted. # to be populated via _read_alarms before calling.
_sweep_expired_snoozes() { _sweep_expired_snoozes() {
[[ -f "${_SNOOZED_FILE}" ]] || return 0 [[ -f "${_SNOOZED_FILE}" ]] || return 0
@ -718,11 +718,25 @@ _sweep_expired_snoozes() {
while IFS= read -r _entry || [[ -n "${_entry}" ]] while IFS= read -r _entry || [[ -n "${_entry}" ]]
do do
[[ -n "${_entry}" ]] || continue [[ -n "${_entry}" ]] || continue
local _entry_id="${_entry%%$'\t'*}"
# Drop expired entries.
if [[ "${_entry}" == *$'\t'* ]] if [[ "${_entry}" == *$'\t'* ]]
then then
local _expiry="${_entry#*$'\t'}" local _expiry="${_entry#*$'\t'}"
(( _expiry <= _now )) && continue (( _expiry <= _now )) && continue
fi fi
# Drop orphaned entries (alarm no longer exists).
local _exists=0 _line
for _line in "${_ALARMS[@]:-}"
do
[[ -n "${_line}" ]] || continue
[[ "${_line%%$'\t'*}" == "${_entry_id}" ]] && _exists=1 && break
done
(( _exists )) || continue
_keep+=("${_entry}") _keep+=("${_entry}")
done < "${_SNOOZED_FILE}" done < "${_SNOOZED_FILE}"
@ -808,6 +822,16 @@ _remove_snoozed_entry() {
return 0 return 0
} }
# Usage:
# _cleanup_alarm_metadata <id>
#
# Description:
# Remove all metadata for an alarm ID (snoozed file entries).
# Called when an alarm is permanently removed.
_cleanup_alarm_metadata() {
_remove_snoozed_entry "${1}" 2>/dev/null || true
}
# Usage: # Usage:
# _tag_list <tag> # _tag_list <tag>
# #
@ -1664,6 +1688,7 @@ stop() {
_ALARMS=("${_new_alarms[@]}") _ALARMS=("${_new_alarms[@]}")
_write_alarms _write_alarms
fi fi
_cleanup_alarm_metadata "${_target}"
_release_lock _release_lock
printf "Stopped alarm %s.\\n" "${_target}" printf "Stopped alarm %s.\\n" "${_target}"
@ -1712,7 +1737,6 @@ _stop_by_tag() {
_ALARMS=("${_new_alarms[@]}") _ALARMS=("${_new_alarms[@]}")
_write_alarms _write_alarms
fi fi
_release_lock
local _m local _m
for _m in "${_matched[@]}" for _m in "${_matched[@]}"
@ -1720,8 +1744,10 @@ _stop_by_tag() {
local _id _message local _id _message
_id="${_m%%$'\t'*}" _id="${_m%%$'\t'*}"
_message="$(_get_alarm_field "${_m}" 5)" _message="$(_get_alarm_field "${_m}" 5)"
_cleanup_alarm_metadata "${_id}"
printf "Stopped [%s] %s\\n" "${_id}" "${_message}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}"
done done
_release_lock
} }
_stop_all() { _stop_all() {
@ -1747,6 +1773,7 @@ _stop_all() {
local _id _message local _id _message
_id="${_line%%$'\t'*}" _id="${_line%%$'\t'*}"
_message="$(_get_alarm_field "${_line}" 5)" _message="$(_get_alarm_field "${_line}" 5)"
_cleanup_alarm_metadata "${_id}"
printf "Stopped [%s] %s\\n" "${_id}" "${_message}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}"
done done
@ -1811,6 +1838,7 @@ skip() {
_human_time="${REPLY}" _human_time="${REPLY}"
printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}" printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}"
else else
_cleanup_alarm_metadata "${_id}"
printf "Stopped [%s] %s\\n" "${_id}" "${_message}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}"
fi fi
else else
@ -1889,6 +1917,7 @@ _skip_by_tag() {
_human_time="${REPLY}" _human_time="${REPLY}"
printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}" printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}"
else else
_cleanup_alarm_metadata "${_id}"
printf "Stopped [%s] %s\\n" "${_id}" "${_message}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}"
fi fi
else else
@ -1945,6 +1974,7 @@ _skip_all() {
_human_time="${REPLY}" _human_time="${REPLY}"
printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}" printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}"
else else
_cleanup_alarm_metadata "${_id}"
printf "Stopped [%s] %s\\n" "${_id}" "${_message}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}"
fi fi
done done

View file

@ -197,3 +197,19 @@ SCRIPT
[ "${status}" -eq 0 ] [ "${status}" -eq 0 ]
grep -q "expired snooze" "${_fired}" grep -q "expired snooze" "${_fired}"
} }
@test "check sweeps orphaned snooze entries for removed alarms" {
local _future_ts=$(( $(date +%s) + 3600 ))
write_alarm "$(printf "2\t\t%s\t\treal alarm" "${_future_ts}")"
# ID 1 does not exist in alarms, ID 2 does.
printf "1\\n" > "${NAG_DIR}/snoozed"
printf "2\\n" >> "${NAG_DIR}/snoozed"
run_nag check
[ "${status}" -eq 0 ]
# Orphan ID 1 should be swept, ID 2 should remain.
[ -f "${NAG_DIR}/snoozed" ]
! grep -q "^1$" "${NAG_DIR}/snoozed"
grep -q "^2$" "${NAG_DIR}/snoozed"
}

View file

@ -87,3 +87,15 @@ load test_helper
run "${_NAG}" -f skip all run "${_NAG}" -f skip all
[ "${status}" -eq 1 ] [ "${status}" -eq 1 ]
} }
@test "skip one-shot cleans up snoozed metadata" {
run_nag at "tomorrow 3pm" "one-shot"
run_nag snooze 1
[ -f "${NAG_DIR}/snoozed" ]
run_nag skip 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Stopped" ]]
if [ -f "${NAG_DIR}/snoozed" ]; then
! grep -q "^1" "${NAG_DIR}/snoozed"
fi
}

View file

@ -85,3 +85,36 @@ load test_helper
run "${_NAG}" -f stop all run "${_NAG}" -f stop all
[ "${status}" -eq 1 ] [ "${status}" -eq 1 ]
} }
@test "stop by ID cleans up snoozed metadata" {
run_nag at "tomorrow 3pm" "take a break"
run_nag snooze 1
[ -f "${NAG_DIR}/snoozed" ]
run_nag stop 1
[ "${status}" -eq 0 ]
if [ -f "${NAG_DIR}/snoozed" ]; then
! grep -q "^1" "${NAG_DIR}/snoozed"
fi
}
@test "stop by tag cleans up snoozed metadata" {
run_nag at "tomorrow 3pm" "work task"
run_nag tag 1 work
run_nag snooze 1
[ -f "${NAG_DIR}/snoozed" ]
run "${_NAG}" -f stop work
[ "${status}" -eq 0 ]
if [ -f "${NAG_DIR}/snoozed" ]; then
! grep -q "^1" "${NAG_DIR}/snoozed"
fi
}
@test "stop all cleans up snoozed metadata" {
run_nag at "tomorrow 3pm" "first"
run_nag at "tomorrow 4pm" "second"
run_nag snooze all
[ -f "${NAG_DIR}/snoozed" ]
run "${_NAG}" -f stop all
[ "${status}" -eq 0 ]
[ ! -f "${NAG_DIR}/snoozed" ] || [ ! -s "${NAG_DIR}/snoozed" ]
}