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_loader_worker_work.cpp
src/interface/card_picture_loader/card_picture_to_load.cpp src/interface/card_picture_loader/card_picture_to_load.cpp
src/interface/intents/intent.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.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.cpp
src/interface/intents/intent_wait_for_database_load.h
src/interface/layouts/flow_layout.cpp src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.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/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.cpp
src/interface/widgets/utility/compact_push_button.h src/interface/widgets/utility/compact_push_button.h
src/single_instance_manager.cpp
src/single_instance_manager.h src/single_instance_manager.h
src/client/url_scheme_event_filter.h src/client/url_scheme_event_filter.h
src/interface/intents/intent_connect_to_server.cpp 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.cpp
src/interface/intents/intent_disconnect_from_server.h src/interface/intents/intent_disconnect_from_server.h
src/interface/intents/intent_join_server_game.cpp 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.cpp
src/interface/intents/intent_join_server_room.h 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.cpp
src/interface/intents/intent_login.h 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.cpp
src/interface/widgets/server/user/user_info_popup.h src/interface/widgets/server/user/user_info_popup.h
) )

View file

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

View file

@ -1 +1,32 @@
#include "intent.h" #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 Q_OBJECT
public: public:
explicit Intent(QObject *parent = nullptr) : QObject(parent) explicit Intent(QObject *parent = nullptr);
{ ~Intent() override;
}
virtual ~Intent() = default;
void execute() void execute();
{
if (checkPrecondition()) {
onPreconditionSatisfied();
} else {
onPreconditionNotSatisfied();
}
}
signals: signals:
void finished(); void finished();
@ -33,17 +24,7 @@ protected:
virtual void onPreconditionNotSatisfied() = 0; virtual void onPreconditionNotSatisfied() = 0;
// Helper to chain another intent // Helper to chain another intent
void runDependency(Intent *dependency) 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();
}
}; };
#endif // COCKATRICE_INTENT_H #endif // COCKATRICE_INTENT_H

View file

@ -1 +1,35 @@
#include "intent_connect_to_server.h" #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 "contexts/context_connect_to_server.h"
#include "intent.h" #include "intent.h"
#include "intent_disconnect_from_server.h"
#include "remote_client.h" #include "remote_client.h"
#include <QTimer>
class IntentConnectToServer : public Intent class IntentConnectToServer : public Intent
{ {
Q_OBJECT Q_OBJECT
public: public:
IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context) IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context);
: Intent(), remoteClient(_remoteClient), context(_context)
{
}
protected: protected:
bool checkPrecondition() const override bool checkPrecondition() const override;
{ void onPreconditionSatisfied() override;
return remoteClient->getStatus() == ClientStatus::StatusDisconnected; void onPreconditionNotSatisfied() override;
}
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));
}
private: private:
RemoteClient *remoteClient; RemoteClient *remoteClient;
ContextConnectToServer *context; ContextConnectToServer *context;
private slots: private slots:
void onStatusChanged(ClientStatus status) 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();
}
}
}; };
#endif // COCKATRICE_INTENT_CONNECT_TO_SERVER_H #endif // COCKATRICE_INTENT_CONNECT_TO_SERVER_H

View file

@ -1 +1,29 @@
#include "intent_disconnect_from_server.h" #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 #ifndef COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H
#define COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H #define COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H
#include "intent.h" #include "intent.h"
#include "remote_client.h" #include "remote_client.h"
@ -8,40 +9,18 @@ class IntentDisconnectFromServer : public Intent
Q_OBJECT Q_OBJECT
public: public:
IntentDisconnectFromServer(RemoteClient *_remoteClient) : Intent(), remoteClient(_remoteClient) IntentDisconnectFromServer(RemoteClient *_remoteClient);
{
}
protected: protected:
bool checkPrecondition() const override bool checkPrecondition() const override;
{ void onPreconditionSatisfied() override;
return remoteClient->getStatus() == ClientStatus::StatusDisconnected; void onPreconditionNotSatisfied() override;
}
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();
}
private: private:
RemoteClient *remoteClient; RemoteClient *remoteClient;
private slots: private slots:
void onStatusChanged(ClientStatus status) void onStatusChanged(ClientStatus status);
{
qWarning() << "Client Status changed: " << status;
if (status == ClientStatus::StatusDisconnected) {
qWarning() << "Client disconnected, finished";
emit finished();
}
}
}; };
#endif // COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H #endif // COCKATRICE_INTENT_DISCONNECT_FROM_SERVER_H

View file

@ -1 +1,78 @@
#include "intent_join_server_game.h" #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 #ifndef COCKATRICE_INTENT_JOIN_SERVER_GAME_H
#define 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_game.h"
#include "contexts/context_join_room.h"
#include "intent.h" #include "intent.h"
#include "intent_join_server_room.h"
#include "remote_client.h" #include "remote_client.h"
#include <QScopedPointer>
class TabRoom;
class TabSupervisor;
class IntentJoinServerGame : public Intent class IntentJoinServerGame : public Intent
{ {
Q_OBJECT Q_OBJECT
public: public:
IntentJoinServerGame(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinGame *_context) IntentJoinServerGame(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinGame *_context);
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
{
}
protected: protected:
bool checkPrecondition() const override bool checkPrecondition() const override;
{ void onPreconditionSatisfied() override;
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) { void onPreconditionNotSatisfied() override;
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));
}
private: private:
bool tryJoinGame(TabRoom *room);
void waitForGame(TabRoom *room);
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor;
RemoteClient *remoteClient; RemoteClient *remoteClient;
ContextJoinGame *context; QScopedPointer<ContextJoinGame> context;
bool joined = false;
}; };
#endif // COCKATRICE_INTENT_JOIN_SERVER_GAME_H #endif // COCKATRICE_INTENT_JOIN_SERVER_GAME_H

View file

@ -1 +1,55 @@
#include "intent_join_server_room.h" #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 #ifndef COCKATRICE_INTENT_JOIN_SERVER_ROOM_H
#define 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 "contexts/context_join_room.h"
#include "intent.h" #include "intent.h"
#include "intent_connect_to_server.h"
#include "intent_disconnect_from_server.h"
#include "remote_client.h" #include "remote_client.h"
class TabSupervisor;
class IntentJoinServerRoom : public Intent class IntentJoinServerRoom : public Intent
{ {
Q_OBJECT Q_OBJECT
public: public:
IntentJoinServerRoom(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinRoom *_context) IntentJoinServerRoom(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinRoom *_context);
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
{
}
protected: protected:
bool checkPrecondition() const override bool checkPrecondition() const override;
{ void onPreconditionSatisfied() override;
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) { void onPreconditionNotSatisfied() override;
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));
}
private: private:
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor;

View file

@ -1 +1,33 @@
#include "intent_login.h" #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 #ifndef COCKATRICE_INTENT_LOGIN_H
#define COCKATRICE_INTENT_LOGIN_H #define COCKATRICE_INTENT_LOGIN_H
#include "../../client/settings/cache_settings.h"
#include "contexts/context_connect_to_server.h" #include "contexts/context_connect_to_server.h"
#include "intent.h" #include "intent.h"
#include "remote_client.h"
class IntentGetLoginCredentials : public Intent class IntentGetLoginCredentials : public Intent
{ {
Q_OBJECT Q_OBJECT
public: public:
IntentGetLoginCredentials(RemoteClient *_remoteClient, ContextConnectToServer *_context) IntentGetLoginCredentials(ContextConnectToServer *_context);
: Intent(), remoteClient(_remoteClient), context(_context)
{
}
protected: protected:
bool checkPrecondition() const override bool checkPrecondition() const override;
{ void onPreconditionSatisfied() override;
ServersSettings &servers = SettingsCache::instance().servers(); void onPreconditionNotSatisfied() override;
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
{
}
private: private:
RemoteClient *remoteClient;
ContextConnectToServer *context; ContextConnectToServer *context;
}; };

View file

@ -1 +1,32 @@
#include "intent_open_local_deck.h" #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 #ifndef COCKATRICE_INTENT_OPEN_LOCAL_DECK_H
#define COCKATRICE_INTENT_OPEN_LOCAL_DECK_H #define COCKATRICE_INTENT_OPEN_LOCAL_DECK_H
#include "../widgets/tabs/tab_supervisor.h"
#include "intent.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 class IntentOpenLocalDeck : public Intent
{ {
Q_OBJECT Q_OBJECT
public: public:
IntentOpenLocalDeck(TabSupervisor *_tabSupervisor, const QString &_file) IntentOpenLocalDeck(TabSupervisor *_tabSupervisor, const QString &_file);
: Intent(), tabSupervisor(_tabSupervisor), file(_file)
{
}
protected: protected:
bool checkPrecondition() const override bool checkPrecondition() const override;
{ void onPreconditionSatisfied() override;
return CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok; void onPreconditionNotSatisfied() override;
}
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);
}
private: private:
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -345,9 +345,10 @@ int main(int argc, char *argv[])
// No primary instance → become server // No primary instance → become server
qInfo() << "No existing instance found, becoming primary instance"; qInfo() << "No existing instance found, becoming primary instance";
} else { } 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())) { if (!instance.tryRun(QStringList())) {
// Server already exists → just run independently // Another instance is already running → just run independently
qInfo() << "Another instance exists, running independently"; qInfo() << "Another instance exists, running independently";
} else { } else {
qInfo() << "No existing instance found, starting server"; qInfo() << "No existing instance found, starting server";

View file

@ -8,53 +8,74 @@ bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
{ {
serverName = "CockatriceSingleInstance"; serverName = "CockatriceSingleInstance";
// Attempt to connect only if we have files to send // Hand off to an already-running primary instance if one exists.
if (!filesToSend.isEmpty()) { if (forwardToPrimary(filesToSend)) {
QLocalSocket socket; return false;
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
}
} }
// Otherwise, start server // No primary instance is currently reachable, so become the primary.
server = new QLocalServer(this); server = new QLocalServer(this);
connect(server, &QLocalServer::newConnection, this, &SingleInstanceManager::handleNewConnection); connect(server, &QLocalServer::newConnection, this, &SingleInstanceManager::handleNewConnection);
if (!server->listen(serverName)) { if (server->listen(serverName)) {
QLocalServer::removeServer(serverName); return true;
server->listen(serverName);
} }
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() void SingleInstanceManager::handleNewConnection()
{ {
QLocalSocket *socket = server->nextPendingConnection(); QLocalSocket *socket = server->nextPendingConnection();
// Per-connection state // Per-connection state. QSharedPointer keeps the buffers alive for as long
auto buffer = new QByteArray(); // as the connection handler is attached to the socket.
auto expectedSize = new quint32(0); auto buffer = QSharedPointer<QByteArray>::create();
auto expectedSize = QSharedPointer<quint32>::create(0);
connect(socket, &QLocalSocket::readyRead, this, [this, socket, buffer, expectedSize]() { connect(socket, &QLocalSocket::readyRead, this, [this, socket, buffer, expectedSize]() {
buffer->append(socket->readAll()); buffer->append(socket->readAll());
QDataStream stream(buffer, QIODevice::ReadOnly); QDataStream stream(buffer.data(), QIODevice::ReadOnly);
while (true) { while (true) {
// Step 1: read size // Step 1: read size

View file

@ -12,6 +12,8 @@ class SingleInstanceManager : public QObject
public: public:
explicit SingleInstanceManager(QObject *parent = nullptr); 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); bool tryRun(const QStringList &initialFiles);
signals: signals:
@ -21,6 +23,8 @@ private slots:
void handleNewConnection(); void handleNewConnection();
private: private:
bool forwardToPrimary(const QStringList &filesToSend);
QString serverName; QString serverName;
QLocalServer *server = nullptr; QLocalServer *server = nullptr;
}; };