summaryrefslogtreecommitdiffstats
path: root/src/mainwindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mainwindow.cpp')
-rw-r--r--src/mainwindow.cpp62
1 files changed, 59 insertions, 3 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 0ce94a8..f830683 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -217,7 +217,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
box.exec();
if (box.clickedButton() == sync) {
- if (m_sync->start()) {
+ if (m_sync->start(pendingSyncChannels())) {
m_syncingForExit = true;
m_syncLog->clear();
setSyncBusy(true);
@@ -238,7 +238,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
return;
}
} else if (m_config.syncOnExit() == Config::SyncOnExit::Always) {
- if (m_sync->start()) {
+ if (m_sync->start(pendingSyncChannels())) {
m_syncingForExit = true;
m_syncLog->clear();
setSyncBusy(true);
@@ -1700,6 +1700,16 @@ void MainWindow::onSyncFinished(bool success, int exitCode)
// so they go to the mail store on the NEXT run. That is the same one-run
// delay any edit made mid-sync gets, bounded by the cron interval.
const bool sentHeldEdits = !m_heldEdits.isEmpty();
+
+ // Snapshotted BEFORE the flush, and this ordering is load-bearing.
+ // flushHeldEdits() calls sendThreadTagChange(), which inserts into
+ // m_editedAccounts SYNCHRONOUSLY, unlike the pending-edit map below which
+ // is written on the worker's queued reply and so is safely counted rather
+ // than wiped. Clearing the whole set after the flush would therefore
+ // discard accounts whose edits this run did not carry, and those edits
+ // would sync only when some later edit happened to name the same account.
+ const QSet<QString> accountsThisRunCarried = m_editedAccounts;
+
flushHeldEdits();
if (success) {
@@ -1708,6 +1718,12 @@ void MainWindow::onSyncFinished(bool success, int exitCode)
// what failed to put them there.
m_pendingTagEdits.clear();
m_unnettablePendingEdits = 0;
+
+ // Only what this run actually carried, per the snapshot above. An
+ // account added by flushHeldEdits() stays, because its edit reaches the
+ // index after the sync that would have taken it and goes out on the
+ // next run.
+ m_editedAccounts.subtract(accountsThisRunCarried);
m_lastSyncFailed = false;
updatePendingIndicator();
@@ -1997,7 +2013,7 @@ void MainWindow::startSync()
m_syncPhase.reset();
m_syncLineBuffer.clear();
- if (!m_sync->start()) {
+ if (!m_sync->start(pendingSyncChannels())) {
showTransientStatus(tr("Sync already running"));
return;
}
@@ -2022,6 +2038,34 @@ void MainWindow::recordPendingEdit(const QString &messageId, const QString &tag,
m_pendingTagEdits.insert(key, added);
}
+QStringList MainWindow::pendingSyncChannels() const
+{
+ // Nothing pending means this run is a FETCH, and a fetch must cover every
+ // account: narrowing it to wherever the last edit happened to be would
+ // quietly stop collecting mail everywhere else. Empty is the signal for
+ // that, and MailSync::start() appends nothing.
+ if (m_editedAccounts.isEmpty())
+ return {};
+
+ QStringList channels;
+ for (const Account &account : m_config.accounts()) {
+ if (m_editedAccounts.contains(account.key))
+ channels.append(account.syncChannel());
+ }
+
+ // An account tag with no matching [account.<key>] section yields no
+ // channel, and syncing a subset that omits it would leave its edits behind
+ // with nothing to say so. Fall back to a full sync, which is correct if
+ // wasteful; the alternative is silently stranding an edit.
+ if (channels.size() != m_editedAccounts.size())
+ return {};
+
+ // Stable order so a run is reproducible and the log reads the same way
+ // twice. QSet has no order of its own.
+ channels.sort();
+ return channels;
+}
+
int MainWindow::pendingEditCount() const
{
// A held edit has NOT reached the index, so onTagsApplied() never counted
@@ -2188,6 +2232,18 @@ void MainWindow::sendThreadTagChange(const QStringList &threadIds,
for (const QString &threadId : threadIds)
m_model->applyTagChange(threadId, add, remove);
+ // Which accounts this touches, recorded HERE and not in onTagsApplied():
+ // TagChange carries message ids, while the account is a property of the
+ // thread, and by the time the worker confirms, the rows may be gone. A
+ // write that is later rejected leaves an account listed here that needed no
+ // sync, which costs one redundant channel on the next run; missing one
+ // would strand the user's edits, which is the failure worth avoiding.
+ for (const QString &threadId : threadIds) {
+ const QStringList keys = m_model->accountKeysForThread(threadId);
+ for (const QString &key : keys)
+ m_editedAccounts.insert(key);
+ }
+
// The strip shows the open thread's tags, so it has to follow a change to
// that thread rather than waiting for the next selection.
if (threadIds.contains(m_currentThreadId)) {