diff options
| author | Craig Jennings <c@cjennings.net> | 2026-08-03 09:39:08 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-08-03 09:39:08 -0500 |
| commit | eb0c2191b77e795cca7884e0690ea51c515527fd (patch) | |
| tree | b6686bca1c7627d24e5fb6fd0b2df525db56d435 /scripts/calendar-sync-run | |
| parent | 0eb65b6dcc2db54663b39dc7201c3dcf151add74 (diff) | |
| download | dotemacs-eb0c2191b77e795cca7884e0690ea51c515527fd.tar.gz dotemacs-eb0c2191b77e795cca7884e0690ea51c515527fd.zip | |
fix(calendar-sync): run the sync from a timer instead of the agenda hook
The hourly timer armed itself from org-agenda-mode-hook, so a session where
I never opened the agenda never synced at all. After a reboot that is every
session until the first agenda call. I found it with all three calendar
files still frozen at the pre-reboot write, five hours stale.
The deferral had a real reason. Feed URLs behind :secret-host live in
authinfo.gpg, and starting at load would prompt for a passphrase on a cold
gpg-agent. The cost was worse than what it bought.
A user timer owns the schedule now, every ten minutes.
calendar-sync-auto-start defaults to nil so the editor no longer arms its
own. scripts/calendar-sync-run is the batch entry point, and it blocks
until every calendar leaves the syncing state. The pipeline is asynchronous
end to end, so a batch Emacs that returns early exits zero having written
nothing.
Installing it on a machine that resolves its feeds through :secret-host
surfaced two bugs the inline-url machine could never show:
- cj/auth-source-secret-value called auth-source-search behind nothing but
a declare-function, which quiets the compiler and loads nothing. An
interactive Emacs always has auth-source in by the time anyone calls, so
the omission stayed invisible until a batch -Q sync died on a void
function. The require is guarded on fboundp. An unconditional one reloads
auth-source over whatever is already there, which replaced a caller's
stubbed search mid-call and sent an existing test out to the real
authinfo.
- A synchronous failure in one calendar aborted the whole loop. The async
callbacks record their own failures, but they never run when the error
lands before a process starts. Resolving a :secret-host feed signals
outright on a cold agent. Failures are contained per calendar now, so one
bad feed no longer costs the other two.
A failed row prints the reason it recorded. Batch Emacs discards the
*Messages* buffer the interactive path logs to, so without it the journal
shows only "error", with no way to tell a cold agent from a revoked token.
Diffstat (limited to 'scripts/calendar-sync-run')
| -rwxr-xr-x | scripts/calendar-sync-run | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/scripts/calendar-sync-run b/scripts/calendar-sync-run new file mode 100755 index 00000000..12181b8d --- /dev/null +++ b/scripts/calendar-sync-run @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Sync every configured calendar from its .ics feed, once, and wait for it. +# +# Runs a batch Emacs rather than talking to the daemon, for the same reason +# agenda-render-cache does: the org files this writes feed the agenda, waybar +# and the projected wallpaper, and none of those should go stale because the +# editor happens to be down. Nothing here touches a running Emacs, so it is +# safe to fire from a timer alongside an active session. +# +# Why a script at all: calendar-sync used to start its hourly timer from +# `org-agenda-mode-hook', so a session where the agenda was never opened never +# synced at all. That is not a rare corner -- it is every reboot until the +# first agenda call. The timer owns the schedule now, and the editor's own +# auto-start is off. +# +# The exit code is the point. `calendar-sync-batch-run-and-report' waits for +# every calendar to leave the syncing state and returns non-zero if any did +# not land, so a failed fetch shows up in `systemctl --user status' instead of +# being swallowed. +# +# Usage: calendar-sync-run +# Env: EMACS_D -- config directory (default ~/.emacs.d) +# EMACS -- emacs binary (default emacs) +# CALENDAR_SYNC_CONFIG -- private config file holding the calendar +# list, instead of the configured default. +# CALENDAR_SYNC_STATE -- sync-state file, instead of the configured +# default. Lets a test run without writing +# the real session's persisted state. +# CALENDAR_SYNC_TIMEOUT -- seconds to wait for all calendars to settle. + +set -euo pipefail + +EMACS_D="${EMACS_D:-$HOME/.emacs.d}" +EMACS="${EMACS:-emacs}" + +if [ ! -d "$EMACS_D/modules" ]; then + echo "calendar-sync-run: no modules directory at $EMACS_D/modules" >&2 + exit 1 +fi + +# The overrides are set before the module loads on purpose: the private config +# is read at load time, and `defvar'/`defcustom' both leave an already-bound +# value alone. This is the same seam the in-editor conversion worker uses. +pre="" +if [ -n "${CALENDAR_SYNC_CONFIG:-}" ]; then + pre="$pre (setq calendar-sync-private-config-file \"$CALENDAR_SYNC_CONFIG\")" +fi +if [ -n "${CALENDAR_SYNC_STATE:-}" ]; then + pre="$pre (setq calendar-sync--state-file \"$CALENDAR_SYNC_STATE\")" +fi +if [ -n "${CALENDAR_SYNC_TIMEOUT:-}" ]; then + pre="$pre (setq calendar-sync-batch-timeout $CALENDAR_SYNC_TIMEOUT)" +fi + +# -Q keeps the daemon's init out of it: this needs the calendar-sync modules, +# not a full editor. load-prefer-newer stops a stale .elc from answering for +# changed source, which would silently sync with yesterday's logic. +exec "$EMACS" --batch -Q \ + --eval "(progn (setq load-prefer-newer t)$pre)" \ + -L "$EMACS_D/modules" \ + --eval '(progn + (require (quote calendar-sync)) + (kill-emacs (calendar-sync-batch-run-and-report)))' |
