Move impl to cpp, fix lifetime issues.

Took 11 minutes

Took 2 minutes
This commit is contained in:
Lukas Brübach
2026-08-03 14:30:45 +02:00
parent 961a973772
commit a9077aa65b
27 changed files with 442 additions and 281 deletions

View File

@@ -136,8 +136,11 @@ set(cockatrice_SOURCES
src/interface/card_picture_loader/card_picture_loader_worker_work.cpp
src/interface/card_picture_loader/card_picture_to_load.cpp
src/interface/intents/intent.cpp
src/interface/intents/intent.h
src/interface/intents/intent_open_local_deck.cpp
src/interface/intents/intent_open_local_deck.h
src/interface/intents/intent_wait_for_database_load.cpp
src/interface/intents/intent_wait_for_database_load.h
src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp
@@ -364,7 +367,6 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
src/interface/widgets/utility/compact_push_button.cpp
src/interface/widgets/utility/compact_push_button.h
src/single_instance_manager.cpp
src/single_instance_manager.h
src/client/url_scheme_event_filter.h
src/interface/intents/intent_connect_to_server.cpp
@@ -372,12 +374,13 @@ set(cockatrice_SOURCES
src/interface/intents/intent_disconnect_from_server.cpp
src/interface/intents/intent_disconnect_from_server.h
src/interface/intents/intent_join_server_game.cpp
src/interface/intents/intent_join_server_game.h
src/interface/intents/intent_join_server_room.cpp
src/interface/intents/intent_join_server_room.h
src/interface/intents/url_parser.cpp
src/interface/intents/url_parser.h
src/interface/intents/intent_login.cpp
src/interface/intents/intent_login.h
src/interface/intents/url_parser.cpp
src/interface/intents/url_parser.h
src/interface/widgets/server/user/user_info_popup.cpp
src/interface/widgets/server/user/user_info_popup.h
)

View File

@@ -3,7 +3,7 @@
Version=1.0
Type=Application
Name=Cockatrice
Exec=cockatrice
Exec=cockatrice %U
Icon=cockatrice
Categories=Game;CardGame;
MimeType=application/x-cockatrice;

View File

@@ -1 +1,32 @@
#include "intent.h"
Intent::Intent(QObject *parent) : QObject(parent)
{
// An intent is done as soon as it reports success or failure. Deleting it
// also tears down its dependency chain and disconnects any signal wiring.
connect(this, &Intent::finished, this, &QObject::deleteLater);
connect(this, &Intent::failed, this, &QObject::deleteLater);
}
Intent::~Intent() = default;
void Intent::execute()
{
if (checkPrecondition()) {
onPreconditionSatisfied();
} else {
onPreconditionNotSatisfied();
}
}
void Intent::runDependency(Intent *dependency)
{
dependency->setParent(this);
connect(dependency, &Intent::finished, this, [this]() {
// Re-check after dependency finishes
this->execute();
});
connect(dependency, &Intent::failed, this, &Intent::failed);
dependency->execute();
}

View File

@@ -8,19 +8,10 @@ class Intent : public QObject
Q_OBJECT
public:
explicit Intent(QObject *parent = nullptr) : QObject(parent)
{
}
virtual ~Intent() = default;
explicit Intent(QObject *parent = nullptr);
~Intent() override;
void execute()
{
if (checkPrecondition()) {
onPreconditionSatisfied();
} else {
onPreconditionNotSatisfied();
}
}
void execute();
signals:
void finished();
@@ -33,17 +24,7 @@ protected:
virtual void onPreconditionNotSatisfied() = 0;
// Helper to chain another intent
void runDependency(Intent *dependency)
{
connect(dependency, &Intent::finished, this, [this]() {
// Re-check after dependency finishes
this->execute();
});
connect(dependency, &Intent::failed, this, &Intent::failed);
dependency->execute();
}
void runDependency(Intent *dependency);
};
#endif // COCKATRICE_INTENT_H

View File

@@ -1 +1,35 @@
#include "intent_connect_to_server.h"
#include "intent_disconnect_from_server.h"
IntentConnectToServer::IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context)
: Intent(), remoteClient(_remoteClient), context(_context)
{
}
bool IntentConnectToServer::checkPrecondition() const
{
return remoteClient->getStatus() == ClientStatus::StatusDisconnected;
}
void IntentConnectToServer::onPreconditionSatisfied()
{
remoteClient->connectToServer(context->hostname, context->port.toUInt(), context->username, context->password);
connect(remoteClient, &RemoteClient::statusChanged, this, &IntentConnectToServer::onStatusChanged);
connect(remoteClient, &RemoteClient::loginError, this,
[this](Response::ResponseCode, const QString &reason, quint32, const QList<QString> &) {
emit failed(reason);
});
}
void IntentConnectToServer::onPreconditionNotSatisfied()
{
runDependency(new IntentDisconnectFromServer(remoteClient));
}
void IntentConnectToServer::onStatusChanged(ClientStatus status)
{
if (status == ClientStatus::StatusLoggedIn) {
emit finished();
}
}

View File

@@ -3,52 +3,26 @@
#include "contexts/context_connect_to_server.h"
#include "intent.h"
#include "intent_disconnect_from_server.h"
#include "remote_client.h"
#include <QTimer>
class IntentConnectToServer : public Intent
{
Q_OBJECT
public:
IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context)
: Intent(), remoteClient(_remoteClient), context(_context)
{
}
IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context);
protected:
bool checkPrecondition() const override
{
return remoteClient->getStatus() == ClientStatus::StatusDisconnected;
}
void onPreconditionSatisfied() override
{
remoteClient->connectToServer(context->hostname, context->port.toUInt(), context->username, context->password);
connect(remoteClient, &RemoteClient::statusChanged, this, &IntentConnectToServer::onStatusChanged);
}
void onPreconditionNotSatisfied() override
{
runDependency(new IntentDisconnectFromServer(remoteClient));
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
private:
RemoteClient *remoteClient;
ContextConnectToServer *context;
private slots:
void onStatusChanged(ClientStatus status)
{
if (status == ClientStatus::StatusLoggedIn) {
auto timer = new QTimer(this);
timer->setSingleShot(true);
timer->setInterval(2000);
connect(timer, &QTimer::timeout, this, &IntentConnectToServer::finished);
timer->start();
}
}
void onStatusChanged(ClientStatus status);
};
#endif // COCKATRICE_INTENT_CONNECT_TO_SERVER_H

View File

@@ -1 +1,29 @@
#include "intent_disconnect_from_server.h"
IntentDisconnectFromServer::IntentDisconnectFromServer(RemoteClient *_remoteClient)
: Intent(), remoteClient(_remoteClient)
{
}
bool IntentDisconnectFromServer::checkPrecondition() const
{
return remoteClient->getStatus() == ClientStatus::StatusDisconnected;
}
void IntentDisconnectFromServer::onPreconditionSatisfied()
{
emit finished();
}
void IntentDisconnectFromServer::onPreconditionNotSatisfied()
{
connect(remoteClient, &RemoteClient::statusChanged, this, &IntentDisconnectFromServer::onStatusChanged);
remoteClient->disconnectFromServer();
}
void IntentDisconnectFromServer::onStatusChanged(ClientStatus status)
{
if (status == ClientStatus::StatusDisconnected) {
emit finished();
}
}

View File

@@ -1,5 +1,6 @@
#ifndef COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H
#define COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H
#include "intent.h"
#include "remote_client.h"
@@ -8,40 +9,18 @@ class IntentDisconnectFromServer : public Intent
Q_OBJECT
public:
IntentDisconnectFromServer(RemoteClient *_remoteClient) : Intent(), remoteClient(_remoteClient)
{
}
IntentDisconnectFromServer(RemoteClient *_remoteClient);
protected:
bool checkPrecondition() const override
{
return remoteClient->getStatus() == ClientStatus::StatusDisconnected;
}
void onPreconditionSatisfied() override
{
qWarning() << "Client disconnected, disconnect is finished";
emit finished();
}
void onPreconditionNotSatisfied() override
{
qWarning() << "Client not disconnected, hooking up signal and disconnecting." << remoteClient->getStatus();
connect(remoteClient, &RemoteClient::statusChanged, this, &IntentDisconnectFromServer::onStatusChanged);
remoteClient->disconnectFromServer();
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
private:
RemoteClient *remoteClient;
private slots:
void onStatusChanged(ClientStatus status)
{
qWarning() << "Client Status changed: " << status;
if (status == ClientStatus::StatusDisconnected) {
qWarning() << "Client disconnected, finished";
emit finished();
}
}
void onStatusChanged(ClientStatus status);
};
#endif // COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H

View File

@@ -1 +1,78 @@
#include "intent_join_server_game.h"
#include "../widgets/server/game_selector.h"
#include "../widgets/tabs/tab_room.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "intent_join_server_room.h"
#include <QTimer>
IntentJoinServerGame::IntentJoinServerGame(TabSupervisor *_tabSupervisor,
RemoteClient *_remoteClient,
ContextJoinGame *_context)
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
{
}
bool IntentJoinServerGame::checkPrecondition() const
{
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
return false;
}
// peerPort() reflects the actual TCP peer, which may differ from the
// configured server port (e.g. when connecting through a proxy), so only
// the hostname is compared here.
if (remoteClient->peerName() != context->roomContext.serverContext.hostname) {
return false;
}
if (!tabSupervisor->getRoomTabs().contains(context->roomContext.roomId)) {
return false;
}
return true;
}
void IntentJoinServerGame::onPreconditionSatisfied()
{
TabRoom *room = tabSupervisor->getRoomTabs().value(context->roomContext.roomId);
if (!tryJoinGame(room)) {
waitForGame(room);
}
}
void IntentJoinServerGame::onPreconditionNotSatisfied()
{
runDependency(new IntentJoinServerRoom(tabSupervisor, remoteClient, &context->roomContext));
}
bool IntentJoinServerGame::tryJoinGame(TabRoom *room)
{
if (!room) {
return false;
}
if (room->getGameSelector()->joinGameById(context->gameId)) {
joined = true;
emit finished();
return true;
}
return false;
}
void IntentJoinServerGame::waitForGame(TabRoom *room)
{
connect(room, &TabRoom::gameListUpdated, this, [this]() {
TabRoom *updatedRoom = tabSupervisor->getRoomTabs().value(context->roomContext.roomId);
if (updatedRoom) {
tryJoinGame(updatedRoom);
}
});
QTimer::singleShot(15000, this, [this]() {
if (!joined) {
emit failed(tr("Game %1 not found in the room").arg(context->gameId));
}
});
}

View File

@@ -1,63 +1,35 @@
#ifndef COCKATRICE_INTENT_JOIN_SERVER_GAME_H
#define COCKATRICE_INTENT_JOIN_SERVER_GAME_H
#include "../widgets/server/game_selector.h"
#include "../widgets/tabs/tab_room.h"
#include "../widgets/tabs/tab_server.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "contexts/context_join_game.h"
#include "contexts/context_join_room.h"
#include "intent.h"
#include "intent_join_server_room.h"
#include "remote_client.h"
#include <QScopedPointer>
class TabRoom;
class TabSupervisor;
class IntentJoinServerGame : public Intent
{
Q_OBJECT
public:
IntentJoinServerGame(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinGame *_context)
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
{
}
IntentJoinServerGame(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinGame *_context);
protected:
bool checkPrecondition() const override
{
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
return false;
}
if (remoteClient->peerName() != context->roomContext.serverContext.hostname) {
return false;
}
if (QString::number(remoteClient->peerPort()) != context->roomContext.serverContext.port) {
return false;
}
if (!tabSupervisor->getRoomTabs()[context->roomContext.roomId]) {
qWarning() << "No room tab";
return false;
};
return true;
}
void onPreconditionSatisfied() override
{
qWarning() << "All lights green, joining game";
TabRoom *room = tabSupervisor->getRoomTabs()[context->roomContext.roomId];
room->getGameSelector()->joinGameById(context->gameId);
}
void onPreconditionNotSatisfied() override
{
runDependency(new IntentJoinServerRoom(tabSupervisor, remoteClient, &context->roomContext));
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
private:
bool tryJoinGame(TabRoom *room);
void waitForGame(TabRoom *room);
TabSupervisor *tabSupervisor;
RemoteClient *remoteClient;
ContextJoinGame *context;
QScopedPointer<ContextJoinGame> context;
bool joined = false;
};
#endif // COCKATRICE_INTENT_JOIN_SERVER_GAME_H

View File

@@ -1 +1,55 @@
#include "intent_join_server_room.h"
#include "../widgets/tabs/tab_room.h"
#include "../widgets/tabs/tab_server.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "intent_connect_to_server.h"
IntentJoinServerRoom::IntentJoinServerRoom(TabSupervisor *_tabSupervisor,
RemoteClient *_remoteClient,
ContextJoinRoom *_context)
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
{
}
bool IntentJoinServerRoom::checkPrecondition() const
{
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
return false;
}
// peerPort() reflects the actual TCP peer, which may differ from the
// configured server port (e.g. when connecting through a proxy), so only
// the hostname is compared here.
if (remoteClient->peerName() != context->serverContext.hostname) {
return false;
}
return true;
}
void IntentJoinServerRoom::onPreconditionSatisfied()
{
if (tabSupervisor->getRoomTabs().contains(context->roomId)) {
tabSupervisor->setCurrentWidget(tabSupervisor->getRoomTabs().value(context->roomId));
emit finished();
return;
}
TabServer *tabServer = tabSupervisor->getTabServer();
if (!tabServer) {
tabSupervisor->openTabServer();
tabServer = tabSupervisor->getTabServer();
}
if (!tabServer) {
emit failed(tr("No server tab available"));
return;
}
tabServer->joinRoom(context->roomId, true);
connect(tabServer, &TabServer::roomJoined, this, &IntentJoinServerRoom::finished);
}
void IntentJoinServerRoom::onPreconditionNotSatisfied()
{
runDependency(new IntentConnectToServer(remoteClient, &context->serverContext));
}

View File

@@ -1,53 +1,23 @@
#ifndef COCKATRICE_INTENT_JOIN_SERVER_ROOM_H
#define COCKATRICE_INTENT_JOIN_SERVER_ROOM_H
#include "../widgets/tabs/tab_server.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "contexts/context_connect_to_server.h"
#include "contexts/context_join_room.h"
#include "intent.h"
#include "intent_connect_to_server.h"
#include "intent_disconnect_from_server.h"
#include "remote_client.h"
class TabSupervisor;
class IntentJoinServerRoom : public Intent
{
Q_OBJECT
public:
IntentJoinServerRoom(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinRoom *_context)
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
{
}
IntentJoinServerRoom(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinRoom *_context);
protected:
bool checkPrecondition() const override
{
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
return false;
}
if (remoteClient->peerName() != context->serverContext.hostname) {
return false;
}
if (QString::number(remoteClient->peerPort()) != context->serverContext.port) {
return false;
}
return true;
}
void onPreconditionSatisfied() override
{
auto tabServer = tabSupervisor->getTabServer();
tabServer->joinRoom(context->roomId, true);
connect(tabServer, &TabServer::roomJoined, this, &IntentJoinServerRoom::finished);
}
void onPreconditionNotSatisfied() override
{
runDependency(new IntentConnectToServer(remoteClient, &context->serverContext));
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
private:
TabSupervisor *tabSupervisor;

View File

@@ -1 +1,33 @@
#include "intent_login.h"
#include "../../client/settings/cache_settings.h"
#include "libcockatrice/settings/servers_settings.h"
IntentGetLoginCredentials::IntentGetLoginCredentials(ContextConnectToServer *_context) : Intent(), context(_context)
{
}
bool IntentGetLoginCredentials::checkPrecondition() const
{
ServersSettings &servers = SettingsCache::instance().servers();
return servers.hasLoginData(context->hostname, context->port);
}
void IntentGetLoginCredentials::onPreconditionSatisfied()
{
ServersSettings &servers = SettingsCache::instance().servers();
const int index = servers.findServerIndex(context->hostname, context->port);
if (index >= 0) {
context->username = servers.getValue(QString("username%1").arg(index), "server", "server_details").toString();
context->password = servers.getValue(QString("password%1").arg(index), "server", "server_details").toString();
emit finished();
} else {
emit failed(tr("No saved credentials for this server"));
}
}
void IntentGetLoginCredentials::onPreconditionNotSatisfied()
{
emit failed(tr("No saved credentials for this server"));
}

View File

@@ -1,51 +1,22 @@
#ifndef COCKATRICE_INTENT_LOGIN_H
#define COCKATRICE_INTENT_LOGIN_H
#include "../../client/settings/cache_settings.h"
#include "contexts/context_connect_to_server.h"
#include "intent.h"
#include "remote_client.h"
class IntentGetLoginCredentials : public Intent
{
Q_OBJECT
public:
IntentGetLoginCredentials(RemoteClient *_remoteClient, ContextConnectToServer *_context)
: Intent(), remoteClient(_remoteClient), context(_context)
{
}
IntentGetLoginCredentials(ContextConnectToServer *_context);
protected:
bool checkPrecondition() const override
{
ServersSettings &servers = SettingsCache::instance().servers();
return servers.hasLoginData(context->hostname, context->port);
}
void onPreconditionSatisfied() override
{
ServersSettings &servers = SettingsCache::instance().servers();
auto index = servers.findServerIndex(context->hostname, context->port);
if (index >= 0) {
context->username =
servers.getValue(QString("username%1").arg(index), "server", "server_details").toString();
context->password =
servers.getValue(QString("password%1").arg(index), "server", "server_details").toString();
emit finished();
qWarning() << "Using saved credentials";
} else {
qWarning() << "No saved server entry";
}
}
void onPreconditionNotSatisfied() override
{
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
private:
RemoteClient *remoteClient;
ContextConnectToServer *context;
};

View File

@@ -1 +1,32 @@
#include "intent_open_local_deck.h"
#include "../deck_loader/deck_file_format.h"
#include "../deck_loader/deck_loader.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "intent_wait_for_database_load.h"
#include <libcockatrice/card/database/card_database_manager.h>
IntentOpenLocalDeck::IntentOpenLocalDeck(TabSupervisor *_tabSupervisor, const QString &_file)
: Intent(), tabSupervisor(_tabSupervisor), file(_file)
{
}
bool IntentOpenLocalDeck::checkPrecondition() const
{
return CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok;
}
void IntentOpenLocalDeck::onPreconditionSatisfied()
{
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true);
if (deckOpt) {
tabSupervisor->openDeckInNewTab(deckOpt.value());
}
emit finished();
}
void IntentOpenLocalDeck::onPreconditionNotSatisfied()
{
runDependency(new IntentWaitForDatabaseLoad);
}

View File

@@ -1,40 +1,23 @@
#ifndef COCKATRICE_INTENT_OPEN_LOCAL_DECK_H
#define COCKATRICE_INTENT_OPEN_LOCAL_DECK_H
#include "../widgets/tabs/tab_supervisor.h"
#include "intent.h"
#include "intent_wait_for_database_load.h"
#include "libcockatrice/card/database/card_database_manager.h"
#include <QString>
class TabSupervisor;
class IntentOpenLocalDeck : public Intent
{
Q_OBJECT
public:
IntentOpenLocalDeck(TabSupervisor *_tabSupervisor, const QString &_file)
: Intent(), tabSupervisor(_tabSupervisor), file(_file)
{
}
IntentOpenLocalDeck(TabSupervisor *_tabSupervisor, const QString &_file);
protected:
bool checkPrecondition() const override
{
return CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok;
}
void onPreconditionSatisfied() override
{
std::optional<LoadedDeck> deckOpt =
DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true);
if (deckOpt) {
tabSupervisor->openDeckInNewTab(deckOpt.value());
}
emit finished();
}
void onPreconditionNotSatisfied() override
{
runDependency(new IntentWaitForDatabaseLoad);
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
private:
TabSupervisor *tabSupervisor;

View File

@@ -1 +1,19 @@
#include "intent_wait_for_database_load.h"
#include <libcockatrice/card/database/card_database_manager.h>
bool IntentWaitForDatabaseLoad::checkPrecondition() const
{
return CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok;
}
void IntentWaitForDatabaseLoad::onPreconditionSatisfied()
{
emit finished();
}
void IntentWaitForDatabaseLoad::onPreconditionNotSatisfied()
{
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
[this]() { emit finished(); });
}

View File

@@ -2,28 +2,15 @@
#define COCKATRICE_INTENT_WAIT_FOR_DATABASE_LOAD_H
#include "intent.h"
#include "libcockatrice/card/database/card_database_manager.h"
class IntentWaitForDatabaseLoad : public Intent
{
Q_OBJECT
protected:
bool checkPrecondition() const override
{
return CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok;
}
void onPreconditionSatisfied() override
{
emit finished();
}
void onPreconditionNotSatisfied() override
{
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
[this]() { emit finished(); });
}
bool checkPrecondition() const override;
void onPreconditionSatisfied() override;
void onPreconditionNotSatisfied() override;
};
#endif // COCKATRICE_INTENT_WAIT_FOR_DATABASE_LOAD_H

View File

@@ -1,9 +1,8 @@
#include "url_parser.h"
#include "../window_main.h"
#include "contexts/context_join_room.h"
#include "contexts/context_join_game.h"
#include "intent_join_server_game.h"
#include "intent_join_server_room.h"
#include "intent_login.h"
#include <QDebug>
@@ -46,6 +45,7 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
if (!ok) {
qWarning() << "Invalid or missing roomId";
delete ctx;
return;
}
@@ -54,15 +54,20 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
if (!ok) {
qWarning() << "Invalid or missing gameId";
delete ctx;
return;
}
auto getLoginCredentialsIntent =
new IntentGetLoginCredentials(mainWindow->getRemoteClient(), &ctx->roomContext.serverContext);
// The join game intent owns the context and the credential lookup; once the
// chain finishes (or fails) it deletes the whole tree.
auto joinGameIntent = new IntentJoinServerGame(mainWindow->getTabSupervisor(), mainWindow->getRemoteClient(), ctx);
joinGameIntent->setParent(this);
auto getLoginCredentialsIntent = new IntentGetLoginCredentials(&ctx->roomContext.serverContext);
getLoginCredentialsIntent->setParent(joinGameIntent);
connect(getLoginCredentialsIntent, &Intent::finished, joinGameIntent, &Intent::execute);
connect(getLoginCredentialsIntent, &Intent::failed, joinGameIntent, &Intent::failed);
getLoginCredentialsIntent->execute();
}

View File

@@ -310,7 +310,6 @@ void GameSelector::customContextMenu(const QPoint &point)
connect(&getGameInfo, &QAction::triggered, this, [=, this]() {
const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt());
const QMap<int, QString> &gameTypes = gameListModel->getGameTypes().value(gameInfo.room_id());
qWarning() << "Game Id: " << gameInfo.game_id();
DlgCreateGame dlg(gameInfo, gameTypes, this);
dlg.exec();

View File

@@ -280,6 +280,7 @@ void TabRoom::processListGamesEvent(const Event_ListGames &event)
for (int i = 0; i < gameListSize; ++i) {
gameSelector->processGameInfo(event.game_list(i));
}
emit gameListUpdated();
}
void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event)

View File

@@ -78,6 +78,7 @@ signals:
void openMessageDialog(const QString &userName, bool focus);
void maximizeClient();
void notIdle();
void gameListUpdated();
private slots:
void sendMessage();
void sayFinished(const Response &response);

View File

@@ -587,6 +587,10 @@ void TabSupervisor::actTabServer(bool checked)
void TabSupervisor::openTabServer()
{
if (tabServer) {
return;
}
tabServer = new TabServer(this, client);
connect(tabServer, &TabServer::roomJoined, this, &TabSupervisor::addRoomTab);
myAddTab(tabServer, aTabServer);

View File

@@ -187,6 +187,7 @@ public slots:
void maximizeMainWindow();
void actTabVisualDeckStorage(bool checked);
void actTabReplays(bool checked);
void openTabServer();
private slots:
void refreshShortcuts();
@@ -199,7 +200,6 @@ private slots:
void openTabVisualDeckStorage();
void openTabHome();
void openTabServer();
void openTabAccount();
void openTabDeckStorage();
void openTabReplays();

View File

@@ -345,9 +345,10 @@ int main(int argc, char *argv[])
// No primary instance → become server
qInfo() << "No existing instance found, becoming primary instance";
} else {
// Plain launch: try to start server, but do not connect to any existing
// Plain launch: if another instance is running, run independently
// instead of handing off and exiting.
if (!instance.tryRun(QStringList())) {
// Server already exists → just run independently
// Another instance is already running → just run independently
qInfo() << "Another instance exists, running independently";
} else {
qInfo() << "No existing instance found, starting server";

View File

@@ -8,53 +8,74 @@ bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
{
serverName = "CockatriceSingleInstance";
// Attempt to connect only if we have files to send
if (!filesToSend.isEmpty()) {
QLocalSocket socket;
socket.connectToServer(serverName);
if (socket.waitForConnected(200)) {
// Serialize payload with length prefix
QByteArray payload;
QDataStream out(&payload, QIODevice::WriteOnly);
out << filesToSend;
QByteArray message;
QDataStream msgStream(&message, QIODevice::WriteOnly);
msgStream << quint32(payload.size());
message.append(payload);
socket.write(message);
socket.flush();
socket.waitForBytesWritten(1000);
return false; // Sent successfully → exit
}
// Hand off to an already-running primary instance if one exists.
if (forwardToPrimary(filesToSend)) {
return false;
}
// Otherwise, start server
// No primary instance is currently reachable, so become the primary.
server = new QLocalServer(this);
connect(server, &QLocalServer::newConnection, this, &SingleInstanceManager::handleNewConnection);
if (!server->listen(serverName)) {
QLocalServer::removeServer(serverName);
server->listen(serverName);
if (server->listen(serverName)) {
return true;
}
return true; // This process is now primary server
// Another instance may have started while we were probing; hand off to it
// instead of stealing its socket.
if (forwardToPrimary(filesToSend)) {
return false;
}
// The socket is stale (left over by a crashed instance): remove it and
// retry. If that still fails, another instance just took the name.
QLocalServer::removeServer(serverName);
if (server->listen(serverName)) {
return true;
}
forwardToPrimary(filesToSend);
return false;
}
bool SingleInstanceManager::forwardToPrimary(const QStringList &filesToSend)
{
QLocalSocket socket;
socket.connectToServer(serverName);
if (!socket.waitForConnected(200)) {
return false;
}
// Serialize payload with length prefix
QByteArray payload;
QDataStream out(&payload, QIODevice::WriteOnly);
out << filesToSend;
QByteArray message;
QDataStream msgStream(&message, QIODevice::WriteOnly);
msgStream << quint32(payload.size());
message.append(payload);
socket.write(message);
socket.flush();
socket.waitForBytesWritten(1000);
return true;
}
void SingleInstanceManager::handleNewConnection()
{
QLocalSocket *socket = server->nextPendingConnection();
// Per-connection state
auto buffer = new QByteArray();
auto expectedSize = new quint32(0);
// Per-connection state. QSharedPointer keeps the buffers alive for as long
// as the connection handler is attached to the socket.
auto buffer = QSharedPointer<QByteArray>::create();
auto expectedSize = QSharedPointer<quint32>::create(0);
connect(socket, &QLocalSocket::readyRead, this, [this, socket, buffer, expectedSize]() {
buffer->append(socket->readAll());
QDataStream stream(buffer, QIODevice::ReadOnly);
QDataStream stream(buffer.data(), QIODevice::ReadOnly);
while (true) {
// Step 1: read size
@@ -90,4 +111,4 @@ void SingleInstanceManager::handleNewConnection()
});
connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
}
}

View File

@@ -12,6 +12,8 @@ class SingleInstanceManager : public QObject
public:
explicit SingleInstanceManager(QObject *parent = nullptr);
// Returns true if this process became the primary instance, false if
// another instance is already running (and received our files).
bool tryRun(const QStringList &initialFiles);
signals:
@@ -21,8 +23,10 @@ private slots:
void handleNewConnection();
private:
bool forwardToPrimary(const QStringList &filesToSend);
QString serverName;
QLocalServer *server = nullptr;
};
#endif // COCKATRICE_SINGLE_INSTANCE_MANAGER_H
#endif // COCKATRICE_SINGLE_INSTANCE_MANAGER_H