aboutsummaryrefslogtreecommitdiffstats
path: root/src/mainwindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mainwindow.cpp')
-rw-r--r--src/mainwindow.cpp82
1 files changed, 81 insertions, 1 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 824bdca..04e951d 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -485,6 +485,13 @@ void MainWindow::buildUi()
connect(m_markReadTimer, &QTimer::timeout,
this, &MainWindow::markCurrentThreadRead);
+ m_autoSyncTimer = new QTimer(this);
+ // Named for the same reason: a test can assert that an edit armed the
+ // debounce without waiting out the delay or starting a real mbsync.
+ m_autoSyncTimer->setObjectName(QStringLiteral("autoSyncTimer"));
+ m_autoSyncTimer->setSingleShot(true);
+ connect(m_autoSyncTimer, &QTimer::timeout, this, &MainWindow::runAutoSync);
+
// The pane and its close button travel together: a QPlainTextEdit has
// nowhere to put one, and a pane that appears on a failed sync and can
// never be dismissed is worse than one that does not appear at all.
@@ -2194,7 +2201,22 @@ void MainWindow::onSyncFinished(bool success, int exitCode)
return;
}
- runCurrentQuery();
+ // refreshCurrentQuery(), NOT runCurrentQuery(). A sync this window
+ // started is not a query the user asked to re-run: runCurrentQuery()
+ // clears the model, the undo stack and the message pane, so a sync
+ // landing while a message was open read the user out of it. The cron
+ // path has reconciled instead since item 35, and there was never a
+ // reason for the two to differ.
+ //
+ // Item 71 is what made it matter. A local sync used to happen only
+ // when the user clicked Sync, where blanking was at least explicable;
+ // the automatic one fires two seconds after a tag edit, which is
+ // precisely when the user is still reading the message they tagged.
+ // Reconciling keeps the pane, and updateStaleThreadNotice() then offers
+ // "Show it anyway" for a thread that has stopped matching the query,
+ // which is the reported case: reading in Unread, the thread is marked
+ // read, and it no longer belongs to the view it was opened from.
+ refreshCurrentQuery();
// A sync is the usual way new tags enter the database.
requestAllTags();
} else if (exitCode == kSyncSkippedExitCode) {
@@ -2280,6 +2302,12 @@ void MainWindow::onTagsApplied(const TagChange &change)
updatePendingIndicator();
+ // Item 71. Armed here, where a write is CONFIRMED and the pending count is
+ // already up to date, for the same reason recordPendingEdit() is called
+ // here: a sync scheduled for a write the worker went on to reject would run
+ // for nothing.
+ scheduleAutoSync();
+
// A tag the user has just created is the one they are most likely to type
// again, so do not wait for the next sync to offer it. A set membership
// test, not a query.
@@ -2678,6 +2706,58 @@ void MainWindow::startSync()
setSyncBusy(true);
}
+void MainWindow::scheduleAutoSync()
+{
+ // Negative disables the behaviour entirely, per the config key, and that is
+ // the pre-0.16.0 behaviour: edits wait for a manual sync or the user's cron
+ // job. Checked before anything else so a disabled delay arms nothing.
+ const int delay = m_config.autoSyncDelayMs();
+ if (delay < 0)
+ return;
+
+ // No sync command means the Sync action is already disabled and startSync()
+ // would only put "No sync command configured" in the status bar. Arming a
+ // timer to say that on a delay, for something the user did not ask for, is
+ // worse than staying quiet.
+ if (!m_sync || !m_sync->isAvailable())
+ return;
+
+ // Nothing outstanding, nothing to carry. An edit netted against its own
+ // inverse leaves the count at zero (item 28), and syncing for it would run
+ // mbsync over a mail store that is already where the server left it.
+ if (pendingEditCount() == 0)
+ return;
+
+ // Restart, not stack. Tagging a multi-row selection confirms one write per
+ // thread and "mark all read" confirms one per thread in the view, so an
+ // armed-per-edit timer would be exactly the storm of syncs a debounce is
+ // for. The last edit of a burst decides when the single sync happens.
+ m_autoSyncTimer->start(delay);
+}
+
+void MainWindow::runAutoSync()
+{
+ // The user can have synced by hand, or undone the edit, in the delay. Both
+ // leave nothing to carry, and re-checking here rather than trusting the arm
+ // is what makes the debounce safe to restart freely.
+ if (pendingEditCount() == 0)
+ return;
+
+ // Skip rather than queue when a sync is already in flight, which item 71
+ // requires: the cron job holds the same lock, and mbsync's own answer to a
+ // second run is to fail on it. The edits are not lost by skipping. They stay
+ // pending, and the sync already running is very likely to carry them, since
+ // they reached the mail store at edit time.
+ //
+ // m_externalSyncBusy covers the cron job SyncMonitor can see. A lock taken
+ // between that poll and now is not visible here, and does not need to be:
+ // MailSync::start() fails on a second run and startSync() reports it.
+ if (m_externalSyncBusy || (m_sync && m_sync->isRunning()))
+ return;
+
+ startSync();
+}
+
void MainWindow::recordPendingEdit(const QString &messageId, const QString &tag,
bool added)
{