aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-13 17:21:31 +0200
committerDanilo M. <danix@danix.xyz>2026-09-13 17:21:31 +0200
commitb79f34725fca2eb43bf1377e43b9aba4ef29afeb (patch)
tree312436053ef54dae7e1a20c8904a5c886b518eb6
parent73539f9d3863013d8a6baebfd05da0b10b4190c7 (diff)
downloadquickshell-b79f34725fca2eb43bf1377e43b9aba4ef29afeb.tar.gz
quickshell-b79f34725fca2eb43bf1377e43b9aba4ef29afeb.zip
feat(mail-overview): track the notified revision
Stores the database UUID beside the revision, because notmuch revisions only compare within one database and a rebuild restarts the counter. Missing, corrupt or mismatched state reports nothing, which the caller treats as seed-silently: with no floor, lastmod:0.. matches every unread message and startup becomes a wall of popups.
-rwxr-xr-xmail-overview/mail-notify.sh37
-rwxr-xr-xmail-overview/test-mail-notify.sh25
2 files changed, 62 insertions, 0 deletions
diff --git a/mail-overview/mail-notify.sh b/mail-overview/mail-notify.sh
index ed0c3e3..3e28bab 100755
--- a/mail-overview/mail-notify.sh
+++ b/mail-overview/mail-notify.sh
@@ -110,6 +110,43 @@ build_body() {
printf '\n'
}
+# The last revision this script notified up to, or empty when there is none
+# to trust. Empty means "seed silently": record where we are now and notify
+# nothing.
+#
+# The stored UUID is checked because notmuch revisions are only comparable
+# within one database. A rebuilt database restarts the counter, so an old
+# revision would be meaningless, and treating it as a floor would either
+# notify nothing forever or notify everything at once.
+read_prev_rev() {
+ local want_uuid="$1" got_uuid rev
+
+ [[ -f "$STATE" ]] || return 0
+ read -r got_uuid rev < "$STATE" 2>/dev/null || return 0
+
+ [[ "$got_uuid" == "$want_uuid" ]] || return 0
+ [[ "$rev" =~ ^[0-9]+$ ]] || return 0
+
+ printf '%s' "$rev"
+}
+
+# Written by atomic replace, the same idiom mail-watcher uses for its
+# heartbeat: a reader must never see a half-written file, and mv within a
+# directory is atomic where a redirect into the final path is not.
+#
+# Failure to write is deliberately not fatal. The notifications have already
+# been sent; taking the watcher down over a failure to record that would turn
+# a bookkeeping problem into a no-mail-notifications problem.
+write_state() {
+ local uuid="$1" rev="$2" tmp
+
+ mkdir -p "$(dirname "$STATE")" 2>/dev/null || return 0
+ tmp="$(mktemp "${STATE}.XXXXXX")" || return 0
+ printf '%s %s\n' "$uuid" "$rev" > "$tmp" || { rm -f "$tmp"; return 0; }
+ mv -f "$tmp" "$STATE" 2>/dev/null || rm -f "$tmp"
+ return 0
+}
+
main() {
echo "not implemented"
}
diff --git a/mail-overview/test-mail-notify.sh b/mail-overview/test-mail-notify.sh
index fccf4b3..95e971d 100755
--- a/mail-overview/test-mail-notify.sh
+++ b/mail-overview/test-mail-notify.sh
@@ -111,5 +111,30 @@ check "malformed json yields an empty body rather than an error" \
"" \
"$(build_body 'not json at all' 1 2>/dev/null)"
+# A scratch state file, removed at exit. Never the real one: that is the
+# user's live notification position, and a test must not move it.
+tmpstate="$(mktemp)"
+trap 'rm -f "$tmpstate"' EXIT
+STATE="$tmpstate"
+
+rm -f "$tmpstate"
+check "a missing state file reports no previous revision" \
+ "" "$(read_prev_rev "uuid-a")"
+
+write_state "uuid-a" 500
+check "a revision written is a revision read back" \
+ "500" "$(read_prev_rev "uuid-a")"
+
+check "a different database UUID discards the revision" \
+ "" "$(read_prev_rev "uuid-b")"
+
+printf 'garbage not a state file\n' > "$tmpstate"
+check "an unparseable state file reports no previous revision" \
+ "" "$(read_prev_rev "uuid-a")"
+
+write_state "uuid-a" 600
+check "a rewrite replaces rather than appends" \
+ "1" "$(wc -l < "$tmpstate")"
+
printf '\n%d passed, %d failed\n' "$pass" "$fail"
[[ "$fail" -eq 0 ]]