diff --git a/nag b/nag index ba7ba3f..b0f7a68 100755 --- a/nag +++ b/nag @@ -704,9 +704,9 @@ _is_snoozed() { # _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. +# Remove stale snooze entries: expired (past expiry timestamp) and +# orphaned (alarm ID no longer exists in _ALARMS). Expects _ALARMS +# to be populated via _read_alarms before calling. _sweep_expired_snoozes() { [[ -f "${_SNOOZED_FILE}" ]] || return 0 @@ -718,11 +718,25 @@ _sweep_expired_snoozes() { while IFS= read -r _entry || [[ -n "${_entry}" ]] do [[ -n "${_entry}" ]] || continue + + local _entry_id="${_entry%%$'\t'*}" + + # Drop expired entries. if [[ "${_entry}" == *$'\t'* ]] then local _expiry="${_entry#*$'\t'}" (( _expiry <= _now )) && continue 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}") done < "${_SNOOZED_FILE}" @@ -808,6 +822,16 @@ _remove_snoozed_entry() { return 0 } +# Usage: +# _cleanup_alarm_metadata +# +# 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: # _tag_list # @@ -1664,6 +1688,7 @@ stop() { _ALARMS=("${_new_alarms[@]}") _write_alarms fi + _cleanup_alarm_metadata "${_target}" _release_lock printf "Stopped alarm %s.\\n" "${_target}" @@ -1712,7 +1737,6 @@ _stop_by_tag() { _ALARMS=("${_new_alarms[@]}") _write_alarms fi - _release_lock local _m for _m in "${_matched[@]}" @@ -1720,8 +1744,10 @@ _stop_by_tag() { local _id _message _id="${_m%%$'\t'*}" _message="$(_get_alarm_field "${_m}" 5)" + _cleanup_alarm_metadata "${_id}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}" done + _release_lock } _stop_all() { @@ -1747,6 +1773,7 @@ _stop_all() { local _id _message _id="${_line%%$'\t'*}" _message="$(_get_alarm_field "${_line}" 5)" + _cleanup_alarm_metadata "${_id}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}" done @@ -1811,6 +1838,7 @@ skip() { _human_time="${REPLY}" printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}" else + _cleanup_alarm_metadata "${_id}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}" fi else @@ -1889,6 +1917,7 @@ _skip_by_tag() { _human_time="${REPLY}" printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}" else + _cleanup_alarm_metadata "${_id}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}" fi else @@ -1945,6 +1974,7 @@ _skip_all() { _human_time="${REPLY}" printf "Skipped [%s] %s — next: %s\\n" "${_id}" "${_message}" "${_human_time}" else + _cleanup_alarm_metadata "${_id}" printf "Stopped [%s] %s\\n" "${_id}" "${_message}" fi done diff --git a/test/check.bats b/test/check.bats index 74fb6b1..1d1cbab 100644 --- a/test/check.bats +++ b/test/check.bats @@ -197,3 +197,19 @@ SCRIPT [ "${status}" -eq 0 ] 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" +} \ No newline at end of file diff --git a/test/skip.bats b/test/skip.bats index 774ddb8..a83288f 100644 --- a/test/skip.bats +++ b/test/skip.bats @@ -87,3 +87,15 @@ load test_helper run "${_NAG}" -f skip all [ "${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 +} diff --git a/test/stop.bats b/test/stop.bats index c9b50b2..90d211a 100644 --- a/test/stop.bats +++ b/test/stop.bats @@ -85,3 +85,36 @@ load test_helper run "${_NAG}" -f stop all [ "${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" ] +}