[PublicDecks] Time out the loading state so a dropped reply cannot wedge the tab

loading only cleared in decksReceived, but the ping sweep can drop a pending
command without ever emitting finished, leaving the tab stuck on 'Loading
public decks...' and the refresh button permanently inert. A single-shot
timer started per refresh clears the latch and reports a timeout; the latch
also clears when the client disconnects.
This commit is contained in:
Lukas Brübach 2026-09-19 07:48:00 +02:00 committed by GitHub
parent 64e0b2bbee
commit 454806d20e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 0 deletions

View file

@ -1,5 +1,8 @@
#include "remote_public_decks_model.h"
#include "../../../client/settings/cache_settings.h"
#include <QTimer>
#include <algorithm>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/command_deck_list_other_user.pb.h>
@ -7,10 +10,26 @@
#include <libcockatrice/protocol/pb/response_deck_list.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_deckstorage.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/network_settings.h>
RemotePublicDecksModel::RemotePublicDecksModel(AbstractClient *_client, QObject *parent)
: QAbstractListModel(parent), client(_client)
{
// The ping sweep can drop a pending command without ever emitting finished,
// so loading must not be a latch: time it out and clear it when the client
// goes away, or the tab is stuck on the loading state for the session.
loadingTimeoutTimer = new QTimer(this);
loadingTimeoutTimer->setSingleShot(true);
loadingTimeoutTimer->setInterval(
static_cast<int>((static_cast<qint64>(SettingsCache::instance().network().getTimeOut()) + 1) *
SettingsCache::instance().network().getKeepAlive() * 1000));
connect(loadingTimeoutTimer, &QTimer::timeout, this, &RemotePublicDecksModel::onLoadingTimeout);
connect(client, &AbstractClient::statusChanged, this, [this](ClientStatus status) {
if (status == StatusDisconnected) {
loadingTimeoutTimer->stop();
setLoading(false);
}
});
}
int RemotePublicDecksModel::rowCount(const QModelIndex &parent) const
@ -136,6 +155,7 @@ void RemotePublicDecksModel::refresh(const QString &userName)
return;
}
setLoading(true);
loadingTimeoutTimer->start();
Command_DeckListOtherUser cmd;
cmd.set_user_name(userName.toStdString());
PendingCommand *pend = client->prepareSessionCommand(cmd);
@ -143,6 +163,12 @@ void RemotePublicDecksModel::refresh(const QString &userName)
client->sendCommand(pend);
}
void RemotePublicDecksModel::onLoadingTimeout()
{
setLoading(false);
emit loadFailed(tr("The server did not respond in time. Try again."));
}
void RemotePublicDecksModel::clear()
{
decks.clear();
@ -161,6 +187,7 @@ void RemotePublicDecksModel::setLoading(bool value)
void RemotePublicDecksModel::decksReceived(const Response &response, const CommandContainer & /*commandContainer*/)
{
setLoading(false);
loadingTimeoutTimer->stop();
if (response.response_code() != Response::RespOk) {
emit loadFailed(tr("Failed to load the user's public decks (server response code %1).")
.arg(QString::number(static_cast<int>(response.response_code()))));

View file

@ -16,6 +16,7 @@
class AbstractClient;
class CommandContainer;
class QTimer;
class Response;
class ServerInfo_DeckStorage_Folder;
class ServerInfo_DeckStorage_TreeItem;
@ -103,6 +104,7 @@ signals:
private slots:
void decksReceived(const Response &response, const CommandContainer &commandContainer);
void onLoadingTimeout();
private:
void addFolder(const ServerInfo_DeckStorage_Folder &folder);
@ -111,6 +113,7 @@ private:
void setLoading(bool value);
AbstractClient *client;
QTimer *loadingTimeoutTimer;
QList<DeckEntry> decks;
QList<int> visibleIndices; ///< Row indices into `decks` that pass the current filters.
bool loading = false;