From 0a9ef3c77c7c593f3568f25761aad5d1f55e0e33 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 7 Aug 2026 11:39:31 +0200 Subject: feat(sync): the status bar says which account is syncing "Syncing..." was set once and never updated, so a run that takes over a minute reported nothing about what it was doing. The original diagnosis in the backlog was half wrong, and two further wrong ones were made and discarded before the real cause: plain "mbsync -a" prints NOTHING until it exits, then a single summary line. Measured on a real run, one line at 11:11:08 then 73 within the second 11:11:33, at the end of a 46-second run. So there was no stream to read for the part of a sync that takes time. It is not buffering, so stdbuf changes nothing, and the account name is not unavailable either, which was the second wrong conclusion. mbsync -V is what changes both: it announces each channel as it reaches it, which is at once the progress and the account name originally asked for. The shipped script now passes it. SyncPhaseTracker derives a short status from the output as it streams: the channel being synced, the summary counts when mbsync ends, then the notmuch reindex. It lives beside MailSync rather than in the window so the matching rules are one testable thing, and it holds no widget. Matching is loose and case-insensitive, since the wording varies by version, and nothing in it decides success or failure: the exit status remains the only authority on that. Lines are reassembled in MainWindow before being fed, because QProcess::readAll() splits wherever it happens to and a half-line would match nothing. Every status is sanitised and truncated: the channel name comes from a config file this app does not own, and a long one must not stretch the status bar. Two defects in existing code, fixed with it. setSyncBusy(true) ran after start(), so a fast run's output arrived before the per-run reset and wiped its own phase. And a first draft deferred phases while a transient message showed, which let a "Background sync completed" message armed before the sync began suppress the whole run: a running sync's state outranks an expiring event message. Verified by replaying real captured mbsync -V output through the tracker, not only against fixtures. The MainWindow test paces its script with sleeps, since a script that prints everything at once arrives in one readyRead and makes every intermediate phase unobservable. Closes item 42. Co-Authored-By: Claude Opus 5 --- src/mainwindow.h | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.h b/src/mainwindow.h index e673181..3984e78 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -28,6 +28,9 @@ #include "config.h" #include "keymap.h" +// Included rather than forward-declared: SyncPhaseTracker is held by value, so +// its size must be known here. MailSync itself stays a forward declaration. +#include "mailsync.h" #include "syncmonitor.h" #include "tagcolors.h" #include "types.h" @@ -155,6 +158,14 @@ private slots: /// the meta-object without widening the public API. void onExternalSyncStateChanged(SyncMonitor::State state); + /// Starts a sync and shows that it started. Every route in goes through + /// here: the toolbar, the menu, the shortcut and the button. + /// + /// A private slot for the same reason as the two above: a test needs to + /// start a real run through the meta-object to exercise the output + /// handling, without this becoming public API. + void startSync(); + /// A tag mutation the worker has confirmed reached the database. Counts it /// as unsynced, since reaching the index is not reaching the mail store. void onTagsApplied(const TagChange &change); @@ -217,9 +228,9 @@ private: /// says "working, duration unknown", which is the truth. void setSyncBusy(bool busy); - /// Starts a sync and shows that it started. Every route in goes through - /// here: the toolbar, the menu, the shortcut and the button. - void startSync(); + /// Reassembles lines from a sync output chunk and updates the status label + /// when the phase or its detail changes. + void feedSyncPhase(const QString &chunk); /// Applies the sync progress bar and button state from BOTH sync sources. /// @@ -298,6 +309,15 @@ private: MessageView *m_messageView = nullptr; MailSync *m_sync = nullptr; + /// Derives "which half of the sync is running" from the output stream, so + /// the status bar says more than "Syncing...". Reset at the start of each + /// local run. + SyncPhaseTracker m_syncPhase; + + /// Holds the tail of a chunk that did not end on a newline, since + /// QProcess::readAll() splits wherever it happens to. + QString m_syncLineBuffer; + /// Watches the sync lock for runs this window did not start. SyncMonitor *m_syncMonitor = nullptr; -- cgit v1.2.3