summaryrefslogtreecommitdiffstats
path: root/src/mailsync.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-09 09:32:09 +0200
committerDanilo M. <danix@danix.xyz>2026-08-09 09:32:09 +0200
commitd213bbffa9f77e78dc21c80308b48b522b0d5a8b (patch)
tree0d5e0357fc6b851f45ef5ea130d6aa866ac0f498 /src/mailsync.h
parent8508ac49d2fe72e362379950b52a64ccd6a6e773 (diff)
downloadqtmaildir-d213bbffa9f77e78dc21c80308b48b522b0d5a8b.tar.gz
qtmaildir-d213bbffa9f77e78dc21c80308b48b522b0d5a8b.zip
fix(sync): clear the pending-edit count on a cron sync
Item 54. A sync fired by the user's cron carries tag edits to the mail store exactly as a local one does, but only onSyncFinished() cleared the pending state, so the indicator kept reporting work that had already shipped and the exit prompt asked to sync for it. Verified against a real cron run: 31 changes, cleared with no manual sync. The window cannot see an external run's exit status, and /proc/locks carries no outcome. It does not need to: mailsync.sh already ends every run with a "RUN END ... status=OK" banner in its log, which outlives the process that wrote it. MailSync::lastRunOutcome() reads a bounded tail of that file and takes the last completed marker, so no change to the script and no optimistic guessing were needed. Only a definite OK clears anything. A failed run, a missing or unreadable log, and a State::Unknown lock reading all leave the count alone: over-reporting costs a redundant sync, under-reporting costs the user their edits. m_editedAccounts is drained in the same place, before flushHeldEdits() and matching the local path's ordering. Item 49 uses it to choose which mbsync channels a run syncs, and a count that reached zero while the set stayed full would look correct and still sync the wrong channels. The log path comes from a new optional [sync] log key, defaulting to where the script writes, so a test never reads the developer's own log. Two notes on the verification, both recorded in the backlog: - A timing probe endorsed a tail read that was not happening. The first version of the huge-log test required the call under 100 ms and passed with the seek deleted, because reading 10 MB is fast either way. Replaced with an assertion on content. - Every fixture was invented and the first batch had the wrong timestamp format, since the script uses date -Iseconds. The tests passed anyway, because the parser keys on the prefix and the status token. One test now builds the banner the way the script does. The before-flushHeldEdits ordering has no test: without a held lock the flush is a no-op, so both orderings pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/mailsync.h')
-rw-r--r--src/mailsync.h28
1 files changed, 28 insertions, 0 deletions
diff --git a/src/mailsync.h b/src/mailsync.h
index f83b526..a826f43 100644
--- a/src/mailsync.h
+++ b/src/mailsync.h
@@ -65,6 +65,17 @@ private:
QString m_status;
};
+/// The outcome of a sync run this process did not start.
+///
+/// Unknown is not a failure, it is the absence of evidence: no log, no marker,
+/// an unreadable file. Callers must treat it as "nothing observed" and change
+/// no state on it, exactly as SyncMonitor::State::Unknown is treated.
+enum class SyncOutcome {
+ Unknown,
+ Ok,
+ Failed,
+};
+
/// Runs the configured external sync command.
///
/// qtmaildir deliberately does not implement sync itself. The existing script
@@ -97,6 +108,23 @@ public:
QString log() const { return m_log; }
+ /// Where assets/mailsync.sh writes its log, unless the config overrides it.
+ static QString defaultLogPath();
+
+ /// Reads the outcome of the last COMPLETED run from \p logPath.
+ ///
+ /// This is how a sync fired by the user's cron is judged: the process that
+ /// ran it is gone and its exit status died with it, but the script writes
+ /// a "RUN END ... status=OK" line before exiting, and that line survives.
+ /// Deriving the outcome from mbsync's own chatter was rejected for the
+ /// reason given on SyncPhaseTracker: a second opinion built from loose text
+ /// matching eventually disagrees with the authoritative one.
+ ///
+ /// Reads a bounded tail, not the file: this runs on the UI thread every
+ /// time a background sync ends, against a file logrotate lets grow all day.
+ /// Anything unreadable, absent or unmarked is Unknown.
+ static SyncOutcome lastRunOutcome(const QString &logPath);
+
signals:
void started();
void outputReceived(const QString &chunk);