From 981db47f9e8d6771d75e1a0c2181195e5ac295f5 Mon Sep 17 00:00:00 2001 From: Max-Wilhelm Bruker Date: Sun, 20 May 2012 18:47:28 +0200 Subject: [PATCH] initial commit for connection pools --- common/CMakeLists.txt | 2 + common/server.cpp | 31 +- common/server.h | 24 +- common/server_database_interface.cpp | 7 + common/server_database_interface.h | 36 ++ common/server_game.cpp | 7 +- common/server_game.h | 4 +- common/server_protocolhandler.cpp | 24 +- common/server_protocolhandler.h | 6 +- common/server_room.cpp | 4 +- common/server_room.h | 3 +- servatrice/CMakeLists.txt | 5 +- servatrice/src/servatrice.cpp | 583 ++---------------- servatrice/src/servatrice.h | 91 ++- servatrice/src/servatrice_connection_pool.h | 29 + .../src/servatrice_database_interface.cpp | 517 ++++++++++++++++ .../src/servatrice_database_interface.h | 49 ++ servatrice/src/serversocketinterface.cpp | 55 +- servatrice/src/serversocketinterface.h | 3 +- servatrice/src/serversocketthread.cpp | 27 - servatrice/src/serversocketthread.h | 22 - 21 files changed, 829 insertions(+), 700 deletions(-) create mode 100644 common/server_database_interface.cpp create mode 100644 common/server_database_interface.h create mode 100644 servatrice/src/servatrice_connection_pool.h create mode 100644 servatrice/src/servatrice_database_interface.cpp create mode 100644 servatrice/src/servatrice_database_interface.h delete mode 100644 servatrice/src/serversocketthread.cpp delete mode 100644 servatrice/src/serversocketthread.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index b01d2afe..83f185e1 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -13,6 +13,7 @@ SET(common_SOURCES server_card.cpp server_cardzone.cpp server_counter.cpp + server_database_interface.cpp server_game.cpp server_player.cpp server_protocolhandler.cpp @@ -30,6 +31,7 @@ SET(common_HEADERS server.h server_arrowtarget.h server_card.h + server_database_interface.h server_game.h server_player.h server_protocolhandler.h diff --git a/common/server.cpp b/common/server.cpp index f60cde20..a318e24d 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -25,6 +25,7 @@ #include "server_protocolhandler.h" #include "server_remoteuserinterface.h" #include "server_metatypes.h" +#include "server_database_interface.h" #include "pb/event_user_joined.pb.h" #include "pb/event_user_left.pb.h" #include "pb/event_list_rooms.pb.h" @@ -34,7 +35,7 @@ #include Server::Server(QObject *parent) - : QObject(parent), nextGameId(0), nextReplayId(0), clientsLock(QReadWriteLock::Recursive) + : QObject(parent), databaseInterface(0), clientsLock(QReadWriteLock::Recursive) { qRegisterMetaType("ServerInfo_Game"); qRegisterMetaType("ServerInfo_Room"); @@ -67,27 +68,33 @@ void Server::prepareDestroy() roomsLock.unlock(); } -AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reasonStr, int &secondsLeft) +void Server::setDatabaseInterface(Server_DatabaseInterface *_databaseInterface) +{ + databaseInterface = _databaseInterface; + connect(this, SIGNAL(endSession(qint64)), databaseInterface, SLOT(endSession(qint64))); +} + +AuthenticationResult Server::loginUser(Server_DatabaseInterface *sessionDatabaseInterface, Server_ProtocolHandler *session, QString &name, const QString &password, QString &reasonStr, int &secondsLeft) { if (name.size() > 35) name = name.left(35); QWriteLocker locker(&clientsLock); - AuthenticationResult authState = checkUserPassword(session, name, password, reasonStr, secondsLeft); + AuthenticationResult authState = sessionDatabaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); if ((authState == NotLoggedIn) || (authState == UserIsBanned)) return authState; - ServerInfo_User data = getUserData(name, true); + ServerInfo_User data = sessionDatabaseInterface->getUserData(name, true); data.set_address(session->getAddress().toStdString()); name = QString::fromStdString(data.name()); // Compensate for case indifference - lockSessionTables(); + sessionDatabaseInterface->lockSessionTables(); if (authState == PasswordRight) { - if (users.contains(name) || userSessionExists(name)) { + if (users.contains(name) || sessionDatabaseInterface->userSessionExists(name)) { qDebug("Login denied: would overwrite old session"); - unlockSessionTables(); + sessionDatabaseInterface->unlockSessionTables(); return WouldOverwriteOldSession; } } else if (authState == UnknownUser) { @@ -95,7 +102,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString // don't interfere with registered user names though. QString tempName = name; int i = 0; - while (users.contains(tempName) || userExists(tempName) || userSessionExists(tempName)) + while (users.contains(tempName) || sessionDatabaseInterface->userExists(tempName) || sessionDatabaseInterface->userSessionExists(tempName)) tempName = name + "_" + QString::number(++i); name = tempName; data.set_name(name.toStdString()); @@ -104,8 +111,8 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString users.insert(name, session); qDebug() << "Server::loginUser: name=" << name; - data.set_session_id(startSession(name, session->getAddress())); - unlockSessionTables(); + data.set_session_id(sessionDatabaseInterface->startSession(name, session->getAddress())); + sessionDatabaseInterface->unlockSessionTables(); usersBySessionId.insert(data.session_id(), session); @@ -176,7 +183,7 @@ void Server::removeClient(Server_ProtocolHandler *client) if (data->has_session_id()) { const qint64 sessionId = data->session_id(); usersBySessionId.remove(sessionId); - endSession(sessionId); + emit endSession(sessionId); qDebug() << "closed session id:" << sessionId; } } @@ -325,7 +332,7 @@ void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cm } ResponseContainer responseContainer(cmdId); - Response::ResponseCode responseCode = room->processJoinGameCommand(cmd, responseContainer, userInterface); + Response::ResponseCode responseCode = room->processJoinGameCommand(cmd, responseContainer, userInterface, databaseInterface); userInterface->sendResponseContainer(responseContainer, responseCode); } catch (Response::ResponseCode code) { Response response; diff --git a/common/server.h b/common/server.h index c69bc173..92440561 100644 --- a/common/server.h +++ b/common/server.h @@ -10,6 +10,7 @@ #include "pb/serverinfo_user.pb.h" #include "server_player_reference.h" +class Server_DatabaseInterface; class Server_Game; class Server_Room; class Server_ProtocolHandler; @@ -35,16 +36,15 @@ signals: void pingClockTimeout(); void sigSendIslMessage(const IslMessage &message, int serverId); void logDebugMessage(QString message, void *caller); + void endSession(qint64 sessionId); private slots: void broadcastRoomUpdate(const ServerInfo_Room &roomInfo, bool sendToIsl = false); public: mutable QReadWriteLock clientsLock, roomsLock; // locking order: roomsLock before clientsLock Server(QObject *parent = 0); ~Server(); - AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); + AuthenticationResult loginUser(Server_DatabaseInterface *sessionDatabaseInterface, Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); const QMap &getRooms() { return rooms; } - virtual int getNextGameId() { return nextGameId++; } - virtual int getNextReplayId() { return nextReplayId++; } const QMap &getUsers() const { return users; } const QMap &getUsersBySessionId() const { return usersBySessionId; } @@ -61,11 +61,6 @@ public: virtual int getMaxGamesPerUser() const { return 0; } virtual bool getThreaded() const { return false; } - virtual QMap getBuddyList(const QString &name) { return QMap(); } - virtual QMap getIgnoreList(const QString &name) { return QMap(); } - virtual bool isInBuddyList(const QString &whoseList, const QString &who) { return false; } - virtual bool isInIgnoreList(const QString &whoseList, const QString &who) { return false; } - virtual void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replays) { } virtual DeckList *getDeckFromDatabase(int deckId, const QString &userName) { return 0; } @@ -101,6 +96,8 @@ protected slots: virtual void doSendIslMessage(const IslMessage &msg, int serverId) { } protected: void prepareDestroy(); + void setDatabaseInterface(Server_DatabaseInterface *_databaseInterface); + Server_DatabaseInterface *databaseInterface; QList clients; QMap usersBySessionId; QMap users; @@ -108,20 +105,9 @@ protected: QMap externalUsers; QMap rooms; - virtual qint64 startSession(const QString &userName, const QString &address) { return 0; } - virtual void endSession(qint64 sessionId) { } - virtual bool userExists(const QString &user) { return false; } - virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reason, int &secondsLeft) { return UnknownUser; } - virtual ServerInfo_User getUserData(const QString &name, bool withId = false) = 0; int getUsersCount() const; int getGamesCount() const; - int nextGameId, nextReplayId; void addRoom(Server_Room *newRoom); - - virtual void clearSessionTables() { } - virtual void lockSessionTables() { } - virtual void unlockSessionTables() { } - virtual bool userSessionExists(const QString &userName) { return false; } }; #endif diff --git a/common/server_database_interface.cpp b/common/server_database_interface.cpp new file mode 100644 index 00000000..e8388198 --- /dev/null +++ b/common/server_database_interface.cpp @@ -0,0 +1,7 @@ +#include "server_database_interface.h" + +Server_DatabaseInterface::Server_DatabaseInterface() + : nextGameId(0), + nextReplayId(0) +{ +} diff --git a/common/server_database_interface.h b/common/server_database_interface.h new file mode 100644 index 00000000..36709b0d --- /dev/null +++ b/common/server_database_interface.h @@ -0,0 +1,36 @@ +#ifndef SERVER_DATABASE_INTERFACE_H +#define SERVER_DATABASE_INTERFACE_H + +#include + +#include "server.h" + +class Server_DatabaseInterface : public QObject { + Q_OBJECT +private: + int nextGameId, nextReplayId; +public: + Server_DatabaseInterface(); + + virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; + virtual bool userExists(const QString &user) { return false; } + virtual QMap getBuddyList(const QString &name) { return QMap(); } + virtual QMap getIgnoreList(const QString &name) { return QMap(); } + virtual bool isInBuddyList(const QString &whoseList, const QString &who) { return false; } + virtual bool isInIgnoreList(const QString &whoseList, const QString &who) { return false; } + virtual ServerInfo_User getUserData(const QString &name, bool withId = false) = 0; + + virtual qint64 startSession(const QString &userName, const QString &address) { return 0; } +public slots: + virtual void endSession(qint64 sessionId) { } +public: + virtual int getNextGameId() { return nextGameId++; } + virtual int getNextReplayId() { return nextReplayId++; } + + virtual void clearSessionTables() { } + virtual void lockSessionTables() { } + virtual void unlockSessionTables() { } + virtual bool userSessionExists(const QString &userName) { return false; } +}; + +#endif diff --git a/common/server_game.cpp b/common/server_game.cpp index 5d8f7de5..669cdf60 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -25,6 +25,7 @@ #include "server_arrow.h" #include "server_card.h" #include "server_cardzone.h" +#include "server_database_interface.h" #include "decklist.h" #include "pb/context_connection_state_changed.pb.h" #include "pb/context_ping_changed.pb.h" @@ -339,7 +340,7 @@ void Server_Game::stopGameIfFinished() emit gameInfoChanged(gameInfo); } -Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions) +Response::ResponseCode Server_Game::checkJoin(Server_DatabaseInterface *databaseInterface, ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions) { { QMapIterator playerIterator(players); @@ -353,9 +354,9 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, const QStri if (!(user->user_level() & ServerInfo_User::IsRegistered) && onlyRegistered) return Response::RespUserLevelTooLow; if (onlyBuddies) - if (!room->getServer()->isInBuddyList(QString::fromStdString(creatorInfo->name()), QString::fromStdString(user->name()))) + if (!databaseInterface->isInBuddyList(QString::fromStdString(creatorInfo->name()), QString::fromStdString(user->name()))) return Response::RespOnlyBuddies; - if (room->getServer()->isInIgnoreList(QString::fromStdString(creatorInfo->name()), QString::fromStdString(user->name()))) + if (databaseInterface->isInIgnoreList(QString::fromStdString(creatorInfo->name()), QString::fromStdString(user->name()))) return Response::RespInIgnoreList; if (spectator) { if (!spectatorsAllowed) diff --git a/common/server_game.h b/common/server_game.h index 31842024..a179581c 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -28,7 +28,6 @@ #include #include "server_response_containers.h" #include "pb/response.pb.h" -//#include "pb/serverinfo_player.pb.h" #include "pb/serverinfo_game.pb.h" class QTimer; @@ -40,6 +39,7 @@ class ServerInfo_User; class ServerInfo_Player; class ServerInfo_Game; class Server_AbstractUserInterface; +class Server_DatabaseInterface; class Event_GameStateChanged; class Server_Game : public QObject { @@ -99,7 +99,7 @@ public: bool getSpectatorsNeedPassword() const { return spectatorsNeedPassword; } bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } bool getSpectatorsSeeEverything() const { return spectatorsSeeEverything; } - Response::ResponseCode checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions); + Response::ResponseCode checkJoin(Server_DatabaseInterface *databaseInterface, ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions); bool containsUser(const QString &userName) const; void addPlayer(Server_AbstractUserInterface *userInterface, ResponseContainer &rc, bool spectator, bool broadcastUpdate = true); void removePlayer(Server_Player *player); diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 5c5efb05..c55c8742 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -1,6 +1,7 @@ #include #include #include "server_protocolhandler.h" +#include "server_database_interface.h" #include "server_room.h" #include "server_game.h" #include "server_player.h" @@ -19,8 +20,15 @@ #include "pb/event_room_say.pb.h" #include -Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, QObject *parent) - : QObject(parent), Server_AbstractUserInterface(_server), authState(NotLoggedIn), acceptsUserListChanges(false), acceptsRoomListChanges(false), timeRunning(0), lastDataReceived(0) +Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent) + : QObject(parent), + Server_AbstractUserInterface(_server), + databaseInterface(_databaseInterface), + authState(NotLoggedIn), + acceptsUserListChanges(false), + acceptsRoomListChanges(false), + timeRunning(0), + lastDataReceived(0) { connect(server, SIGNAL(pingClockTimeout()), this, SLOT(pingClockTimeout())); } @@ -309,7 +317,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd return Response::RespContextError; QString reasonStr; int banSecondsLeft = 0; - AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft); + AuthenticationResult res = server->loginUser(databaseInterface, this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft); switch (res) { case UserIsBanned: { Response_Login *re = new Response_Login; @@ -333,11 +341,11 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd re->mutable_user_info()->CopyFrom(copyUserInfo(true)); if (authState == PasswordRight) { - QMapIterator buddyIterator(server->getBuddyList(userName)); + QMapIterator buddyIterator(databaseInterface->getBuddyList(userName)); while (buddyIterator.hasNext()) re->add_buddy_list()->CopyFrom(buddyIterator.next().value()); - QMapIterator ignoreIterator(server->getIgnoreList(userName)); + QMapIterator ignoreIterator(databaseInterface->getIgnoreList(userName)); while (ignoreIterator.hasNext()) re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); } @@ -362,7 +370,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message if (!userInterface) return Response::RespNameNotFound; } - if (server->isInIgnoreList(receiver, QString::fromStdString(userInfo->name()))) + if (databaseInterface->isInIgnoreList(receiver, QString::fromStdString(userInfo->name()))) return Response::RespInIgnoreList; Event_UserMessage event; @@ -488,9 +496,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUs QMapIterator extIterator = server->getExternalUsers(); while (extIterator.hasNext()) re->add_user_list()->CopyFrom(extIterator.next().value()->copyUserInfo(false)); - server->clientsLock.unlock(); acceptsUserListChanges = true; + server->clientsLock.unlock(); rc.setResponseExtension(re); return Response::RespOk; @@ -561,5 +569,5 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinGame(const Command_JoinGam if (authState == NotLoggedIn) return Response::RespLoginNeeded; - return room->processJoinGameCommand(cmd, rc, this); + return room->processJoinGameCommand(cmd, rc, this, databaseInterface); } diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index 95867a6d..bcf26f65 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -8,6 +8,7 @@ #include "pb/response.pb.h" #include "pb/server_message.pb.h" +class Server_DatabaseInterface; class Server_Player; class ServerInfo_User; class Server_Room; @@ -43,11 +44,11 @@ class Server_ProtocolHandler : public QObject, public Server_AbstractUserInterfa protected: QMap rooms; + Server_DatabaseInterface *databaseInterface; AuthenticationResult authState; bool acceptsUserListChanges; bool acceptsRoomListChanges; private: - QList messageSizeOverTime, messageCountOverTime; int timeRunning, lastDataReceived; QTimer *pingClock; @@ -82,12 +83,13 @@ signals: public slots: void prepareDestroy(); public: - Server_ProtocolHandler(Server *_server, QObject *parent = 0); + Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent = 0); ~Server_ProtocolHandler(); bool getAcceptsUserListChanges() const { return acceptsUserListChanges; } bool getAcceptsRoomListChanges() const { return acceptsRoomListChanges; } virtual QString getAddress() const = 0; + Server_DatabaseInterface *getDatabaseInterface() const { return databaseInterface; } int getLastCommandTime() const { return timeRunning - lastDataReceived; } void processCommandContainer(const CommandContainer &cont); diff --git a/common/server_room.cpp b/common/server_room.cpp index 85f0a9c7..4114d30a 100644 --- a/common/server_room.cpp +++ b/common/server_room.cpp @@ -169,7 +169,7 @@ void Server_Room::updateExternalGameList(const ServerInfo_Game &gameInfo) emit roomInfoChanged(getInfo(roomInfo, false, false, true)); } -Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGame &cmd, ResponseContainer &rc, Server_AbstractUserInterface *userInterface) +Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGame &cmd, ResponseContainer &rc, Server_AbstractUserInterface *userInterface, Server_DatabaseInterface *databaseInterface) { // This function is called from the Server thread and from the S_PH thread. // server->roomsMutex is always locked. @@ -191,7 +191,7 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam QMutexLocker gameLocker(&g->gameMutex); - Response::ResponseCode result = g->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(), cmd.override_restrictions()); + Response::ResponseCode result = g->checkJoin(databaseInterface, userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(), cmd.override_restrictions()); if (result == Response::RespOk) g->addPlayer(userInterface, rc, cmd.spectator()); diff --git a/common/server_room.h b/common/server_room.h index 1a2801e0..a62b0f32 100644 --- a/common/server_room.h +++ b/common/server_room.h @@ -10,6 +10,7 @@ #include "serverinfo_user_container.h" #include "pb/response.pb.h" +class Server_DatabaseInterface; class Server_ProtocolHandler; class RoomEvent; class ServerInfo_User; @@ -66,7 +67,7 @@ public: const QMap &getExternalUsers() const { return externalUsers; } void updateExternalGameList(const ServerInfo_Game &gameInfo); - Response::ResponseCode processJoinGameCommand(const Command_JoinGame &cmd, ResponseContainer &rc, Server_AbstractUserInterface *userInterface); + Response::ResponseCode processJoinGameCommand(const Command_JoinGame &cmd, ResponseContainer &rc, Server_AbstractUserInterface *userInterface, Server_DatabaseInterface *databaseInterface); void say(const QString &userName, const QString &s, bool sendToIsl = true); diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index dbe795a1..11f9ee7a 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -6,17 +6,18 @@ SET(servatrice_SOURCES src/main.cpp src/passwordhasher.cpp src/servatrice.cpp + src/servatrice_database_interface.cpp src/server_logger.cpp src/serversocketinterface.cpp - src/serversocketthread.cpp src/isl_interface.cpp ${CMAKE_CURRENT_BINARY_DIR}/version_string.cpp ) SET(servatrice_HEADERS src/servatrice.h + src/servatrice_connection_pool.h + src/servatrice_database_interface.h src/server_logger.h src/serversocketinterface.h - src/serversocketthread.h src/isl_interface.h ) diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 34fcf53f..cf4f5b01 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -22,13 +22,13 @@ #include #include #include "servatrice.h" +#include "servatrice_database_interface.h" +#include "servatrice_connection_pool.h" #include "server_room.h" #include "serversocketinterface.h" -#include "serversocketthread.h" #include "isl_interface.h" #include "server_logger.h" #include "main.h" -#include "passwordhasher.h" #include "decklist.h" #include "pb/game_replay.pb.h" #include "pb/event_replay_added.pb.h" @@ -36,19 +36,45 @@ #include "pb/event_server_shutdown.pb.h" #include "pb/event_connection_closed.pb.h" +Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, bool _threaded, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent) + : QTcpServer(parent), + server(_server), + threaded(_threaded) +{ + for (int i = 0; i < _numberPools; ++i) { + Servatrice_DatabaseInterface *newDatabaseInterface = new Servatrice_DatabaseInterface(i, server); + Servatrice_ConnectionPool *newPool = new Servatrice_ConnectionPool(newDatabaseInterface); + + // --- + newDatabaseInterface->initDatabase(_sqlDatabase); + // --- + + connectionPools.append(newPool); + } +} + void Servatrice_GameServer::incomingConnection(int socketDescriptor) { - if (threaded) { - ServerSocketThread *sst = new ServerSocketThread(socketDescriptor, server, this); - sst->start(); - } else { - QTcpSocket *socket = new QTcpSocket; - ServerSocketInterface *ssi = new ServerSocketInterface(server, socket); - socket->setSocketDescriptor(socketDescriptor); - socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); - logger->logMessage(QString("incoming connection: %1").arg(socket->peerAddress().toString()), ssi); - ssi->initSessionDeprecated(); + // Determine connection pool with smallest client count + int minClientCount = -1; + int poolIndex = -1; + for (int i = 0; i < connectionPools.size(); ++i) { + const int clientCount = connectionPools[i]->getClientCount(); + if ((poolIndex == -1) || (clientCount < minClientCount)) { + minClientCount = clientCount; + poolIndex = i; + } } + Servatrice_ConnectionPool *pool = connectionPools[poolIndex]; + + QTcpSocket *socket = new QTcpSocket; + ServerSocketInterface *ssi = new ServerSocketInterface(server, pool->getDatabaseInterface(), socket); + pool->addClient(); + connect(ssi, SIGNAL(destroyed()), pool, SLOT(removeClient())); + socket->setSocketDescriptor(socketDescriptor); + socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); + logger->logMessage(QString("[pool %1] Incoming connection: %2").arg(poolIndex).arg(socket->peerAddress().toString()), ssi); + ssi->initSessionDeprecated(); } void Servatrice_IslServer::incomingConnection(int socketDescriptor) @@ -65,7 +91,7 @@ void Servatrice_IslServer::incomingConnection(int socketDescriptor) } Servatrice::Servatrice(QSettings *_settings, QObject *parent) - : Server(parent), dbMutex(QMutex::Recursive), settings(_settings), uptime(0), shutdownTimer(0) + : Server(parent), settings(_settings), uptime(0), shutdownTimer(0) { } @@ -95,16 +121,18 @@ bool Servatrice::initServer() if (databaseType != DatabaseNone) { openDatabase(); updateServerList(); - clearSessionTables(); + + qDebug() << "Clearing previous sessions..."; + databaseInterface->clearSessionTables(); } const QString roomMethod = settings->value("rooms/method").toString(); if (roomMethod == "sql") { - QSqlQuery query; + QSqlQuery query(sqlDatabase); query.prepare("select id, name, descr, auto_join, join_message from " + dbPrefix + "_rooms order by id asc"); execSqlQuery(query); while (query.next()) { - QSqlQuery query2; + QSqlQuery query2(sqlDatabase); query2.prepare("select name from " + dbPrefix + "_rooms_gametypes where id_room = :id_room"); query2.bindValue(":id_room", query.value(0).toInt()); execSqlQuery(query2); @@ -223,7 +251,8 @@ bool Servatrice::initServer() } threaded = settings->value("server/threaded", false).toInt(); - gameServer = new Servatrice_GameServer(this, threaded, this); + const int numberPools = settings->value("server/number_pools", 1).toInt(); + gameServer = new Servatrice_GameServer(this, threaded, numberPools, sqlDatabase, this); const int gamePort = settings->value("server/port", 4747).toInt(); qDebug() << "Starting server on port" << gamePort; if (gameServer->listen(QHostAddress::Any, gamePort)) @@ -237,23 +266,22 @@ bool Servatrice::initServer() bool Servatrice::openDatabase() { - if (!QSqlDatabase::connectionNames().isEmpty()) - QSqlDatabase::removeDatabase(QSqlDatabase::database().connectionNames().at(0)); + if (!sqlDatabase.isValid()) { + settings->beginGroup("database"); + sqlDatabase = QSqlDatabase::addDatabase("QMYSQL"); + sqlDatabase.setHostName(settings->value("hostname").toString()); + sqlDatabase.setDatabaseName(settings->value("database").toString()); + sqlDatabase.setUserName(settings->value("user").toString()); + sqlDatabase.setPassword(settings->value("password").toString()); + settings->endGroup(); + } else if (sqlDatabase.isOpen()) + sqlDatabase.close(); - settings->beginGroup("database"); - QSqlDatabase sqldb = QSqlDatabase::addDatabase("QMYSQL"); - sqldb.setHostName(settings->value("hostname").toString()); - sqldb.setDatabaseName(settings->value("database").toString()); - sqldb.setUserName(settings->value("user").toString()); - sqldb.setPassword(settings->value("password").toString()); - settings->endGroup(); - - std::cerr << "Opening database..."; - if (!sqldb.open()) { - std::cerr << "error" << std::endl; + qDebug() << QString("[main] Opening database..."); + if (!sqlDatabase.open()) { + qDebug() << QString("[main] Error opening database: %1").arg(sqlDatabase.lastError().text()); return false; } - std::cerr << "OK" << std::endl; return true; } @@ -263,8 +291,7 @@ bool Servatrice::checkSql() if (databaseType == DatabaseNone) return false; - QMutexLocker locker(&dbMutex); - if (!QSqlDatabase::database().exec("select 1").isActive()) + if (!sqlDatabase.exec("select 1").isActive()) return openDatabase(); return true; } @@ -273,7 +300,7 @@ bool Servatrice::execSqlQuery(QSqlQuery &query) { if (query.exec()) return true; - qCritical() << "Database error:" << query.lastError().text(); + qCritical() << QString("[main] Error executing query: %1").arg(query.lastError().text()); return false; } @@ -284,7 +311,7 @@ void Servatrice::updateServerList() serverListMutex.lock(); serverList.clear(); - QSqlQuery query; + QSqlQuery query(sqlDatabase); query.prepare("select id, ssl_cert, hostname, address, game_port, control_port from " + dbPrefix + "_servers order by id asc"); execSqlQuery(query); while (query.next()) { @@ -305,220 +332,6 @@ QList Servatrice::getServerList() const return result; } -AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) -{ - QMutexLocker locker(&dbMutex); - const QString method = settings->value("authentication/method").toString(); - switch (authenticationMethod) { - case AuthenticationNone: return UnknownUser; - case AuthenticationSql: { - if (!checkSql()) - return UnknownUser; - - QSqlQuery ipBanQuery; - ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.ip_address = :address) and b.ip_address = :address2"); - ipBanQuery.bindValue(":address", static_cast(handler)->getPeerAddress().toString()); - ipBanQuery.bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); - if (!execSqlQuery(ipBanQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } - - if (ipBanQuery.next()) { - const int secondsLeft = -ipBanQuery.value(0).toInt(); - const bool permanentBan = ipBanQuery.value(1).toInt(); - if ((secondsLeft > 0) || permanentBan) { - reasonStr = ipBanQuery.value(2).toString(); - banSecondsLeft = permanentBan ? 0 : secondsLeft; - qDebug("Login denied: banned by address"); - return UserIsBanned; - } - } - - QSqlQuery nameBanQuery; - nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.user_name = :name2) and b.user_name = :name1"); - nameBanQuery.bindValue(":name1", user); - nameBanQuery.bindValue(":name2", user); - if (!execSqlQuery(nameBanQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } - - if (nameBanQuery.next()) { - const int secondsLeft = -nameBanQuery.value(0).toInt(); - const bool permanentBan = nameBanQuery.value(1).toInt(); - if ((secondsLeft > 0) || permanentBan) { - reasonStr = nameBanQuery.value(2).toString(); - banSecondsLeft = permanentBan ? 0 : secondsLeft; - qDebug("Login denied: banned by name"); - return UserIsBanned; - } - } - - QSqlQuery passwordQuery; - passwordQuery.prepare("select password_sha512 from " + dbPrefix + "_users where name = :name and active = 1"); - passwordQuery.bindValue(":name", user); - if (!execSqlQuery(passwordQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } - - if (passwordQuery.next()) { - const QString correctPassword = passwordQuery.value(0).toString(); - if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { - qDebug("Login accepted: password right"); - return PasswordRight; - } else { - qDebug("Login denied: password wrong"); - return NotLoggedIn; - } - } else { - qDebug("Login accepted: unknown user"); - return UnknownUser; - } - } - } - return UnknownUser; -} - -bool Servatrice::userExists(const QString &user) -{ - if (authenticationMethod == AuthenticationSql) { - QMutexLocker locker(&dbMutex); - checkSql(); - - QSqlQuery query; - query.prepare("select 1 from " + dbPrefix + "_users where name = :name and active = 1"); - query.bindValue(":name", user); - if (!execSqlQuery(query)) - return false; - return query.next(); - } - return false; -} - -int Servatrice::getUserIdInDB(const QString &name) -{ - if (authenticationMethod == AuthenticationSql) { - QMutexLocker locker(&dbMutex); - QSqlQuery query; - query.prepare("select id from " + dbPrefix + "_users where name = :name and active = 1"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return -1; - if (!query.next()) - return -1; - return query.value(0).toInt(); - } - return -1; -} - -bool Servatrice::isInBuddyList(const QString &whoseList, const QString &who) -{ - if (authenticationMethod == AuthenticationNone) - return false; - - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return false; - - int id1 = getUserIdInDB(whoseList); - int id2 = getUserIdInDB(who); - - QSqlQuery query; - query.prepare("select 1 from " + dbPrefix + "_buddylist where id_user1 = :id_user1 and id_user2 = :id_user2"); - query.bindValue(":id_user1", id1); - query.bindValue(":id_user2", id2); - if (!execSqlQuery(query)) - return false; - return query.next(); -} - -bool Servatrice::isInIgnoreList(const QString &whoseList, const QString &who) -{ - if (authenticationMethod == AuthenticationNone) - return false; - - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return false; - - int id1 = getUserIdInDB(whoseList); - int id2 = getUserIdInDB(who); - - QSqlQuery query; - query.prepare("select 1 from " + dbPrefix + "_ignorelist where id_user1 = :id_user1 and id_user2 = :id_user2"); - query.bindValue(":id_user1", id1); - query.bindValue(":id_user2", id2); - if (!execSqlQuery(query)) - return false; - return query.next(); -} - -ServerInfo_User Servatrice::evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId) -{ - ServerInfo_User result; - - if (withId) - result.set_id(query.value(0).toInt()); - result.set_name(query.value(1).toString().toStdString()); - - const QString country = query.value(5).toString(); - if (!country.isEmpty()) - result.set_country(country.toStdString()); - - if (complete) { - const QByteArray avatarBmp = query.value(6).toByteArray(); - if (avatarBmp.size()) - result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); - } - - const QString genderStr = query.value(4).toString(); - if (genderStr == "m") - result.set_gender(ServerInfo_User::Male); - else if (genderStr == "f") - result.set_gender(ServerInfo_User::Female); - - const int is_admin = query.value(2).toInt(); - int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; - if (is_admin == 1) - userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; - else if (is_admin == 2) - userLevel |= ServerInfo_User::IsModerator; - result.set_user_level(userLevel); - - const QString realName = query.value(3).toString(); - if (!realName.isEmpty()) - result.set_real_name(realName.toStdString()); - - return result; -} - -ServerInfo_User Servatrice::getUserData(const QString &name, bool withId) -{ - ServerInfo_User result; - result.set_name(name.toStdString()); - result.set_user_level(ServerInfo_User::IsUser); - - if (authenticationMethod == AuthenticationSql) { - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return result; - - QSqlQuery query; - query.prepare("select id, name, admin, realname, gender, country, avatar_bmp from " + dbPrefix + "_users where name = :name and active = 1"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return result; - - if (query.next()) - return evalUserQueryResult(query, true, withId); - else - return result; - } else - return result; -} - int Servatrice::getUsersWithAddress(const QHostAddress &address) const { int result = 0; @@ -539,122 +352,8 @@ QList Servatrice::getUsersWithAddressAsList(const QHost return result; } -void Servatrice::clearSessionTables() -{ - qDebug() << "Clearing previous sessions..."; - - lockSessionTables(); - QSqlQuery query; - query.prepare("update " + dbPrefix + "_sessions set end_time=now() where end_time is null and id_server = :id_server"); - query.bindValue(":id_server", serverId); - query.exec(); - unlockSessionTables(); -} - -void Servatrice::lockSessionTables() -{ - QSqlQuery("lock tables " + dbPrefix + "_sessions write, " + dbPrefix + "_users read").exec(); -} - -void Servatrice::unlockSessionTables() -{ - QSqlQuery("unlock tables").exec(); -} - -bool Servatrice::userSessionExists(const QString &userName) -{ - // Call only after lockSessionTables(). - - QSqlQuery query; - query.prepare("select 1 from " + dbPrefix + "_sessions where user_name = :user_name and end_time is null"); - query.bindValue(":user_name", userName); - query.exec(); - return query.next(); -} - -qint64 Servatrice::startSession(const QString &userName, const QString &address) -{ - if (authenticationMethod == AuthenticationNone) - return -1; - - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return -1; - - QSqlQuery query; - query.prepare("insert into " + dbPrefix + "_sessions (user_name, id_server, ip_address, start_time) values(:user_name, :id_server, :ip_address, NOW())"); - query.bindValue(":user_name", userName); - query.bindValue(":id_server", serverId); - query.bindValue(":ip_address", address); - if (execSqlQuery(query)) - return query.lastInsertId().toInt(); - return -1; -} - -void Servatrice::endSession(qint64 sessionId) -{ - if (authenticationMethod == AuthenticationNone) - return; - - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return; - - QSqlQuery query; - query.exec("lock tables " + dbPrefix + "_sessions write"); - query.prepare("update " + dbPrefix + "_sessions set end_time=NOW() where id = :id_session"); - query.bindValue(":id_session", sessionId); - execSqlQuery(query); - query.exec("unlock tables"); -} - -QMap Servatrice::getBuddyList(const QString &name) -{ - QMap result; - - if (authenticationMethod == AuthenticationSql) { - QMutexLocker locker(&dbMutex); - checkSql(); - - QSqlQuery query; - query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_buddylist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return result; - - while (query.next()) { - const ServerInfo_User &temp = evalUserQueryResult(query, false); - result.insert(QString::fromStdString(temp.name()), temp); - } - } - return result; -} - -QMap Servatrice::getIgnoreList(const QString &name) -{ - QMap result; - - if (authenticationMethod == AuthenticationSql) { - QMutexLocker locker(&dbMutex); - checkSql(); - - QSqlQuery query; - query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_ignorelist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return result; - - while (query.next()) { - ServerInfo_User temp = evalUserQueryResult(query, false); - result.insert(QString::fromStdString(temp.name()), temp); - } - } - return result; -} - void Servatrice::updateLoginMessage() { - QMutexLocker locker(&dbMutex); if (!checkSql()) return; @@ -691,7 +390,6 @@ void Servatrice::statusUpdate() rxBytes = 0; rxBytesMutex.unlock(); - QMutexLocker locker(&dbMutex); if (!checkSql()) return; @@ -706,163 +404,6 @@ void Servatrice::statusUpdate() execSqlQuery(query); } -int Servatrice::getNextGameId() -{ - if (databaseType == DatabaseNone) - return Server::getNextGameId(); - - checkSql(); - - QSqlQuery query; - query.prepare("insert into " + dbPrefix + "_games (time_started) values (now())"); - execSqlQuery(query); - - return query.lastInsertId().toInt(); -} - -int Servatrice::getNextReplayId() -{ - if (databaseType == DatabaseNone) - return Server::getNextGameId(); - - checkSql(); - - QSqlQuery query; - query.prepare("insert into " + dbPrefix + "_replays () values ()"); - execSqlQuery(query); - - return query.lastInsertId().toInt(); -} - -void Servatrice::storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replayList) -{ - const ServerInfo_Game &gameInfo = replayList.first()->game_info(); - - Server_Room *room = rooms.value(gameInfo.room_id()); - - Event_ReplayAdded replayEvent; - ServerInfo_ReplayMatch *replayMatchInfo = replayEvent.mutable_match_info(); - replayMatchInfo->set_game_id(gameInfo.game_id()); - replayMatchInfo->set_room_name(room->getName().toStdString()); - replayMatchInfo->set_time_started(QDateTime::currentDateTime().addSecs(-secondsElapsed).toTime_t()); - replayMatchInfo->set_length(secondsElapsed); - replayMatchInfo->set_game_name(gameInfo.description()); - - const QStringList &allGameTypes = room->getGameTypes(); - QStringList gameTypes; - for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) - gameTypes.append(allGameTypes[gameInfo.game_types(i)]); - - QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; - QSetIterator playerIterator(allPlayersEver); - while (playerIterator.hasNext()) { - gameIds1.append(gameInfo.game_id()); - const QString &playerName = playerIterator.next(); - playerNames.append(playerName); - replayMatchInfo->add_player_names(playerName.toStdString()); - } - QSet allUsersInGame = allPlayersEver + allSpectatorsEver; - QSetIterator allUsersIterator(allUsersInGame); - while (allUsersIterator.hasNext()) { - int id = getUserIdInDB(allUsersIterator.next()); - if (id == -1) - continue; - gameIds2.append(gameInfo.game_id()); - userIds.append(id); - replayNames.append(QString::fromStdString(gameInfo.description())); - } - - QVariantList replayIds, replayGameIds, replayDurations, replayBlobs; - for (int i = 0; i < replayList.size(); ++i) { - QByteArray blob; - const unsigned int size = replayList[i]->ByteSize(); - blob.resize(size); - replayList[i]->SerializeToArray(blob.data(), size); - - replayIds.append(QVariant((qulonglong) replayList[i]->replay_id())); - replayGameIds.append(gameInfo.game_id()); - replayDurations.append(replayList[i]->duration_seconds()); - replayBlobs.append(blob); - - ServerInfo_Replay *replayInfo = replayMatchInfo->add_replay_list(); - replayInfo->set_replay_id(replayList[i]->replay_id()); - replayInfo->set_replay_name(gameInfo.description()); - replayInfo->set_duration(replayList[i]->duration_seconds()); - } - - SessionEvent *sessionEvent = Server_ProtocolHandler::prepareSessionEvent(replayEvent); - allUsersIterator.toFront(); - clientsLock.lockForRead(); - while (allUsersIterator.hasNext()) { - const QString userName = allUsersIterator.next(); - Server_AbstractUserInterface *userHandler = users.value(userName); - if (!userHandler) - userHandler = externalUsers.value(userName); - if (userHandler) - userHandler->sendProtocolItem(*sessionEvent); - } - clientsLock.unlock(); - delete sessionEvent; - - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return; - - QSqlQuery query1; - query1.prepare("update " + dbPrefix + "_games set room_name=:room_name, descr=:descr, creator_name=:creator_name, password=:password, game_types=:game_types, player_count=:player_count, time_finished=now() where id=:id_game"); - query1.bindValue(":room_name", room->getName()); - query1.bindValue(":id_game", gameInfo.game_id()); - query1.bindValue(":descr", QString::fromStdString(gameInfo.description())); - query1.bindValue(":creator_name", QString::fromStdString(gameInfo.creator_info().name())); - query1.bindValue(":password", gameInfo.with_password() ? 1 : 0); - query1.bindValue(":game_types", gameTypes.isEmpty() ? QString("") : gameTypes.join(", ")); - query1.bindValue(":player_count", gameInfo.max_players()); - if (!execSqlQuery(query1)) - return; - - QSqlQuery query2; - query2.prepare("insert into " + dbPrefix + "_games_players (id_game, player_name) values (:id_game, :player_name)"); - query2.bindValue(":id_game", gameIds1); - query2.bindValue(":player_name", playerNames); - query2.execBatch(); - - QSqlQuery replayQuery1; - replayQuery1.prepare("update " + dbPrefix + "_replays set id_game=:id_game, duration=:duration, replay=:replay where id=:id_replay"); - replayQuery1.bindValue(":id_replay", replayIds); - replayQuery1.bindValue(":id_game", replayGameIds); - replayQuery1.bindValue(":duration", replayDurations); - replayQuery1.bindValue(":replay", replayBlobs); - replayQuery1.execBatch(); - - QSqlQuery query3; - query3.prepare("insert into " + dbPrefix + "_replays_access (id_game, id_player, replay_name) values (:id_game, :id_player, :replay_name)"); - query3.bindValue(":id_game", gameIds2); - query3.bindValue(":id_player", userIds); - query3.bindValue(":replay_name", replayNames); - query3.execBatch(); -} - -DeckList *Servatrice::getDeckFromDatabase(int deckId, const QString &userName) -{ - checkSql(); - - QMutexLocker locker(&dbMutex); - QSqlQuery query; - - query.prepare("select content from " + dbPrefix + "_decklist_files where id = :id and user = :user"); - query.bindValue(":id", deckId); - query.bindValue(":user", userName); - execSqlQuery(query); - if (!query.next()) - throw Response::RespNameNotFound; - - QXmlStreamReader deckReader(query.value(0).toString()); - DeckList *deck = new DeckList; - deck->loadFromXml(&deckReader); - - return deck; -} - void Servatrice::scheduleShutdown(const QString &reason, int minutes) { shutdownReason = reason; diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 03ae2d71..afb256eb 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "server.h" class QSqlDatabase; @@ -35,6 +36,7 @@ class QTimer; class GameReplay; class Servatrice; +class Servatrice_ConnectionPool; class ServerSocketInterface; class IslInterface; @@ -43,9 +45,9 @@ class Servatrice_GameServer : public QTcpServer { private: Servatrice *server; bool threaded; + QList connectionPools; public: - Servatrice_GameServer(Servatrice *_server, bool _threaded, QObject *parent = 0) - : QTcpServer(parent), server(_server), threaded(_threaded) { } + Servatrice_GameServer(Servatrice *_server, bool _threaded, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent = 0); protected: void incomingConnection(int socketDescriptor); }; @@ -79,13 +81,45 @@ public: class Servatrice : public Server { Q_OBJECT +public: + enum AuthenticationMethod { AuthenticationNone, AuthenticationSql }; private slots: void statusUpdate(); void shutdownTimeout(); +protected: + void doSendIslMessage(const IslMessage &msg, int serverId); +private: + enum DatabaseType { DatabaseNone, DatabaseMySql }; + AuthenticationMethod authenticationMethod; + DatabaseType databaseType; + QSqlDatabase sqlDatabase; + QTimer *pingClock, *statusUpdateClock; + Servatrice_GameServer *gameServer; + Servatrice_IslServer *islServer; + QString serverName; + QString loginMessage; + QString dbPrefix; + QSettings *settings; + int serverId; + bool threaded; + int uptime; + QMutex txBytesMutex, rxBytesMutex; + quint64 txBytes, rxBytes; + int maxGameInactivityTime, maxPlayerInactivityTime; + int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser; + + QString shutdownReason; + int shutdownMinutes; + QTimer *shutdownTimer; + + mutable QMutex serverListMutex; + QList serverList; + void updateServerList(); + + QMap islInterfaces; public slots: void scheduleShutdown(const QString &reason, int minutes); public: - mutable QMutex dbMutex; Servatrice(QSettings *_settings, QObject *parent = 0); ~Servatrice(); bool initServer(); @@ -102,22 +136,15 @@ public: int getMaxMessageCountPerInterval() const { return maxMessageCountPerInterval; } int getMaxMessageSizePerInterval() const { return maxMessageSizePerInterval; } int getMaxGamesPerUser() const { return maxGamesPerUser; } + AuthenticationMethod getAuthenticationMethod() const { return authenticationMethod; } bool getThreaded() const { return threaded; } QString getDbPrefix() const { return dbPrefix; } int getServerId() const { return serverId; } void updateLoginMessage(); - ServerInfo_User getUserData(const QString &name, bool withId = false); int getUsersWithAddress(const QHostAddress &address) const; QList getUsersWithAddressAsList(const QHostAddress &address) const; - QMap getBuddyList(const QString &name); - QMap getIgnoreList(const QString &name); - bool isInBuddyList(const QString &whoseList, const QString &who); - bool isInIgnoreList(const QString &whoseList, const QString &who); void incTxBytes(quint64 num); void incRxBytes(quint64 num); - int getUserIdInDB(const QString &name); - int getNextGameId(); - int getNextReplayId(); void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replays); DeckList *getDeckFromDatabase(int deckId, const QString &userName); @@ -127,48 +154,6 @@ public: QReadWriteLock islLock; QList getServerList() const; -protected: - qint64 startSession(const QString &userName, const QString &address); - void endSession(qint64 sessionId); - bool userExists(const QString &user); - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); - - void clearSessionTables(); - void lockSessionTables(); - void unlockSessionTables(); - bool userSessionExists(const QString &userName); - - void doSendIslMessage(const IslMessage &msg, int serverId); -private: - enum AuthenticationMethod { AuthenticationNone, AuthenticationSql }; - enum DatabaseType { DatabaseNone, DatabaseMySql }; - AuthenticationMethod authenticationMethod; - DatabaseType databaseType; - QTimer *pingClock, *statusUpdateClock; - Servatrice_GameServer *gameServer; - Servatrice_IslServer *islServer; - QString serverName; - QString loginMessage; - QString dbPrefix; - QSettings *settings; - int serverId; - bool threaded; - int uptime; - QMutex txBytesMutex, rxBytesMutex; - quint64 txBytes, rxBytes; - int maxGameInactivityTime, maxPlayerInactivityTime; - int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser; - ServerInfo_User evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId = false); - - QString shutdownReason; - int shutdownMinutes; - QTimer *shutdownTimer; - - mutable QMutex serverListMutex; - QList serverList; - void updateServerList(); - - QMap islInterfaces; }; #endif diff --git a/servatrice/src/servatrice_connection_pool.h b/servatrice/src/servatrice_connection_pool.h new file mode 100644 index 00000000..38772105 --- /dev/null +++ b/servatrice/src/servatrice_connection_pool.h @@ -0,0 +1,29 @@ +#ifndef SERVATRICE_CONNECTION_POOL_H +#define SERVATRICE_CONNECTION_POOL_H + +#include +#include +#include + +class Servatrice_DatabaseInterface; + +class Servatrice_ConnectionPool : public QObject { + Q_OBJECT +private: + Servatrice_DatabaseInterface *databaseInterface; + mutable QMutex clientCountMutex; + int clientCount; +public: + Servatrice_ConnectionPool(Servatrice_DatabaseInterface *_databaseInterface) + : databaseInterface(_databaseInterface), clientCount(0) + { + } + Servatrice_DatabaseInterface *getDatabaseInterface() const { return databaseInterface; } + + int getClientCount() const { QMutexLocker locker(&clientCountMutex); return clientCount; } + void addClient() { QMutexLocker locker(&clientCountMutex); ++clientCount; } +public slots: + void removeClient() { QMutexLocker locker(&clientCountMutex); --clientCount; } +}; + +#endif diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp new file mode 100644 index 00000000..235deac3 --- /dev/null +++ b/servatrice/src/servatrice_database_interface.cpp @@ -0,0 +1,517 @@ +#include "servatrice.h" +#include "servatrice_database_interface.h" +#include "passwordhasher.h" +#include "serversocketinterface.h" +#include +#include +#include + +Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) + : instanceId(_instanceId), + sqlDatabase(QSqlDatabase()), + server(_server) +{ +} + +void Servatrice_DatabaseInterface::initDatabase(const QSqlDatabase &_sqlDatabase) +{ + sqlDatabase = QSqlDatabase::cloneDatabase(_sqlDatabase, "pool_" + QString::number(instanceId)); + openDatabase(); +} + +bool Servatrice_DatabaseInterface::openDatabase() +{ + if (sqlDatabase.isOpen()) + sqlDatabase.close(); + + qDebug() << QString("[pool %1] Opening database...").arg(instanceId); + if (!sqlDatabase.open()) { + qCritical() << QString("[pool %1] Error opening database: %2").arg(instanceId).arg(sqlDatabase.lastError().text()); + return false; + } + + return true; +} + +bool Servatrice_DatabaseInterface::checkSql() +{ + if (!sqlDatabase.isValid()) + return false; + + if (!sqlDatabase.exec("select 1").isActive()) + return openDatabase(); + return true; +} + +bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery &query) +{ + if (query.exec()) + return true; + qCritical() << QString("[pool %1] Error executing query: %2").arg(instanceId).arg(query.lastError().text()); + return false; +} + +AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) +{ + switch (server->getAuthenticationMethod()) { + case Servatrice::AuthenticationNone: return UnknownUser; + case Servatrice::AuthenticationSql: { + if (!checkSql()) + return UnknownUser; + + QSqlQuery ipBanQuery(sqlDatabase); + ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + server->getDbPrefix() + "_bans b where b.time_from = (select max(c.time_from) from " + server->getDbPrefix() + "_bans c where c.ip_address = :address) and b.ip_address = :address2"); + ipBanQuery.bindValue(":address", static_cast(handler)->getPeerAddress().toString()); + ipBanQuery.bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); + if (!execSqlQuery(ipBanQuery)) { + qDebug("Login denied: SQL error"); + return NotLoggedIn; + } + + if (ipBanQuery.next()) { + const int secondsLeft = -ipBanQuery.value(0).toInt(); + const bool permanentBan = ipBanQuery.value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + reasonStr = ipBanQuery.value(2).toString(); + banSecondsLeft = permanentBan ? 0 : secondsLeft; + qDebug("Login denied: banned by address"); + return UserIsBanned; + } + } + + QSqlQuery nameBanQuery(sqlDatabase); + nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + server->getDbPrefix() + "_bans b where b.time_from = (select max(c.time_from) from " + server->getDbPrefix() + "_bans c where c.user_name = :name2) and b.user_name = :name1"); + nameBanQuery.bindValue(":name1", user); + nameBanQuery.bindValue(":name2", user); + if (!execSqlQuery(nameBanQuery)) { + qDebug("Login denied: SQL error"); + return NotLoggedIn; + } + + if (nameBanQuery.next()) { + const int secondsLeft = -nameBanQuery.value(0).toInt(); + const bool permanentBan = nameBanQuery.value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + reasonStr = nameBanQuery.value(2).toString(); + banSecondsLeft = permanentBan ? 0 : secondsLeft; + qDebug("Login denied: banned by name"); + return UserIsBanned; + } + } + + QSqlQuery passwordQuery(sqlDatabase); + passwordQuery.prepare("select password_sha512 from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + passwordQuery.bindValue(":name", user); + if (!execSqlQuery(passwordQuery)) { + qDebug("Login denied: SQL error"); + return NotLoggedIn; + } + + if (passwordQuery.next()) { + const QString correctPassword = passwordQuery.value(0).toString(); + if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { + qDebug("Login accepted: password right"); + return PasswordRight; + } else { + qDebug("Login denied: password wrong"); + return NotLoggedIn; + } + } else { + qDebug("Login accepted: unknown user"); + return UnknownUser; + } + } + } + return UnknownUser; +} + +bool Servatrice_DatabaseInterface::userExists(const QString &user) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery query; + query.prepare("select 1 from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + query.bindValue(":name", user); + if (!execSqlQuery(query)) + return false; + return query.next(); + } + return false; +} + +int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + QSqlQuery query; + query.prepare("select id from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return -1; + if (!query.next()) + return -1; + return query.value(0).toInt(); + } + return -1; +} + +bool Servatrice_DatabaseInterface::isInBuddyList(const QString &whoseList, const QString &who) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return false; + + if (!checkSql()) + return false; + + int id1 = getUserIdInDB(whoseList); + int id2 = getUserIdInDB(who); + + QSqlQuery query; + query.prepare("select 1 from " + server->getDbPrefix() + "_buddylist where id_user1 = :id_user1 and id_user2 = :id_user2"); + query.bindValue(":id_user1", id1); + query.bindValue(":id_user2", id2); + if (!execSqlQuery(query)) + return false; + return query.next(); +} + +bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, const QString &who) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return false; + + if (!checkSql()) + return false; + + int id1 = getUserIdInDB(whoseList); + int id2 = getUserIdInDB(who); + + QSqlQuery query; + query.prepare("select 1 from " + server->getDbPrefix() + "_ignorelist where id_user1 = :id_user1 and id_user2 = :id_user2"); + query.bindValue(":id_user1", id1); + query.bindValue(":id_user2", id2); + if (!execSqlQuery(query)) + return false; + return query.next(); +} + +ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId) +{ + ServerInfo_User result; + + if (withId) + result.set_id(query.value(0).toInt()); + result.set_name(query.value(1).toString().toStdString()); + + const QString country = query.value(5).toString(); + if (!country.isEmpty()) + result.set_country(country.toStdString()); + + if (complete) { + const QByteArray avatarBmp = query.value(6).toByteArray(); + if (avatarBmp.size()) + result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); + } + + const QString genderStr = query.value(4).toString(); + if (genderStr == "m") + result.set_gender(ServerInfo_User::Male); + else if (genderStr == "f") + result.set_gender(ServerInfo_User::Female); + + const int is_admin = query.value(2).toInt(); + int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; + if (is_admin == 1) + userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; + else if (is_admin == 2) + userLevel |= ServerInfo_User::IsModerator; + result.set_user_level(userLevel); + + const QString realName = query.value(3).toString(); + if (!realName.isEmpty()) + result.set_real_name(realName.toStdString()); + + return result; +} + +ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, bool withId) +{ + ServerInfo_User result; + result.set_name(name.toStdString()); + result.set_user_level(ServerInfo_User::IsUser); + + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + if (!checkSql()) + return result; + + QSqlQuery query; + query.prepare("select id, name, admin, realname, gender, country, avatar_bmp from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return result; + + if (query.next()) + return evalUserQueryResult(query, true, withId); + else + return result; + } else + return result; +} + +void Servatrice_DatabaseInterface::clearSessionTables() +{ + lockSessionTables(); + QSqlQuery query; + query.prepare("update " + server->getDbPrefix() + "_sessions set end_time=now() where end_time is null and id_server = :id_server"); + query.bindValue(":id_server", server->getServerId()); + query.exec(); + unlockSessionTables(); +} + +void Servatrice_DatabaseInterface::lockSessionTables() +{ + QSqlQuery("lock tables " + server->getDbPrefix() + "_sessions write, " + server->getDbPrefix() + "_users read").exec(); +} + +void Servatrice_DatabaseInterface::unlockSessionTables() +{ + QSqlQuery("unlock tables").exec(); +} + +bool Servatrice_DatabaseInterface::userSessionExists(const QString &userName) +{ + // Call only after lockSessionTables(). + + QSqlQuery query; + query.prepare("select 1 from " + server->getDbPrefix() + "_sessions where user_name = :user_name and end_time is null"); + query.bindValue(":user_name", userName); + query.exec(); + return query.next(); +} + +qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const QString &address) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return -1; + + if (!checkSql()) + return -1; + + QSqlQuery query; + query.prepare("insert into " + server->getDbPrefix() + "_sessions (user_name, id_server, ip_address, start_time) values(:user_name, :id_server, :ip_address, NOW())"); + query.bindValue(":user_name", userName); + query.bindValue(":id_server", server->getServerId()); + query.bindValue(":ip_address", address); + if (execSqlQuery(query)) + return query.lastInsertId().toInt(); + return -1; +} + +void Servatrice_DatabaseInterface::endSession(qint64 sessionId) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return; + + if (!checkSql()) + return; + + QSqlQuery query; + query.exec("lock tables " + server->getDbPrefix() + "_sessions write"); + query.prepare("update " + server->getDbPrefix() + "_sessions set end_time=NOW() where id = :id_session"); + query.bindValue(":id_session", sessionId); + execSqlQuery(query); + query.exec("unlock tables"); +} + +QMap Servatrice_DatabaseInterface::getBuddyList(const QString &name) +{ + QMap result; + + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery query; + query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + server->getDbPrefix() + "_users a left join " + server->getDbPrefix() + "_buddylist b on a.id = b.id_user2 left join " + server->getDbPrefix() + "_users c on b.id_user1 = c.id where c.name = :name"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return result; + + while (query.next()) { + const ServerInfo_User &temp = evalUserQueryResult(query, false); + result.insert(QString::fromStdString(temp.name()), temp); + } + } + return result; +} + +QMap Servatrice_DatabaseInterface::getIgnoreList(const QString &name) +{ + QMap result; + + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery query; + query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + server->getDbPrefix() + "_users a left join " + server->getDbPrefix() + "_ignorelist b on a.id = b.id_user2 left join " + server->getDbPrefix() + "_users c on b.id_user1 = c.id where c.name = :name"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return result; + + while (query.next()) { + ServerInfo_User temp = evalUserQueryResult(query, false); + result.insert(QString::fromStdString(temp.name()), temp); + } + } + return result; +} + +int Servatrice_DatabaseInterface::getNextGameId() +{ + if (!checkSql()) + return Server_DatabaseInterface::getNextGameId(); + + QSqlQuery query; + query.prepare("insert into " + dbPrefix + "_games (time_started) values (now())"); + execSqlQuery(query); + + return query.lastInsertId().toInt(); +} + +int Servatrice_DatabaseInterface::getNextReplayId() +{ + if (!checkSql()) + return Server_DatabaseInterface::getNextReplayId(); + + QSqlQuery query; + query.prepare("insert into " + dbPrefix + "_replays () values ()"); + execSqlQuery(query); + + return query.lastInsertId().toInt(); +} + +void Servatrice_DatabaseInterface::storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replayList) +{ + const ServerInfo_Game &gameInfo = replayList.first()->game_info(); + + Server_Room *room = rooms.value(gameInfo.room_id()); + + Event_ReplayAdded replayEvent; + ServerInfo_ReplayMatch *replayMatchInfo = replayEvent.mutable_match_info(); + replayMatchInfo->set_game_id(gameInfo.game_id()); + replayMatchInfo->set_room_name(room->getName().toStdString()); + replayMatchInfo->set_time_started(QDateTime::currentDateTime().addSecs(-secondsElapsed).toTime_t()); + replayMatchInfo->set_length(secondsElapsed); + replayMatchInfo->set_game_name(gameInfo.description()); + + const QStringList &allGameTypes = room->getGameTypes(); + QStringList gameTypes; + for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) + gameTypes.append(allGameTypes[gameInfo.game_types(i)]); + + QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; + QSetIterator playerIterator(allPlayersEver); + while (playerIterator.hasNext()) { + gameIds1.append(gameInfo.game_id()); + const QString &playerName = playerIterator.next(); + playerNames.append(playerName); + replayMatchInfo->add_player_names(playerName.toStdString()); + } + QSet allUsersInGame = allPlayersEver + allSpectatorsEver; + QSetIterator allUsersIterator(allUsersInGame); + while (allUsersIterator.hasNext()) { + int id = getUserIdInDB(allUsersIterator.next()); + if (id == -1) + continue; + gameIds2.append(gameInfo.game_id()); + userIds.append(id); + replayNames.append(QString::fromStdString(gameInfo.description())); + } + + QVariantList replayIds, replayGameIds, replayDurations, replayBlobs; + for (int i = 0; i < replayList.size(); ++i) { + QByteArray blob; + const unsigned int size = replayList[i]->ByteSize(); + blob.resize(size); + replayList[i]->SerializeToArray(blob.data(), size); + + replayIds.append(QVariant((qulonglong) replayList[i]->replay_id())); + replayGameIds.append(gameInfo.game_id()); + replayDurations.append(replayList[i]->duration_seconds()); + replayBlobs.append(blob); + + ServerInfo_Replay *replayInfo = replayMatchInfo->add_replay_list(); + replayInfo->set_replay_id(replayList[i]->replay_id()); + replayInfo->set_replay_name(gameInfo.description()); + replayInfo->set_duration(replayList[i]->duration_seconds()); + } + + SessionEvent *sessionEvent = Server_ProtocolHandler::prepareSessionEvent(replayEvent); + allUsersIterator.toFront(); + clientsLock.lockForRead(); + while (allUsersIterator.hasNext()) { + const QString userName = allUsersIterator.next(); + Server_AbstractUserInterface *userHandler = users.value(userName); + if (!userHandler) + userHandler = externalUsers.value(userName); + if (userHandler) + userHandler->sendProtocolItem(*sessionEvent); + } + clientsLock.unlock(); + delete sessionEvent; + + if (!checkSql()) + return; + + QSqlQuery query1; + query1.prepare("update " + dbPrefix + "_games set room_name=:room_name, descr=:descr, creator_name=:creator_name, password=:password, game_types=:game_types, player_count=:player_count, time_finished=now() where id=:id_game"); + query1.bindValue(":room_name", room->getName()); + query1.bindValue(":id_game", gameInfo.game_id()); + query1.bindValue(":descr", QString::fromStdString(gameInfo.description())); + query1.bindValue(":creator_name", QString::fromStdString(gameInfo.creator_info().name())); + query1.bindValue(":password", gameInfo.with_password() ? 1 : 0); + query1.bindValue(":game_types", gameTypes.isEmpty() ? QString("") : gameTypes.join(", ")); + query1.bindValue(":player_count", gameInfo.max_players()); + if (!execSqlQuery(query1)) + return; + + QSqlQuery query2; + query2.prepare("insert into " + dbPrefix + "_games_players (id_game, player_name) values (:id_game, :player_name)"); + query2.bindValue(":id_game", gameIds1); + query2.bindValue(":player_name", playerNames); + query2.execBatch(); + + QSqlQuery replayQuery1; + replayQuery1.prepare("update " + dbPrefix + "_replays set id_game=:id_game, duration=:duration, replay=:replay where id=:id_replay"); + replayQuery1.bindValue(":id_replay", replayIds); + replayQuery1.bindValue(":id_game", replayGameIds); + replayQuery1.bindValue(":duration", replayDurations); + replayQuery1.bindValue(":replay", replayBlobs); + replayQuery1.execBatch(); + + QSqlQuery query3; + query3.prepare("insert into " + dbPrefix + "_replays_access (id_game, id_player, replay_name) values (:id_game, :id_player, :replay_name)"); + query3.bindValue(":id_game", gameIds2); + query3.bindValue(":id_player", userIds); + query3.bindValue(":replay_name", replayNames); + query3.execBatch(); +} + +DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, const QString &userName) +{ + checkSql(); + + QSqlQuery query; + + query.prepare("select content from " + dbPrefix + "_decklist_files where id = :id and user = :user"); + query.bindValue(":id", deckId); + query.bindValue(":user", userName); + execSqlQuery(query); + if (!query.next()) + throw Response::RespNameNotFound; + + QXmlStreamReader deckReader(query.value(0).toString()); + DeckList *deck = new DeckList; + deck->loadFromXml(&deckReader); + + return deck; +} diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h new file mode 100644 index 00000000..59beddef --- /dev/null +++ b/servatrice/src/servatrice_database_interface.h @@ -0,0 +1,49 @@ +#ifndef SERVATRICE_DATABASE_INTERFACE_H +#define SERVATRICE_DATABASE_INTERFACE_H + +#include +#include + +#include "server.h" +#include "server_database_interface.h" + +class Servatrice; + +class Servatrice_DatabaseInterface : public Server_DatabaseInterface { + Q_OBJECT +private: + int instanceId; + QSqlDatabase sqlDatabase; + Servatrice *server; + ServerInfo_User evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId = false); +protected: + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); +public: + Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server); + void initDatabase(const QSqlDatabase &_sqlDatabase); + bool openDatabase(); + bool checkSql(); + bool execSqlQuery(QSqlQuery &query); + + bool userExists(const QString &user); + int getUserIdInDB(const QString &name); + QMap getBuddyList(const QString &name); + QMap getIgnoreList(const QString &name); + bool isInBuddyList(const QString &whoseList, const QString &who); + bool isInIgnoreList(const QString &whoseList, const QString &who); + ServerInfo_User getUserData(const QString &name, bool withId = false); + + int getNextGameId(); + int getNextReplayId(); + + qint64 startSession(const QString &userName, const QString &address); + void endSession(qint64 sessionId); + + void clearSessionTables(); + void lockSessionTables(); + void unlockSessionTables(); + bool userSessionExists(const QString &userName); + +}; + +#endif diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 6080b65c..e8287872 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -23,6 +23,7 @@ #include #include "serversocketinterface.h" #include "servatrice.h" +#include "servatrice_database_interface.h" #include "decklist.h" #include "server_player.h" #include "main.h" @@ -59,8 +60,12 @@ static const int protocolVersion = 14; -ServerSocketInterface::ServerSocketInterface(Servatrice *_server, QTcpSocket *_socket, QObject *parent) - : Server_ProtocolHandler(_server, parent), servatrice(_server), socket(_socket), messageInProgress(false), handshakeStarted(false) +ServerSocketInterface::ServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QTcpSocket *_socket, QObject *parent) + : Server_ProtocolHandler(_server, _databaseInterface, parent), + servatrice(_server), + socket(_socket), + messageInProgress(false), + handshakeStarted(false) { connect(socket, SIGNAL(readyRead()), this, SLOT(readClient())); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError))); @@ -232,20 +237,20 @@ Response::ResponseCode ServerSocketInterface::cmdAddToList(const Command_AddToLi return Response::RespContextError; if (list == "buddy") - if (servatrice->isInBuddyList(QString::fromStdString(userInfo->name()), user)) + if (databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) return Response::RespContextError; if (list == "ignore") - if (servatrice->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) + if (databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) return Response::RespContextError; int id1 = userInfo->id(); - int id2 = servatrice->getUserIdInDB(user); + int id2 = static_cast(databaseInterface)->getUserIdInDB(user); if (id2 < 0) return Response::RespNameNotFound; if (id1 == id2) return Response::RespContextError; - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("insert into " + servatrice->getDbPrefix() + "_" + list + "list (id_user1, id_user2) values(:id1, :id2)"); query.bindValue(":id1", id1); @@ -255,7 +260,7 @@ Response::ResponseCode ServerSocketInterface::cmdAddToList(const Command_AddToLi Event_AddToList event; event.set_list_name(cmd.list()); - event.mutable_user_info()->CopyFrom(servatrice->getUserData(user)); + event.mutable_user_info()->CopyFrom(databaseInterface->getUserData(user)); rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); return Response::RespOk; @@ -273,18 +278,18 @@ Response::ResponseCode ServerSocketInterface::cmdRemoveFromList(const Command_Re return Response::RespContextError; if (list == "buddy") - if (!servatrice->isInBuddyList(QString::fromStdString(userInfo->name()), user)) + if (!databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) return Response::RespContextError; if (list == "ignore") - if (!servatrice->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) + if (!databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) return Response::RespContextError; int id1 = userInfo->id(); - int id2 = servatrice->getUserIdInDB(user); + int id2 = static_cast(databaseInterface)->getUserIdInDB(user); if (id2 < 0) return Response::RespNameNotFound; - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("delete from " + servatrice->getDbPrefix() + "_" + list + "list where id_user1 = :id1 and id_user2 = :id2"); query.bindValue(":id1", id1); @@ -307,7 +312,7 @@ int ServerSocketInterface::getDeckPathId(int basePathId, QStringList path) if (path[0].isEmpty()) return 0; - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and name = :name and user = :user"); query.bindValue(":id_parent", basePathId); @@ -331,7 +336,7 @@ int ServerSocketInterface::getDeckPathId(const QString &path) bool ServerSocketInterface::deckListHelper(int folderId, ServerInfo_DeckStorage_Folder *folder) { - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("select id, name from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and user = :user"); query.bindValue(":id_parent", folderId); @@ -397,7 +402,7 @@ Response::ResponseCode ServerSocketInterface::cmdDeckNewDir(const Command_DeckNe if (folderId == -1) return Response::RespNameNotFound; - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_folders (id_parent, user, name) values(:id_parent, :user, :name)"); query.bindValue(":id_parent", folderId); @@ -412,7 +417,7 @@ void ServerSocketInterface::deckDelDirHelper(int basePathId) { servatrice->checkSql(); - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent"); @@ -451,7 +456,7 @@ Response::ResponseCode ServerSocketInterface::cmdDeckDel(const Command_DeckDel & servatrice->checkSql(); - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and user = :user"); @@ -490,7 +495,7 @@ Response::ResponseCode ServerSocketInterface::cmdDeckUpload(const Command_DeckUp if (folderId == -1) return Response::RespNameNotFound; - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_files (id_folder, user, name, upload_time, content) values(:id_folder, :user, :name, NOW(), :content)"); query.bindValue(":id_folder", folderId); @@ -506,7 +511,7 @@ Response::ResponseCode ServerSocketInterface::cmdDeckUpload(const Command_DeckUp fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toTime_t()); rc.setResponseExtension(re); } else if (cmd.has_deck_id()) { - QMutexLocker locker(&servatrice->dbMutex); +// QMutexLocker locker(&servatrice->dbMutex); QSqlQuery query; query.prepare("update " + servatrice->getDbPrefix() + "_decklist_files set name=:name, upload_time=NOW(), content=:content where id = :id_deck and user = :user"); query.bindValue(":id_deck", cmd.deck_id()); @@ -557,7 +562,7 @@ Response::ResponseCode ServerSocketInterface::cmdReplayList(const Command_Replay Response_ReplayList *re = new Response_ReplayList; - servatrice->dbMutex.lock(); +// servatrice->dbMutex.lock(); QSqlQuery query1; query1.prepare("select a.id_game, a.replay_name, b.room_name, b.time_started, b.time_finished, b.descr, a.do_not_hide from cockatrice_replays_access a left join cockatrice_games b on b.id = a.id_game where a.id_player = :id_player and (a.do_not_hide = 1 or date_add(b.time_started, interval 7 day) > now())"); query1.bindValue(":id_player", userInfo->id()); @@ -594,7 +599,7 @@ Response::ResponseCode ServerSocketInterface::cmdReplayList(const Command_Replay replayInfo->set_duration(query3.value(1).toInt()); } } - servatrice->dbMutex.unlock(); +// servatrice->dbMutex.unlock(); rc.setResponseExtension(re); return Response::RespOk; @@ -605,7 +610,7 @@ Response::ResponseCode ServerSocketInterface::cmdReplayDownload(const Command_Re if (authState != PasswordRight) return Response::RespFunctionNotAllowed; - QMutexLocker dbLocker(&servatrice->dbMutex); +// QMutexLocker dbLocker(&servatrice->dbMutex); QSqlQuery query1; query1.prepare("select 1 from " + servatrice->getDbPrefix() + "_replays_access a left join " + servatrice->getDbPrefix() + "_replays b on a.id_game = b.id_game where b.id = :id_replay and a.id_player = :id_player"); @@ -638,7 +643,7 @@ Response::ResponseCode ServerSocketInterface::cmdReplayModifyMatch(const Command if (authState != PasswordRight) return Response::RespFunctionNotAllowed; - QMutexLocker dbLocker(&servatrice->dbMutex); +// QMutexLocker dbLocker(&servatrice->dbMutex); QSqlQuery query1; query1.prepare("update " + servatrice->getDbPrefix() + "_replays_access set do_not_hide=:do_not_hide where id_player = :id_player and id_game = :id_game"); @@ -654,7 +659,7 @@ Response::ResponseCode ServerSocketInterface::cmdReplayDeleteMatch(const Command if (authState != PasswordRight) return Response::RespFunctionNotAllowed; - QMutexLocker dbLocker(&servatrice->dbMutex); +// QMutexLocker dbLocker(&servatrice->dbMutex); QSqlQuery query1; query1.prepare("delete from " + servatrice->getDbPrefix() + "_replays_access where id_player = :id_player and id_game = :id_game"); @@ -675,7 +680,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban QString address = QString::fromStdString(cmd.address()); int minutes = cmd.minutes(); - servatrice->dbMutex.lock(); +// servatrice->dbMutex.lock(); QSqlQuery query; query.prepare("insert into " + servatrice->getDbPrefix() + "_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason, :visible_reason)"); query.bindValue(":user_name", userName); @@ -685,7 +690,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban query.bindValue(":reason", QString::fromStdString(cmd.reason())); query.bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason())); servatrice->execSqlQuery(query); - servatrice->dbMutex.unlock(); +// servatrice->dbMutex.unlock(); QList userList = servatrice->getUsersWithAddressAsList(QHostAddress(address)); ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index ed4b526c..86e063f8 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -27,6 +27,7 @@ class QTcpSocket; class Servatrice; +class Servatrice_DatabaseInterface; class DeckList; class ServerInfo_DeckStorage_Folder; @@ -91,7 +92,7 @@ private: Response::ResponseCode processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc); Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc); public: - ServerSocketInterface(Servatrice *_server, QTcpSocket *_socket, QObject *parent = 0); + ServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QTcpSocket *_socket, QObject *parent = 0); ~ServerSocketInterface(); void initSessionDeprecated(); bool initSession(); diff --git a/servatrice/src/serversocketthread.cpp b/servatrice/src/serversocketthread.cpp deleted file mode 100644 index 9c08ce1b..00000000 --- a/servatrice/src/serversocketthread.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "serversocketthread.h" -#include "serversocketinterface.h" -#include "server_logger.h" -#include "main.h" - -ServerSocketThread::ServerSocketThread(int _socketDescriptor, Servatrice *_server, QObject *parent) - : QThread(parent), server(_server), socketDescriptor(_socketDescriptor) -{ -} - -ServerSocketThread::~ServerSocketThread() -{ - quit(); - wait(); -} - -void ServerSocketThread::run() -{ - QTcpSocket *socket = new QTcpSocket; - socket->setSocketDescriptor(socketDescriptor); - logger->logMessage(QString("incoming connection: %1").arg(socket->peerAddress().toString())); - - ssi = new ServerSocketInterface(server, socket); - connect(ssi, SIGNAL(destroyed()), this, SLOT(deleteLater())); - - exec(); -} diff --git a/servatrice/src/serversocketthread.h b/servatrice/src/serversocketthread.h deleted file mode 100644 index 2aa6dc8b..00000000 --- a/servatrice/src/serversocketthread.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef SERVERSOCKETTHREAD_H -#define SERVERSOCKETTHREAD_H - -#include - -class Servatrice; -class ServerSocketInterface; - -class ServerSocketThread : public QThread { - Q_OBJECT -private: - Servatrice *server; - ServerSocketInterface *ssi; - int socketDescriptor; -public: - ServerSocketThread(int _socketDescriptor, Servatrice *_server, QObject *parent = 0); - ~ServerSocketThread(); -protected: - void run(); -}; - -#endif