From a9077aa65b7679c35ecc43b8b59aca8224a08fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Mon, 3 Aug 2026 14:30:45 +0200 Subject: [PATCH] Move impl to cpp, fix lifetime issues. Took 11 minutes Took 2 minutes --- cockatrice/CMakeLists.txt | 9 +- cockatrice/cockatrice.desktop | 2 +- cockatrice/src/interface/intents/intent.cpp | 31 +++++++ cockatrice/src/interface/intents/intent.h | 27 +----- .../intents/intent_connect_to_server.cpp | 34 ++++++++ .../intents/intent_connect_to_server.h | 38 ++------- .../intents/intent_disconnect_from_server.cpp | 28 +++++++ .../intents/intent_disconnect_from_server.h | 35 ++------ .../intents/intent_join_server_game.cpp | 77 +++++++++++++++++ .../intents/intent_join_server_game.h | 56 ++++--------- .../intents/intent_join_server_room.cpp | 54 ++++++++++++ .../intents/intent_join_server_room.h | 42 ++-------- .../src/interface/intents/intent_login.cpp | 32 +++++++ .../src/interface/intents/intent_login.h | 37 +-------- .../intents/intent_open_local_deck.cpp | 31 +++++++ .../intents/intent_open_local_deck.h | 35 ++------ .../intents/intent_wait_for_database_load.cpp | 18 ++++ .../intents/intent_wait_for_database_load.h | 19 +---- .../src/interface/intents/url_parser.cpp | 15 ++-- .../widgets/server/game_selector.cpp | 1 - .../src/interface/widgets/tabs/tab_room.cpp | 1 + .../src/interface/widgets/tabs/tab_room.h | 1 + .../interface/widgets/tabs/tab_supervisor.cpp | 4 + .../interface/widgets/tabs/tab_supervisor.h | 2 +- cockatrice/src/main.cpp | 5 +- cockatrice/src/single_instance_manager.cpp | 83 ++++++++++++------- cockatrice/src/single_instance_manager.h | 6 +- 27 files changed, 442 insertions(+), 281 deletions(-) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index e56455592..6f0495ec6 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -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 ) diff --git a/cockatrice/cockatrice.desktop b/cockatrice/cockatrice.desktop index 65e77e9f9..ef952e4dd 100644 --- a/cockatrice/cockatrice.desktop +++ b/cockatrice/cockatrice.desktop @@ -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; diff --git a/cockatrice/src/interface/intents/intent.cpp b/cockatrice/src/interface/intents/intent.cpp index 916af1649..199812a43 100644 --- a/cockatrice/src/interface/intents/intent.cpp +++ b/cockatrice/src/interface/intents/intent.cpp @@ -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(); +} diff --git a/cockatrice/src/interface/intents/intent.h b/cockatrice/src/interface/intents/intent.h index 651946a14..731e6de75 100644 --- a/cockatrice/src/interface/intents/intent.h +++ b/cockatrice/src/interface/intents/intent.h @@ -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 diff --git a/cockatrice/src/interface/intents/intent_connect_to_server.cpp b/cockatrice/src/interface/intents/intent_connect_to_server.cpp index 912afa09c..927079d5d 100644 --- a/cockatrice/src/interface/intents/intent_connect_to_server.cpp +++ b/cockatrice/src/interface/intents/intent_connect_to_server.cpp @@ -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 &) { + emit failed(reason); + }); +} + +void IntentConnectToServer::onPreconditionNotSatisfied() +{ + runDependency(new IntentDisconnectFromServer(remoteClient)); +} + +void IntentConnectToServer::onStatusChanged(ClientStatus status) +{ + if (status == ClientStatus::StatusLoggedIn) { + emit finished(); + } +} diff --git a/cockatrice/src/interface/intents/intent_connect_to_server.h b/cockatrice/src/interface/intents/intent_connect_to_server.h index 291f68849..86490a765 100644 --- a/cockatrice/src/interface/intents/intent_connect_to_server.h +++ b/cockatrice/src/interface/intents/intent_connect_to_server.h @@ -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 - 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 diff --git a/cockatrice/src/interface/intents/intent_disconnect_from_server.cpp b/cockatrice/src/interface/intents/intent_disconnect_from_server.cpp index 36f78cede..fd53dccc5 100644 --- a/cockatrice/src/interface/intents/intent_disconnect_from_server.cpp +++ b/cockatrice/src/interface/intents/intent_disconnect_from_server.cpp @@ -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(); + } +} diff --git a/cockatrice/src/interface/intents/intent_disconnect_from_server.h b/cockatrice/src/interface/intents/intent_disconnect_from_server.h index b11452639..6e1dfd0c1 100644 --- a/cockatrice/src/interface/intents/intent_disconnect_from_server.h +++ b/cockatrice/src/interface/intents/intent_disconnect_from_server.h @@ -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 diff --git a/cockatrice/src/interface/intents/intent_join_server_game.cpp b/cockatrice/src/interface/intents/intent_join_server_game.cpp index 853307eb5..775abe3fe 100644 --- a/cockatrice/src/interface/intents/intent_join_server_game.cpp +++ b/cockatrice/src/interface/intents/intent_join_server_game.cpp @@ -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 + +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)); + } + }); +} diff --git a/cockatrice/src/interface/intents/intent_join_server_game.h b/cockatrice/src/interface/intents/intent_join_server_game.h index 79bbfe7f9..cd55f8323 100644 --- a/cockatrice/src/interface/intents/intent_join_server_game.h +++ b/cockatrice/src/interface/intents/intent_join_server_game.h @@ -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 + +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 context; + bool joined = false; }; #endif // COCKATRICE_INTENT_JOIN_SERVER_GAME_H diff --git a/cockatrice/src/interface/intents/intent_join_server_room.cpp b/cockatrice/src/interface/intents/intent_join_server_room.cpp index 89dffc0b3..c88dd5871 100644 --- a/cockatrice/src/interface/intents/intent_join_server_room.cpp +++ b/cockatrice/src/interface/intents/intent_join_server_room.cpp @@ -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)); +} diff --git a/cockatrice/src/interface/intents/intent_join_server_room.h b/cockatrice/src/interface/intents/intent_join_server_room.h index cd48c1df9..4a5599896 100644 --- a/cockatrice/src/interface/intents/intent_join_server_room.h +++ b/cockatrice/src/interface/intents/intent_join_server_room.h @@ -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; diff --git a/cockatrice/src/interface/intents/intent_login.cpp b/cockatrice/src/interface/intents/intent_login.cpp index b788872c3..010f3eaf2 100644 --- a/cockatrice/src/interface/intents/intent_login.cpp +++ b/cockatrice/src/interface/intents/intent_login.cpp @@ -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")); +} diff --git a/cockatrice/src/interface/intents/intent_login.h b/cockatrice/src/interface/intents/intent_login.h index d7df3df7a..c7fec92b7 100644 --- a/cockatrice/src/interface/intents/intent_login.h +++ b/cockatrice/src/interface/intents/intent_login.h @@ -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; }; diff --git a/cockatrice/src/interface/intents/intent_open_local_deck.cpp b/cockatrice/src/interface/intents/intent_open_local_deck.cpp index c4a0c81b8..ccafd434c 100644 --- a/cockatrice/src/interface/intents/intent_open_local_deck.cpp +++ b/cockatrice/src/interface/intents/intent_open_local_deck.cpp @@ -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 + +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 deckOpt = DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true); + if (deckOpt) { + tabSupervisor->openDeckInNewTab(deckOpt.value()); + } + emit finished(); +} + +void IntentOpenLocalDeck::onPreconditionNotSatisfied() +{ + runDependency(new IntentWaitForDatabaseLoad); +} diff --git a/cockatrice/src/interface/intents/intent_open_local_deck.h b/cockatrice/src/interface/intents/intent_open_local_deck.h index 38fed6e80..97f875e39 100644 --- a/cockatrice/src/interface/intents/intent_open_local_deck.h +++ b/cockatrice/src/interface/intents/intent_open_local_deck.h @@ -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 + +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 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; diff --git a/cockatrice/src/interface/intents/intent_wait_for_database_load.cpp b/cockatrice/src/interface/intents/intent_wait_for_database_load.cpp index dfdeab8b1..62c7f346b 100644 --- a/cockatrice/src/interface/intents/intent_wait_for_database_load.cpp +++ b/cockatrice/src/interface/intents/intent_wait_for_database_load.cpp @@ -1 +1,19 @@ #include "intent_wait_for_database_load.h" + +#include + +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(); }); +} diff --git a/cockatrice/src/interface/intents/intent_wait_for_database_load.h b/cockatrice/src/interface/intents/intent_wait_for_database_load.h index 51f4712a8..72f4a1ffc 100644 --- a/cockatrice/src/interface/intents/intent_wait_for_database_load.h +++ b/cockatrice/src/interface/intents/intent_wait_for_database_load.h @@ -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 diff --git a/cockatrice/src/interface/intents/url_parser.cpp b/cockatrice/src/interface/intents/url_parser.cpp index 6fa55b48a..5732df49d 100644 --- a/cockatrice/src/interface/intents/url_parser.cpp +++ b/cockatrice/src/interface/intents/url_parser.cpp @@ -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 @@ -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(); } diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index 142c5ac35..30690dc82 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -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 &gameTypes = gameListModel->getGameTypes().value(gameInfo.room_id()); - qWarning() << "Game Id: " << gameInfo.game_id(); DlgCreateGame dlg(gameInfo, gameTypes, this); dlg.exec(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp index 5cf400099..899f38ec2 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp @@ -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) diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.h b/cockatrice/src/interface/widgets/tabs/tab_room.h index dc62e0fad..2881c25f4 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.h +++ b/cockatrice/src/interface/widgets/tabs/tab_room.h @@ -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); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index c9478ee0b..3f30ba8be 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 87447d99c..e6c009fda 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -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(); diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 2289eb07e..65f1ae96f 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -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"; diff --git a/cockatrice/src/single_instance_manager.cpp b/cockatrice/src/single_instance_manager.cpp index 54d2ab588..3aa0f1a12 100644 --- a/cockatrice/src/single_instance_manager.cpp +++ b/cockatrice/src/single_instance_manager.cpp @@ -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::create(); + auto expectedSize = QSharedPointer::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); -} \ No newline at end of file +} diff --git a/cockatrice/src/single_instance_manager.h b/cockatrice/src/single_instance_manager.h index 9f0e54d32..55bff0e80 100644 --- a/cockatrice/src/single_instance_manager.h +++ b/cockatrice/src/single_instance_manager.h @@ -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 \ No newline at end of file +#endif // COCKATRICE_SINGLE_INSTANCE_MANAGER_H