diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-13 14:02:05 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-13 14:02:05 +0200 |
| commit | 9fc6ca0d9a9cdba9d5808951ce290e559a9e8554 (patch) | |
| tree | fab4f7bb606b38efaf00e22937c3c5a0f5cc0f57 /mail-overview/MailPanel.qml | |
| parent | 87cd101eb3c55d0ad7e8f2142fcce90865da7d39 (diff) | |
| download | quickshell-9fc6ca0d9a9cdba9d5808951ce290e559a9e8554.tar.gz quickshell-9fc6ca0d9a9cdba9d5808951ce290e559a9e8554.zip | |
feat(mail-overview): show mail-watcher health in the drawer
The watcher already writes ~/.local/state/mail-watcher.heartbeat every
60s, so the drawer reads it and shows a dot: green when idling, yellow
when a folder gave up, red when the heartbeat is stale or missing.
Backoff stays green because it is normal recovery and the watcher's own
health check treats it that way. The staleness rule is reimplemented in
a few lines rather than shelling out to --status; the heartbeat file is
the documented cheap-to-poll surface.
Read once per open rather than watched: the heartbeat is written by
atomic replace, so an inotify watch held on the old inode dies with it.
"Sync now" is removed with its Process. The watcher triggers syncs, the
cron tick is the backstop, and waybar's right-click still runs
mailsync.sh.
Diffstat (limited to 'mail-overview/MailPanel.qml')
| -rw-r--r-- | mail-overview/MailPanel.qml | 78 |
1 files changed, 72 insertions, 6 deletions
diff --git a/mail-overview/MailPanel.qml b/mail-overview/MailPanel.qml index 7457fb2..0a7f36a 100644 --- a/mail-overview/MailPanel.qml +++ b/mail-overview/MailPanel.qml @@ -10,6 +10,7 @@ // GNU General Public License for more details. import Quickshell +import Quickshell.Io import Quickshell.Wayland import QtQuick @@ -20,6 +21,45 @@ Scope { property string monitor: "DP-1" property bool open: false + // mail-watcher's heartbeat, written every 60s. Read once per open: the + // drawer is a LazyLoader, so closing and reopening rebuilds the FileView + // and reads the current file. Watch is deliberately not used, because the + // heartbeat is written by atomic replace (tmpfile + rename) and an inotify + // watch held on the old inode dies with it. + property bool watcherAlive: false + property int watcherDead: 0 + property int watcherExpected: 0 + + // The same staleness rule as mail-watcher's heartbeat_is_healthy: dead + // threads or a heartbeat older than 300s mean the watcher needs a look. + // Backoff is healthy, so it never turns the dot. + function readHeartbeat(payload) { + root.watcherAlive = false; + root.watcherDead = 0; + root.watcherExpected = 0; + + let data = null; + try { data = JSON.parse(payload); } catch (e) { return; } + if (!data || typeof data.ts !== "string") return; + + const ts = Date.parse(data.ts); + if (isNaN(ts) || (Date.now() - ts) / 1000 > 300) return; + + root.watcherAlive = true; + root.watcherDead = Number(data.dead) || 0; + root.watcherExpected = Number(data.expected) || 0; + } + + readonly property color watcherColor: + !watcherAlive ? Theme.red + : watcherDead > 0 ? Theme.yellow + : Theme.green + + readonly property string watcherText: + !watcherAlive ? "watcher not running" + : watcherDead > 0 ? `${watcherDead} folder(s) dead, check the log` + : `watcher ok · ${watcherExpected} folders` + readonly property var screenObj: Quickshell.screens.find(s => s.name === root.monitor) ?? Quickshell.screens[0] @@ -95,6 +135,13 @@ Scope { // the drawer. MouseArea { anchors.fill: parent } + FileView { + id: heartbeat + path: `${Quickshell.env("HOME")}/.local/state/mail-watcher.heartbeat` + onLoaded: root.readHeartbeat(text()) + onLoadFailed: root.readHeartbeat("") + } + Column { id: content anchors { left: parent.left; right: parent.right; top: parent.top; margins: 16 } @@ -230,17 +277,36 @@ Scope { Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.12) } + // Watcher health, above the button. Green when idling, + // yellow when a folder gave up, red when there is no fresh + // heartbeat at all. + Item { + width: parent.width + implicitHeight: 22 + + Rectangle { + id: watcherDot + anchors.verticalCenter: parent.verticalCenter + width: 8; height: 8; radius: 4 + color: root.watcherColor + } + + Text { + anchors { left: watcherDot.right; leftMargin: 10; verticalCenter: parent.verticalCenter } + text: root.watcherText + color: Theme.subtext + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSize - 2 + } + } + + Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.12) } + Row { anchors.right: parent.right spacing: 10 Button { - text: Accounts.syncing ? "Syncing..." : "Sync now" - enabled: !Accounts.syncing - onClicked: Accounts.sync() - } - - Button { text: "Open qtmaildir" onClicked: { Accounts.openClient(); root.close(); } } |
