From e0f8fd0091fb702f46e84a1ff65bf4ff43d34658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 20 Sep 2026 20:17:18 +0200 Subject: [PATCH] [DeckStorage] Keep the visibility refresh armed until replies land The single-shot drain was armed with the 500 ms delay at send time, so a round trip slower than that drained before the server applied the change, re-read the old state and never re-armed, leaving the column stale until a manual refresh. The timer is now armed with the full network timeout at send time (a lost reply still costs one stale refresh) and re-armed with the short delay every time a reply lands. --- .../widgets/tabs/tab_deck_storage.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index 833dd0430..62769d0e3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -127,10 +127,11 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, SettingsCache::instance().network().getKeepAlive() * 1000)); connect(shareTimeoutTimer, &QTimer::timeout, this, &TabDeckStorage::onShareFromTreeTimeout); - // Restartable single-shot refresh for the Public/Private column. A dropped - // visibility reply must not leave the widget permanently stale, so the tree - // is re-read whenever publishes quiet down instead of waiting on a count - // that can get stuck above zero. + // Restartable single-shot refresh for the Public/Private column. It is + // armed with the full network timeout when a publish is sent (so a dropped + // reply still drains once) and re-armed with the short delay every time a + // reply lands, so the drain cannot fire while a slow round trip is still in + // flight. Either way the tree is re-read once things quiet down. visibilityRefreshTimer = new QTimer(this); visibilityRefreshTimer->setSingleShot(true); visibilityRefreshTimer->setInterval(VISIBILITY_REFRESH_DELAY); @@ -862,6 +863,13 @@ void TabDeckStorage::onShareFromTreeTimeout() void TabDeckStorage::actPublishDeck() { visibilityFailures.clear(); + // Arm the drain with the full network timeout so a lost reply still costs + // one refresh instead of a dead column; each reply shrinks it to the short + // delay below, so a slow round trip is never drained before it lands. + const int visibilityFailSafeDelay = + static_cast((static_cast(SettingsCache::instance().network().getTimeOut()) + 1) * + SettingsCache::instance().network().getKeepAlive() * 1000); + const auto selection = serverDirView->getCurrentSelection(); for (const auto *node : selection) { Command_DeckSetVisibility cmd; @@ -884,6 +892,7 @@ void TabDeckStorage::actPublishDeck() PendingCommand *pend = client->prepareSessionCommand(cmd); connect(pend, &PendingCommand::finished, this, &TabDeckStorage::setVisibilityFinished); visibilityRefreshStarted = true; + visibilityRefreshTimer->setInterval(visibilityFailSafeDelay); visibilityRefreshTimer->start(); client->sendCommand(pend); } @@ -893,6 +902,7 @@ void TabDeckStorage::setVisibilityFinished(const Response &r, const CommandConta { if (r.response_code() == Response::RespOk) { if (visibilityRefreshStarted) { + visibilityRefreshTimer->setInterval(VISIBILITY_REFRESH_DELAY); visibilityRefreshTimer->start(); } return; @@ -904,6 +914,7 @@ void TabDeckStorage::setVisibilityFinished(const Response &r, const CommandConta .arg(QString::number(static_cast(r.response_code()))); if (visibilityRefreshStarted) { visibilityFailures.append(message); + visibilityRefreshTimer->setInterval(VISIBILITY_REFRESH_DELAY); visibilityRefreshTimer->start(); } else { QMessageBox::critical(this, tr("Error"), message);