From 1ca24b25975f68bf5f2379200c0400f970e6f749 Mon Sep 17 00:00:00 2001 From: Matt Lowe Date: Tue, 28 Jul 2015 00:33:37 +0200 Subject: [PATCH 01/20] Previous server connection history This adds the ability to select previous servers from a list to connect to. You can remove items from the drop down by selecting them and pressing delete. If you connect to a new host it will be added to the previous hosts. It will remember the last host connected to in the dropdown. --- cockatrice/src/dlg_connect.cpp | 102 +++++++++++++++++++++++++++++---- cockatrice/src/dlg_connect.h | 16 +++++- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/dlg_connect.cpp b/cockatrice/src/dlg_connect.cpp index d3bc4027..c97e4e6d 100644 --- a/cockatrice/src/dlg_connect.cpp +++ b/cockatrice/src/dlg_connect.cpp @@ -1,10 +1,14 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include #include #include "dlg_connect.h" @@ -14,8 +18,24 @@ DlgConnect::DlgConnect(QWidget *parent) QSettings settings; settings.beginGroup("server"); + previousHostButton = new QRadioButton(tr("Previous Host"), this); + + previousHosts = new QComboBox(this); + previousHosts->installEventFilter(new DeleteHighlightedItemWhenShiftDelPressedEventFilter); + QStringList previousHostList = settings.value("previoushosts").toStringList(); + if (previousHostList.isEmpty()) { + previousHostList << "cockatrice.woogerworks.com"; + previousHostList << "vps.poixen.com"; + previousHostList << "chickatrice.net"; + } + previousHosts->addItems(previousHostList); + previousHosts->setCurrentIndex(settings.value("previoushostindex").toInt()); + + newHostButton = new QRadioButton(tr("New Host"), this); + hostLabel = new QLabel(tr("&Host:")); - hostEdit = new QLineEdit(settings.value("hostname", "cockatrice.woogerworks.com").toString()); + hostEdit = new QLineEdit(); + hostEdit->setPlaceholderText(tr("Enter host name")); hostLabel->setBuddy(hostEdit); portLabel = new QLabel(tr("&Port:")); @@ -48,16 +68,19 @@ DlgConnect::DlgConnect(QWidget *parent) connect(savePasswordCheckBox, SIGNAL(stateChanged(int)), this, SLOT(passwordSaved(int))); QGridLayout *grid = new QGridLayout; - grid->addWidget(hostLabel, 0, 0); - grid->addWidget(hostEdit, 0, 1); - grid->addWidget(portLabel, 1, 0); - grid->addWidget(portEdit, 1, 1); - grid->addWidget(playernameLabel, 2, 0); - grid->addWidget(playernameEdit, 2, 1); - grid->addWidget(passwordLabel, 3, 0); - grid->addWidget(passwordEdit, 3, 1); - grid->addWidget(savePasswordCheckBox, 4, 0, 1, 2); - grid->addWidget(autoConnectCheckBox, 5, 0, 1, 2); + grid->addWidget(previousHostButton, 0, 1); + grid->addWidget(previousHosts, 1, 1); + grid->addWidget(newHostButton, 2, 1); + grid->addWidget(hostLabel, 3, 0); + grid->addWidget(hostEdit, 3, 1); + grid->addWidget(portLabel, 4, 0); + grid->addWidget(portEdit, 4, 1); + grid->addWidget(playernameLabel, 5, 0); + grid->addWidget(playernameEdit, 5, 1); + grid->addWidget(passwordLabel, 6, 0); + grid->addWidget(passwordEdit, 6, 1); + grid->addWidget(savePasswordCheckBox, 7, 0, 1, 2); + grid->addWidget(autoConnectCheckBox, 8, 0, 1, 2); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); @@ -71,8 +94,32 @@ DlgConnect::DlgConnect(QWidget *parent) setWindowTitle(tr("Connect to server")); setFixedHeight(sizeHint().height()); setMinimumWidth(300); + + connect(previousHostButton, SIGNAL(toggled(bool)), this, SLOT(previousHostSelected(bool))); + connect(newHostButton, SIGNAL(toggled(bool)), this, SLOT(newHostSelected(bool))); + + if (settings.value("previoushostlogin", 1).toInt()) + previousHostButton->setChecked(true); + else + newHostButton->setChecked(true); } + +void DlgConnect::previousHostSelected(bool state) { + if (state) { + hostEdit->setDisabled(true); + previousHosts->setDisabled(false); + } +} + +void DlgConnect::newHostSelected(bool state) { + if (state) { + hostEdit->setDisabled(false); + previousHosts->setDisabled(true); + } +} + + void DlgConnect::passwordSaved(int state) { Q_UNUSED(state); @@ -88,17 +135,34 @@ void DlgConnect::actOk() { QSettings settings; settings.beginGroup("server"); - settings.setValue("hostname", hostEdit->text()); settings.setValue("port", portEdit->text()); settings.setValue("playername", playernameEdit->text()); settings.setValue("password", savePasswordCheckBox->isChecked() ? passwordEdit->text() : QString()); settings.setValue("save_password", savePasswordCheckBox->isChecked() ? 1 : 0); settings.setValue("auto_connect", autoConnectCheckBox->isChecked() ? 1 : 0); + settings.setValue("previoushostlogin", previousHostButton->isChecked() ? 1 : 0); + + QStringList hostList; + if (newHostButton->isChecked()) + if (!hostEdit->text().trimmed().isEmpty()) + hostList << hostEdit->text(); + + for (int i = 0; i < previousHosts->count(); i++) + if(!previousHosts->itemText(i).trimmed().isEmpty()) + hostList << previousHosts->itemText(i); + + settings.setValue("previoushosts", hostList); + settings.setValue("previoushostindex", previousHosts->currentIndex()); settings.endGroup(); accept(); } + +QString DlgConnect::getHost() const { + return previousHostButton->isChecked() ? previousHosts->currentText() : hostEdit->text(); +} + void DlgConnect::actCancel() { QSettings settings; @@ -109,3 +173,17 @@ void DlgConnect::actCancel() reject(); } + + +bool DeleteHighlightedItemWhenShiftDelPressedEventFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Delete) { + QComboBox* combobox = reinterpret_cast(obj); + combobox->removeItem(combobox->currentIndex()); + return true; + } + } + return QObject::eventFilter(obj, event); +} diff --git a/cockatrice/src/dlg_connect.h b/cockatrice/src/dlg_connect.h index 6f135aa0..43cb319e 100644 --- a/cockatrice/src/dlg_connect.h +++ b/cockatrice/src/dlg_connect.h @@ -7,12 +7,22 @@ class QLabel; class QPushButton; class QCheckBox; +class QComboBox; +class QRadioButton; + +class DeleteHighlightedItemWhenShiftDelPressedEventFilter : public QObject +{ + Q_OBJECT +protected: + bool eventFilter(QObject *obj, QEvent *event); +}; + class DlgConnect : public QDialog { Q_OBJECT public: DlgConnect(QWidget *parent = 0); - QString getHost() const { return hostEdit->text(); } + QString getHost() const; int getPort() const { return portEdit->text().toInt(); } QString getPlayerName() const { return playernameEdit->text(); } QString getPassword() const { return passwordEdit->text(); } @@ -20,10 +30,14 @@ private slots: void actOk(); void actCancel(); void passwordSaved(int state); + void previousHostSelected(bool state); + void newHostSelected(bool state); private: QLabel *hostLabel, *portLabel, *playernameLabel, *passwordLabel; QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit; QCheckBox *savePasswordCheckBox, *autoConnectCheckBox; + QComboBox *previousHosts; + QRadioButton *newHostButton, *previousHostButton; }; #endif From 8ebd8532d6ec20eab997333b723b8acc2525bb58 Mon Sep 17 00:00:00 2001 From: tooomm Date: Thu, 6 Aug 2015 23:21:51 +0200 Subject: [PATCH 02/20] readme adjustments - toc changed (removed DocToc) - relinks to the toc on top under every category - image included on top - added other servers in community ressources --- README.md | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a94926ad..e1b2b9dc 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,30 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* +

-- [Cockatrice](#cockatrice) -- [Get Involved [![Gitter chat](https://badges.gitter.im/Cockatrice/Cockatrice.png)](https://gitter.im/Cockatrice/Cockatrice)](#get-involved-) -- [Community Resources](#community-resources) -- [Translation Status [![Cockatrice on Transiflex](https://ds0k0en9abmn1.cloudfront.net/static/charts/images/tx-logo-micro.646b0065fce6.png)](https://www.transifex.com/projects/p/cockatrice/)](#translation-status-) -- [Building [![Build Status](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice)](#building-) -- [Building servatrice Docker container](#building-servatrice-docker-container) -- [Running](#running) -- [License](#license) +--- - +**Table of Contents**    [Cockatrice](#cockatrice) | [Get Involved] (#get-involved-) | [Community](#community-resources) | [Translation](#translation-status-) | [Building](#building-) | [Running](#running) | [License](#license) + +--- # Cockatrice Cockatrice is an open-source multiplatform software for playing card games, such as Magic: The Gathering, over a network. It is fully client-server based to prevent any kind of cheating, though it supports single-player games without -a network interface as well. Both client and server are written in Qt, supporting both Qt4 and Qt5. +a network interface as well. Both client and server are written in Qt, supporting both Qt4 and Qt5.
[▲](https://github.com/Cockatrice/Cockatrice#readme) + # Get Involved [![Gitter chat](https://badges.gitter.im/Cockatrice/Cockatrice.png)](https://gitter.im/Cockatrice/Cockatrice) -Chat with the Cockatrice developers on Gitter. Come here to talk about the application, features, or just to hang out. For support regarding specific servers, please contact that server's admin or forum for support rather than asking here. +Chat with the Cockatrice developers on Gitter. Come here to talk about the application, features, or just to hang out. For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.
+[▲](https://github.com/Cockatrice/Cockatrice#readme) # Community Resources -- [reddit r/Cockatrice](http://reddit.com/r/cockatrice) -- [Woogerworks Server & Forums](http://www.woogerworks.com) - [Cockatrice Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) +- [reddit r/Cockatrice](http://reddit.com/r/cockatrice) +- [Woogerworks](http://www.woogerworks.com) / [Chickatrice] (http://www.chickatrice.net/) / [Poixen](http://www.poixen.com/) (incomplete Serverlist)
+ +[▲](https://github.com/Cockatrice/Cockatrice#readme) # Translation Status [![Cockatrice on Transiflex](https://ds0k0en9abmn1.cloudfront.net/static/charts/images/tx-logo-micro.646b0065fce6.png)](https://www.transifex.com/projects/p/cockatrice/) @@ -38,26 +35,25 @@ Language statistics for `Cockatrice` *(on the left)* and `Oracle` *(on the right [![Cockatrice translations](https://www.transifex.com/projects/p/cockatrice/resource/cockatrice/chart/image_png)](https://www.transifex.com/projects/p/cockatrice/resource/cockatrice/)      [![Oracle translations](https://www.transifex.com/projects/p/cockatrice/resource/oracle/chart/image_png)](https://www.transifex.com/projects/p/cockatrice/resource/oracle/) -Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information! +Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information!
+[▲](https://github.com/Cockatrice/Cockatrice#readme) # Building [![Build Status](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice) **Detailed compiling instructions are on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** Dependencies: - - [Qt](http://qt-project.org/) - [protobuf](http://code.google.com/p/protobuf/) - [CMake](http://www.cmake.org/) Oracle can optionally use zlib to load zipped files: - - [zlib](http://www.zlib.net/) The server requires an additional dependency when compiled under Qt4: - - [libgcrypt](http://www.gnu.org/software/libgcrypt/) + To compile: mkdir build @@ -75,15 +71,18 @@ The following flags can be passed to `cmake`: - `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings. - `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files. -# Building servatrice Docker container -`docker build -t servatrice .` +#### Building servatrice Docker container +`docker build -t servatrice .`
+[▲](https://github.com/Cockatrice/Cockatrice#readme) # Running `oracle` fetches card data `cockatrice` is the game client -`servatrice` is the server +`servatrice` is the server
+[▲](https://github.com/Cockatrice/Cockatrice#readme) # License -Cockatrice is free software, licensed under the GPLv2; see COPYING for details. +Cockatrice is free software, licensed under the GPLv2; see COPYING for details.
+[▲](https://github.com/Cockatrice/Cockatrice#readme) From 17392f1ae5c84d9c3ed122959aa582233b6d00da Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Sat, 8 Aug 2015 16:24:37 -0400 Subject: [PATCH 03/20] Moved the RegOnlyRequirement functions out of the Database interface into the proper Server block of code. --- common/server.cpp | 137 +++++++++--------- common/server.h | 1 + servatrice/servatrice.ini.example | 4 +- servatrice/src/servatrice.cpp | 6 +- servatrice/src/servatrice.h | 3 +- .../src/servatrice_database_interface.cpp | 94 ++++++------ .../src/servatrice_database_interface.h | 11 +- 7 files changed, 125 insertions(+), 131 deletions(-) diff --git a/common/server.cpp b/common/server.cpp index e07db95e..ef1f9400 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -46,7 +46,7 @@ Server::Server(bool _threaded, QObject *parent) qRegisterMetaType("GameEventContainer"); qRegisterMetaType("IslMessage"); qRegisterMetaType("Command_JoinGame"); - + connect(this, SIGNAL(sigSendIslMessage(IslMessage, int)), this, SLOT(doSendIslMessage(IslMessage, int)), Qt::QueuedConnection); } @@ -62,9 +62,9 @@ void Server::prepareDestroy() for (int i = 0; i < clients.size(); ++i) QMetaObject::invokeMethod(clients.at(i), "prepareDestroy", Qt::QueuedConnection); clientsLock.unlock(); - + bool done = false; - + class SleeperThread : public QThread { public: @@ -83,7 +83,7 @@ void Server::prepareDestroy() while (!clients.isEmpty()) clients.first()->prepareDestroy(); } - + roomsLock.lockForWrite(); QMapIterator roomIterator(rooms); while (roomIterator.hasNext()) @@ -107,21 +107,21 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString { if (name.size() > 35) name = name.left(35); - + Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); - + QWriteLocker locker(&clientsLock); - + AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid || authState == UserIsInactive) return authState; - + ServerInfo_User data = databaseInterface->getUserData(name, true); data.set_address(session->getAddress().toStdString()); name = QString::fromStdString(data.name()); // Compensate for case indifference - + databaseInterface->lockSessionTables(); - + if (authState == PasswordRight) { // verify that new session would not cause problems with older existing session @@ -133,8 +133,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString } else if (authState == UnknownUser) { // Change user name so that no two users have the same names, // don't interfere with registered user names though. - bool requireReg = databaseInterface->getRequireRegistration(); - if (requireReg) { + if (getRegOnlyServer()) { qDebug("Login denied: registration required"); databaseInterface->unlockSessionTables(); return RegistrationRequired; @@ -147,18 +146,18 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString name = tempName; data.set_name(name.toStdString()); } - + users.insert(name, session); qDebug() << "Server::loginUser:" << session << "name=" << name; - - data.set_session_id(databaseInterface->startSession(name, session->getAddress())); + + data.set_session_id(databaseInterface->startSession(name, session->getAddress())); databaseInterface->unlockSessionTables(); - + usersBySessionId.insert(data.session_id(), session); - + qDebug() << "session id:" << data.session_id(); session->setUserInfo(data); - + Event_UserJoined event; event.mutable_user_info()->CopyFrom(session->copyUserInfo(false)); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); @@ -166,10 +165,10 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString if (clients[i]->getAcceptsUserListChanges()) clients[i]->sendProtocolItem(*se); delete se; - + event.mutable_user_info()->CopyFrom(session->copyUserInfo(true, true, true)); locker.unlock(); - + if (clientid.isEmpty()){ // client id is empty, either out dated client or client has been modified if (getClientIdRequired()) @@ -183,7 +182,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString se = Server_ProtocolHandler::prepareSessionEvent(event); sendIsl_SessionEvent(*se); delete se; - + return authState; } @@ -208,7 +207,7 @@ QList Server::getPersistentPlayerReferences(const QString &user Server_AbstractUserInterface *Server::findUser(const QString &userName) const { // Call this only with clientsLock set. - + Server_AbstractUserInterface *userHandler = users.value(userName); if (userHandler) return userHandler; @@ -236,10 +235,10 @@ void Server::removeClient(Server_ProtocolHandler *client) clients[i]->sendProtocolItem(*se); sendIsl_SessionEvent(*se); delete se; - + users.remove(QString::fromStdString(data->name())); qDebug() << "Server::removeClient: name=" << QString::fromStdString(data->name()); - + if (data->has_session_id()) { const qint64 sessionId = data->session_id(); usersBySessionId.remove(sessionId); @@ -254,21 +253,21 @@ void Server::externalUserJoined(const ServerInfo_User &userInfo) { // This function is always called from the main thread via signal/slot. clientsLock.lockForWrite(); - + Server_RemoteUserInterface *newUser = new Server_RemoteUserInterface(this, ServerInfo_User_Container(userInfo)); externalUsers.insert(QString::fromStdString(userInfo.name()), newUser); externalUsersBySessionId.insert(userInfo.session_id(), newUser); - + Event_UserJoined event; event.mutable_user_info()->CopyFrom(userInfo); - + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); for (int i = 0; i < clients.size(); ++i) if (clients[i]->getAcceptsUserListChanges()) clients[i]->sendProtocolItem(*se); delete se; clientsLock.unlock(); - + ResponseContainer rc(-1); newUser->joinPersistentGames(rc); newUser->sendResponseContainer(rc, Response::RespNothing); @@ -277,12 +276,12 @@ void Server::externalUserJoined(const ServerInfo_User &userInfo) void Server::externalUserLeft(const QString &userName) { // This function is always called from the main thread via signal/slot. - + clientsLock.lockForWrite(); Server_AbstractUserInterface *user = externalUsers.take(userName); externalUsersBySessionId.remove(user->getUserInfo()->session_id()); clientsLock.unlock(); - + QMap > userGames(user->getGames()); QMapIterator > userGamesIterator(userGames); roomsLock.lockForRead(); @@ -291,26 +290,26 @@ void Server::externalUserLeft(const QString &userName) Server_Room *room = rooms.value(userGamesIterator.value().first); if (!room) continue; - + QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(userGamesIterator.key()); if (!game) continue; - + QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(userGamesIterator.value().second); if (!player) continue; - + player->disconnectClient(); } roomsLock.unlock(); - + delete user; - + Event_UserLeft event; event.set_name(userName.toStdString()); - + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); clientsLock.lockForRead(); for (int i = 0; i < clients.size(); ++i) @@ -324,7 +323,7 @@ void Server::externalRoomUserJoined(int roomId, const ServerInfo_User &userInfo) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); - + Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomUserJoined: room id=" << roomId << "not found"; @@ -337,7 +336,7 @@ void Server::externalRoomUserLeft(int roomId, const QString &userName) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); - + Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomUserLeft: room id=" << roomId << "not found"; @@ -350,7 +349,7 @@ void Server::externalRoomSay(int roomId, const QString &userName, const QString { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); - + Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomSay: room id=" << roomId << "not found"; @@ -365,7 +364,7 @@ void Server::externalRoomGameListChanged(int roomId, const ServerInfo_Game &game { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); - + Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomGameListChanged: room id=" << roomId << "not found"; @@ -377,11 +376,11 @@ void Server::externalRoomGameListChanged(int roomId, const ServerInfo_Game &game void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId) { // This function is always called from the main thread via signal/slot. - + try { QReadLocker roomsLocker(&roomsLock); QReadLocker clientsLocker(&clientsLock); - + Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalJoinGameCommandReceived: room id=" << roomId << "not found"; @@ -392,7 +391,7 @@ void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cm qDebug() << "externalJoinGameCommandReceived: session id=" << sessionId << "not found"; throw Response::RespNotInRoom; } - + ResponseContainer responseContainer(cmdId); Response::ResponseCode responseCode = room->processJoinGameCommand(cmd, responseContainer, userInterface); userInterface->sendResponseContainer(responseContainer, responseCode); @@ -400,7 +399,7 @@ void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cm Response response; response.set_cmd_id(cmdId); response.set_response_code(code); - + sendIsl_Response(response, serverId, sessionId); } } @@ -408,44 +407,44 @@ void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cm void Server::externalGameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId) { // This function is always called from the main thread via signal/slot. - + try { ResponseContainer responseContainer(cont.cmd_id()); Response::ResponseCode finalResponseCode = Response::RespOk; - + QReadLocker roomsLocker(&roomsLock); Server_Room *room = rooms.value(cont.room_id()); if (!room) { qDebug() << "externalGameCommandContainerReceived: room id=" << cont.room_id() << "not found"; throw Response::RespNotInRoom; } - + QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(cont.game_id()); if (!game) { qDebug() << "externalGameCommandContainerReceived: game id=" << cont.game_id() << "not found"; throw Response::RespNotInRoom; } - + QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(playerId); if (!player) { qDebug() << "externalGameCommandContainerReceived: player id=" << playerId << "not found"; throw Response::RespNotInRoom; } - + GameEventStorage ges; for (int i = cont.game_command_size() - 1; i >= 0; --i) { const GameCommand &sc = cont.game_command(i); qDebug() << "[ISL]" << QString::fromStdString(sc.ShortDebugString()); - + Response::ResponseCode resp = player->processGameCommand(sc, responseContainer, ges); - + if (resp != Response::RespOk) finalResponseCode = resp; } ges.sendToGame(game); - + if (finalResponseCode != Response::RespNothing) { player->playerMutex.lock(); player->getUserInterface()->sendResponseContainer(responseContainer, finalResponseCode); @@ -455,7 +454,7 @@ void Server::externalGameCommandContainerReceived(const CommandContainer &cont, Response response; response.set_cmd_id(cont.cmd_id()); response.set_response_code(code); - + sendIsl_Response(response, serverId, sessionId); } } @@ -463,9 +462,9 @@ void Server::externalGameCommandContainerReceived(const CommandContainer &cont, void Server::externalGameEventContainerReceived(const GameEventContainer &cont, qint64 sessionId) { // This function is always called from the main thread via signal/slot. - + QReadLocker usersLocker(&clientsLock); - + Server_ProtocolHandler *client = usersBySessionId.value(sessionId); if (!client) { qDebug() << "externalGameEventContainerReceived: session" << sessionId << "not found"; @@ -477,9 +476,9 @@ void Server::externalGameEventContainerReceived(const GameEventContainer &cont, void Server::externalResponseReceived(const Response &resp, qint64 sessionId) { // This function is always called from the main thread via signal/slot. - + QReadLocker usersLocker(&clientsLock); - + Server_ProtocolHandler *client = usersBySessionId.value(sessionId); if (!client) { qDebug() << "externalResponseReceived: session" << sessionId << "not found"; @@ -491,10 +490,10 @@ void Server::externalResponseReceived(const Response &resp, qint64 sessionId) void Server::broadcastRoomUpdate(const ServerInfo_Room &roomInfo, bool sendToIsl) { // This function is always called from the main thread via signal/slot. - + Event_ListRooms event; event.add_room_list()->CopyFrom(roomInfo); - + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); clientsLock.lockForRead(); @@ -502,10 +501,10 @@ void Server::broadcastRoomUpdate(const ServerInfo_Room &roomInfo, bool sendToIsl if (clients[i]->getAcceptsRoomListChanges()) clients[i]->sendProtocolItem(*se); clientsLock.unlock(); - + if (sendToIsl) sendIsl_SessionEvent(*se); - + delete se; } @@ -543,7 +542,7 @@ void Server::sendIsl_Response(const Response &item, int serverId, qint64 session if (sessionId != -1) msg.set_session_id(sessionId); msg.mutable_response()->CopyFrom(item); - + emit sigSendIslMessage(msg, serverId); } @@ -554,7 +553,7 @@ void Server::sendIsl_SessionEvent(const SessionEvent &item, int serverId, qint64 if (sessionId != -1) msg.set_session_id(sessionId); msg.mutable_session_event()->CopyFrom(item); - + emit sigSendIslMessage(msg, serverId); } @@ -565,7 +564,7 @@ void Server::sendIsl_GameEventContainer(const GameEventContainer &item, int serv if (sessionId != -1) msg.set_session_id(sessionId); msg.mutable_game_event_container()->CopyFrom(item); - + emit sigSendIslMessage(msg, serverId); } @@ -576,7 +575,7 @@ void Server::sendIsl_RoomEvent(const RoomEvent &item, int serverId, qint64 sessi if (sessionId != -1) msg.set_session_id(sessionId); msg.mutable_room_event()->CopyFrom(item); - + emit sigSendIslMessage(msg, serverId); } @@ -586,11 +585,11 @@ void Server::sendIsl_GameCommand(const CommandContainer &item, int serverId, qin msg.set_message_type(IslMessage::GAME_COMMAND_CONTAINER); msg.set_session_id(sessionId); msg.set_player_id(playerId); - + CommandContainer *cont = msg.mutable_game_command(); cont->CopyFrom(item); cont->set_room_id(roomId); - + emit sigSendIslMessage(msg, serverId); } @@ -599,10 +598,10 @@ void Server::sendIsl_RoomCommand(const CommandContainer &item, int serverId, qin IslMessage msg; msg.set_message_type(IslMessage::ROOM_COMMAND_CONTAINER); msg.set_session_id(sessionId); - + CommandContainer *cont = msg.mutable_room_command(); cont->CopyFrom(item); cont->set_room_id(roomId); - + emit sigSendIslMessage(msg, serverId); } diff --git a/common/server.h b/common/server.h index 46008fb0..46113a50 100644 --- a/common/server.h +++ b/common/server.h @@ -57,6 +57,7 @@ public: virtual bool getGameShouldPing() const { return false; } virtual bool getClientIdRequired() const { return false; } + virtual bool getRegOnlyServer() const { return false; } virtual int getPingClockInterval() const { return 0; } virtual int getMaxGameInactivityTime() const { return 9999999; } virtual int getMaxPlayerInactivityTime() const { return 9999999; } diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index 313001c7..814c3ab0 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -62,8 +62,8 @@ method=none ; if the chosen authentication method is password, here you can define the password your users will use to log in password=123456 -; Accept only registered users? default is 0 (accept unregistered users) -regonly=0 +; Accept only registered users? default is false (accept unregistered users) +regonly=false [users] diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index d218cfac..df570f5d 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -143,7 +143,7 @@ bool Servatrice::initServer() serverName = settingsCache->value("server/name", "My Cockatrice server").toString(); serverId = settingsCache->value("server/id", 0).toInt(); clientIdRequired = settingsCache->value("server/requireclientid",0).toBool(); - bool regServerOnly = settingsCache->value("authentication/regonly", 0).toBool(); + regServerOnly = settingsCache->value("authentication/regonly", 0).toBool(); const QString authenticationMethodStr = settingsCache->value("authentication/method").toString(); if (authenticationMethodStr == "sql") { @@ -161,7 +161,7 @@ bool Servatrice::initServer() qDebug() << "Authenticating method: none"; authenticationMethod = AuthenticationNone; } - + qDebug() << "Client ID Required: " << clientIdRequired; bool maxUserLimitEnabled = settingsCache->value("security/enable_max_user_limit", false).toBool(); qDebug() << "Maximum user limit enabled: " << maxUserLimitEnabled; @@ -174,7 +174,7 @@ bool Servatrice::initServer() bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); - qDebug() << "Registration enabled: " << registrationEnabled; + qDebug() << "Registration enabled: " << regServerOnly; if (registrationEnabled) qDebug() << "Require email address to register: " << requireEmailForRegistration; diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index b097022c..500df92c 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -120,7 +120,7 @@ private: QString shutdownReason; int shutdownMinutes; QTimer *shutdownTimer; - bool isFirstShutdownMessage, clientIdRequired; + bool isFirstShutdownMessage, clientIdRequired, regServerOnly; mutable QMutex serverListMutex; QList serverList; @@ -138,6 +138,7 @@ public: QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; } bool getGameShouldPing() const { return true; } bool getClientIdRequired() const { return clientIdRequired; } + bool getRegOnlyServer() const { return regServerOnly; } int getPingClockInterval() const { return pingClockInterval; } int getMaxGameInactivityTime() const { return maxGameInactivityTime; } int getMaxPlayerInactivityTime() const { return maxPlayerInactivityTime; } diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index e2b681a0..e2b56a51 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -44,7 +44,7 @@ bool Servatrice_DatabaseInterface::initDatabase(const QString &type, const QStri sqlDatabase.setDatabaseName(databaseName); sqlDatabase.setUserName(userName); sqlDatabase.setPassword(password); - + return openDatabase(); } @@ -52,7 +52,7 @@ bool Servatrice_DatabaseInterface::openDatabase() { if (sqlDatabase.isOpen()) sqlDatabase.close(); - + const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); qDebug() << QString("[%1] Opening database...").arg(poolStr); if (!sqlDatabase.open()) { @@ -92,7 +92,7 @@ bool Servatrice_DatabaseInterface::checkSql() { if (!sqlDatabase.isValid()) return false; - + if (!sqlDatabase.exec("select 1").isActive()) return openDatabase(); return true; @@ -152,12 +152,6 @@ bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user, QString return re.exactMatch(user); } -// TODO move this to Server -bool Servatrice_DatabaseInterface::getRequireRegistration() -{ - return settingsCache->value("authentication/regonly", 0).toBool(); -} - bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active) { if (!checkSql()) @@ -252,23 +246,23 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (!usernameIsValid(user, reasonStr)) return UsernameInvalid; - + if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) return UserIsBanned; - + QSqlQuery *passwordQuery = prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { qDebug("Login denied: SQL error"); return NotLoggedIn; } - + if (passwordQuery->next()) { const QString correctPassword = passwordQuery->value(0).toString(); const bool userIsActive = passwordQuery->value(1).toBool(); if(!userIsActive) { qDebug("Login denied: user not active"); - return UserIsInactive; + return UserIsInactive; } if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { qDebug("Login accepted: password right"); @@ -363,7 +357,7 @@ bool Servatrice_DatabaseInterface::activeUserExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); - + QSqlQuery *query = prepareQuery("select 1 from {prefix}_users where name = :name and active = 1"); query->bindValue(":name", user); if (!execSqlQuery(query)) @@ -377,7 +371,7 @@ bool Servatrice_DatabaseInterface::userExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); - + QSqlQuery *query = prepareQuery("select 1 from {prefix}_users where name = :name"); query->bindValue(":name", user); if (!execSqlQuery(query)) @@ -405,13 +399,13 @@ bool Servatrice_DatabaseInterface::isInBuddyList(const QString &whoseList, const { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return false; - + if (!checkSql()) return false; - + int id1 = getUserIdInDB(whoseList); int id2 = getUserIdInDB(who); - + QSqlQuery *query = prepareQuery("select 1 from {prefix}_buddylist where id_user1 = :id_user1 and id_user2 = :id_user2"); query->bindValue(":id_user1", id1); query->bindValue(":id_user2", id2); @@ -424,13 +418,13 @@ bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, cons { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return false; - + if (!checkSql()) return false; - + int id1 = getUserIdInDB(whoseList); int id2 = getUserIdInDB(who); - + QSqlQuery *query = prepareQuery("select 1 from {prefix}_ignorelist where id_user1 = :id_user1 and id_user2 = :id_user2"); query->bindValue(":id_user1", id1); query->bindValue(":id_user2", id2); @@ -442,11 +436,11 @@ bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, cons 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 int is_admin = query->value(2).toInt(); int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; if (is_admin == 1) @@ -492,16 +486,16 @@ ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, b 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 = prepareQuery("select id, name, admin, country, gender, realname, avatar_bmp, registrationDate, email from {prefix}_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 @@ -534,7 +528,7 @@ void Servatrice_DatabaseInterface::unlockSessionTables() bool Servatrice_DatabaseInterface::userSessionExists(const QString &userName) { // Call only after lockSessionTables(). - + QSqlQuery *query = prepareQuery("select 1 from {prefix}_sessions where user_name = :user_name and id_server = :id_server and end_time is null"); query->bindValue(":id_server", server->getServerId()); query->bindValue(":user_name", userName); @@ -546,10 +540,10 @@ qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return -1; - + if (!checkSql()) return -1; - + QSqlQuery *query = prepareQuery("insert into {prefix}_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()); @@ -563,13 +557,13 @@ void Servatrice_DatabaseInterface::endSession(qint64 sessionId) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return; - + if (!checkSql()) return; QSqlQuery *query = prepareQuery("lock tables {prefix}_sessions write"); execSqlQuery(query); - + query = prepareQuery("update {prefix}_sessions set end_time=NOW() where id = :id_session"); query->bindValue(":id_session", sessionId); execSqlQuery(query); @@ -581,7 +575,7 @@ void Servatrice_DatabaseInterface::endSession(qint64 sessionId) QMap Servatrice_DatabaseInterface::getBuddyList(const QString &name) { QMap result; - + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); @@ -589,7 +583,7 @@ QMap Servatrice_DatabaseInterface::getBuddyList(const 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); @@ -601,7 +595,7 @@ QMap Servatrice_DatabaseInterface::getBuddyList(const QMap Servatrice_DatabaseInterface::getIgnoreList(const QString &name) { QMap result; - + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); @@ -609,7 +603,7 @@ QMap Servatrice_DatabaseInterface::getIgnoreList(const 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); @@ -622,13 +616,13 @@ int Servatrice_DatabaseInterface::getNextGameId() { if (!sqlDatabase.isValid()) return server->getNextLocalGameId(); - + if (!checkSql()) return -1; - + QSqlQuery *query = prepareQuery("insert into {prefix}_games (time_started) values (now())"); execSqlQuery(query); - + return query->lastInsertId().toInt(); } @@ -636,10 +630,10 @@ int Servatrice_DatabaseInterface::getNextReplayId() { if (!checkSql()) return -1; - + QSqlQuery *query = prepareQuery("insert into {prefix}_replays () values ()"); execSqlQuery(query); - + return query->lastInsertId().toInt(); } @@ -647,7 +641,7 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, { if (!checkSql()) return; - + QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; QSetIterator playerIterator(allPlayersEver); while (playerIterator.hasNext()) { @@ -665,20 +659,20 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, 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); } - + { QSqlQuery *query = prepareQuery("update {prefix}_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"); query->bindValue(":room_name", roomName); @@ -717,17 +711,17 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, int userId) { checkSql(); - + QSqlQuery *query = prepareQuery("select content from {prefix}_decklist_files where id = :id and id_user = :id_user"); query->bindValue(":id", deckId); query->bindValue(":id_user", userId); execSqlQuery(query); if (!query->next()) throw Response::RespNameNotFound; - + DeckList *deck = new DeckList; deck->loadFromString_Native(query->value(0).toString()); - + return deck; } @@ -789,7 +783,7 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, const qDebug("Change password denied: SQL error"); return true; } - + if (!passwordQuery->next()) return true; @@ -831,7 +825,7 @@ int Servatrice_DatabaseInterface::getActiveUserCount() void Servatrice_DatabaseInterface::updateUsersClientID(const QString &userName, const QString &userClientID) { - + if (!checkSql()) return; @@ -839,5 +833,5 @@ void Servatrice_DatabaseInterface::updateUsersClientID(const QString &userName, query->bindValue(":clientid", userClientID); query->bindValue(":username", userName); execSqlQuery(query); - + } diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index c5bb08a5..4c5b376a 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -27,7 +27,7 @@ private: bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); protected: - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); public slots: @@ -36,7 +36,7 @@ public slots: public: Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server); ~Servatrice_DatabaseInterface(); - bool initDatabase(const QString &type, const QString &hostName, const QString &databaseName, + bool initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password); bool openDatabase(); bool checkSql(); @@ -52,7 +52,7 @@ public: 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); - void storeGameInformation(const QString &roomName, const QStringList &roomGameTypes, const ServerInfo_Game &gameInfo, + void storeGameInformation(const QString &roomName, const QStringList &roomGameTypes, const ServerInfo_Game &gameInfo, const QSet &allPlayersEver, const QSet&allSpectatorsEver, const QList &replayList); DeckList *getDeckFromDatabase(int deckId, int userId); @@ -68,12 +68,11 @@ public: bool usernameIsValid(const QString &user, QString & error); bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); - bool getRequireRegistration(); - bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, + bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active = false); bool activateUser(const QString &userName, const QString &token); void updateUsersClientID(const QString &userName, const QString &userClientID); - void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, + void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); bool changeUserPassword(const QString &user, const QString &oldPassword, const QString &newPassword); QChar getGenderChar(ServerInfo_User_Gender const &gender); From b4e0c00cca48c5cf1a775cc3b65709520cee5d66 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Sun, 9 Aug 2015 08:47:21 -0400 Subject: [PATCH 04/20] Add database migration script for client id --- servatrice/migrations/servatrice_0002_to_0003.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 servatrice/migrations/servatrice_0002_to_0003.sql diff --git a/servatrice/migrations/servatrice_0002_to_0003.sql b/servatrice/migrations/servatrice_0002_to_0003.sql new file mode 100644 index 00000000..4a87bea4 --- /dev/null +++ b/servatrice/migrations/servatrice_0002_to_0003.sql @@ -0,0 +1,5 @@ +-- Servatrice db migration from version 2 to version 3 + +alter table cockatrice_users add clientid varchar(15) not null; + +UPDATE cockatrice_schema_version SET version=3 WHERE version=2; From 76ba3b557d375e15e5c5013bc3804133f2017268 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 9 Aug 2015 18:17:10 +0200 Subject: [PATCH 05/20] Fix compilation of servatrice with mdvc Should fix #1336 --- servatrice/src/smtp/qxtglobal.h | 1 + servatrice/src/smtp/qxtmailattachment.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/servatrice/src/smtp/qxtglobal.h b/servatrice/src/smtp/qxtglobal.h index a5744d88..51d374a7 100644 --- a/servatrice/src/smtp/qxtglobal.h +++ b/servatrice/src/smtp/qxtglobal.h @@ -30,6 +30,7 @@ #define QXT_VERSION 0x000602 #define QXT_VERSION_STR "0.6.2" +#define QXT_STATIC //--------------------------global macros------------------------------ diff --git a/servatrice/src/smtp/qxtmailattachment.h b/servatrice/src/smtp/qxtmailattachment.h index 8084bbec..23955ff9 100644 --- a/servatrice/src/smtp/qxtmailattachment.h +++ b/servatrice/src/smtp/qxtmailattachment.h @@ -34,7 +34,7 @@ #include #include -class QxtMailAttachmentPrivate; +struct QxtMailAttachmentPrivate; class QXT_NETWORK_EXPORT QxtMailAttachment { public: From 3f78e0bec397caf3a26b7d79ee84d643806d0fc7 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Sun, 9 Aug 2015 12:56:09 -0400 Subject: [PATCH 06/20] Update Error 11 Clarity --- cockatrice/src/window_main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 4c979729..9eff2c60 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -319,6 +319,9 @@ void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 case Response::RespClientIdRequired: QMessageBox::critical(this, tr("Error"), tr("This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client.\nPlease close and reopen your client to try again.")); break; + case Response::RespContextError: + QMessageBox::critical(this, tr("Error"), tr("An internal error has occurred, please try closing and reopening your client and try again. If the error persists try updating your client to the most recent build and if need be contact your software provider.")); + break; case Response::RespAccountNotActivated: { bool ok = false; QString token = QInputDialog::getText(this, tr("Account activation"), tr("Your account has not been activated yet.\nYou need to provide the activation token received in the activation email"), QLineEdit::Normal, QString(), &ok); From 5fc0da6ab9f30490dbb6591b54cef60918d1998d Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Sun, 9 Aug 2015 15:07:30 -0400 Subject: [PATCH 07/20] #1351 Fix This update address's the issue were a server with no db has only non-registered users but when creating a game will cause the dialogue to only allow the check box of "registered users only" resulting in a game that no user will ever be able to join. --- cockatrice/src/settingscache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index b46746a5..06da3c9d 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -111,7 +111,7 @@ SettingsCache::SettingsCache() maxPlayers = settings->value("game/maxplayers", 2).toInt(); gameTypes = settings->value("game/gametypes","").toString(); onlyBuddies = settings->value("game/onlybuddies", false).toBool(); - onlyRegistered = settings->value("game/onlyregistered", true).toBool(); + onlyRegistered = settings->value("game/onlyregistered", false).toBool(); spectatorsAllowed = settings->value("game/spectatorsallowed", true).toBool(); spectatorsNeedPassword = settings->value("game/spectatorsneedpassword", false).toBool(); spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool(); From 59568bf13c0274cddbf2c49e19f4b5af17bb17f7 Mon Sep 17 00:00:00 2001 From: Gavin Bisesi Date: Sun, 9 Aug 2015 15:52:32 -0400 Subject: [PATCH 08/20] Revert "#1351 Fix" --- cockatrice/src/settingscache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 06da3c9d..b46746a5 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -111,7 +111,7 @@ SettingsCache::SettingsCache() maxPlayers = settings->value("game/maxplayers", 2).toInt(); gameTypes = settings->value("game/gametypes","").toString(); onlyBuddies = settings->value("game/onlybuddies", false).toBool(); - onlyRegistered = settings->value("game/onlyregistered", false).toBool(); + onlyRegistered = settings->value("game/onlyregistered", true).toBool(); spectatorsAllowed = settings->value("game/spectatorsallowed", true).toBool(); spectatorsNeedPassword = settings->value("game/spectatorsneedpassword", false).toBool(); spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool(); From fefceac2c6ea1c7926154552f5c001f282ad4e60 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Mon, 10 Aug 2015 15:29:30 -0400 Subject: [PATCH 09/20] Add option to servatrice to disable replay storage. --- servatrice/servatrice.ini.example | 5 +++++ servatrice/src/servatrice.cpp | 1 + servatrice/src/servatrice_database_interface.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index 814c3ab0..f8aeffa7 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -193,6 +193,11 @@ roomlist\1\game_types\3\name="GameType3" ; default is 120 max_game_inactivity_time=120 +; All actions during a game are recorded and stored in the database as a replay that all participants of +; the game can go back to and review after the game is closed. This can require a fairly large amount of +; storage to save all the information. Disable this option to prevent the storing of replay data in +; the database. Default value is true. +store_replays=true [security] ; You may want to restrict the number of users that can connect to your server at any given time. diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index df570f5d..0f56003c 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -162,6 +162,7 @@ bool Servatrice::initServer() authenticationMethod = AuthenticationNone; } + qDebug() << "Store Replays: " << settingsCache->value("game/store_replays", true).toBool(); qDebug() << "Client ID Required: " << clientIdRequired; bool maxUserLimitEnabled = settingsCache->value("security/enable_max_user_limit", false).toBool(); qDebug() << "Maximum user limit enabled: " << maxUserLimitEnabled; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index e2b56a51..f6ba71b7 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -642,6 +642,9 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, if (!checkSql()) return; + if (!settingsCache->value("game/store_replays", 1).toBool() ) + return; + QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; QSetIterator playerIterator(allPlayersEver); while (playerIterator.hasNext()) { From fa77cdf3b579828504213ab1f4a538c3b46c92d8 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Mon, 10 Aug 2015 16:07:20 -0400 Subject: [PATCH 10/20] Removed clientid requirements from the protocol handler. --- common/server_protocolhandler.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 073a3d95..272b8177 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -385,8 +385,6 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd if (userName.isEmpty() || (userInfo != 0)) return Response::RespContextError; - if (clientId.isEmpty()) - return Response::RespContextError; QString reasonStr; int banSecondsLeft = 0; From eb5833609adbb657fe25657a70921ef16049f2dc Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Tue, 11 Aug 2015 12:45:04 -0400 Subject: [PATCH 11/20] Add clientid field to sessions table. --- common/server.cpp | 2 +- common/server_database_interface.h | 2 +- servatrice/migrations/servatrice_0003_to_0004.sql | 5 +++++ servatrice/servatrice.sql | 2 +- servatrice/src/servatrice_database_interface.cpp | 5 +++-- servatrice/src/servatrice_database_interface.h | 4 ++-- 6 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 servatrice/migrations/servatrice_0003_to_0004.sql diff --git a/common/server.cpp b/common/server.cpp index ef1f9400..543a2d1c 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -150,7 +150,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString users.insert(name, session); qDebug() << "Server::loginUser:" << session << "name=" << name; - data.set_session_id(databaseInterface->startSession(name, session->getAddress())); + data.set_session_id(databaseInterface->startSession(name, session->getAddress(), clientid)); databaseInterface->unlockSessionTables(); usersBySessionId.insert(data.session_id(), session); diff --git a/common/server_database_interface.h b/common/server_database_interface.h index c132cb15..ebd944bc 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -24,7 +24,7 @@ public: virtual void storeGameInformation(const QString & /* roomName */, const QStringList & /* roomGameTypes */, const ServerInfo_Game & /* gameInfo */, const QSet & /* allPlayersEver */, const QSet & /* allSpectatorsEver */, const QList & /* replayList */) { } virtual DeckList *getDeckFromDatabase(int /* deckId */, int /* userId */) { return 0; } - virtual qint64 startSession(const QString & /* userName */, const QString & /* address */) { return 0; } + virtual qint64 startSession(const QString & /* userName */, const QString & /* address */, const QString & /* clientId */) { return 0; } virtual bool usernameIsValid(const QString & /*userName */, QString & /* error */) { return true; }; public slots: virtual void endSession(qint64 /* sessionId */ ) { } diff --git a/servatrice/migrations/servatrice_0003_to_0004.sql b/servatrice/migrations/servatrice_0003_to_0004.sql new file mode 100644 index 00000000..bbfce750 --- /dev/null +++ b/servatrice/migrations/servatrice_0003_to_0004.sql @@ -0,0 +1,5 @@ +-- Servatrice db migration from version 3 to version 4 + +alter table cockatrice_sessions add clientid varchar(15) not null; + +UPDATE cockatrice_schema_version SET version=4 WHERE version=3; diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index b33f7de5..80d517c4 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` ( PRIMARY KEY (`version`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO cockatrice_schema_version VALUES(3); +INSERT INTO cockatrice_schema_version VALUES(4); CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index e2b56a51..c48f8d0c 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -536,7 +536,7 @@ bool Servatrice_DatabaseInterface::userSessionExists(const QString &userName) return query->next(); } -qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const QString &address) +qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const QString &address, const QString &clientId) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return -1; @@ -544,10 +544,11 @@ qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const if (!checkSql()) return -1; - QSqlQuery *query = prepareQuery("insert into {prefix}_sessions (user_name, id_server, ip_address, start_time) values(:user_name, :id_server, :ip_address, NOW())"); + QSqlQuery *query = prepareQuery("insert into {prefix}_sessions (user_name, id_server, ip_address, start_time, clientid) values(:user_name, :id_server, :ip_address, NOW(), :client_id)"); query->bindValue(":user_name", userName); query->bindValue(":id_server", server->getServerId()); query->bindValue(":ip_address", address); + query->bindValue(":client_id", clientId); if (execSqlQuery(query)) return query->lastInsertId().toInt(); return -1; diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 4c5b376a..89e0dd3e 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -9,7 +9,7 @@ #include "server.h" #include "server_database_interface.h" -#define DATABASE_SCHEMA_VERSION 3 +#define DATABASE_SCHEMA_VERSION 4 class Servatrice; @@ -59,7 +59,7 @@ public: int getNextGameId(); int getNextReplayId(); int getActiveUserCount(); - qint64 startSession(const QString &userName, const QString &address); + qint64 startSession(const QString &userName, const QString &address, const QString &clientId); void endSession(qint64 sessionId); void clearSessionTables(); void lockSessionTables(); From d52cf379eecf976748735b5e5a47051ef90ae763 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Tue, 11 Aug 2015 12:53:56 -0400 Subject: [PATCH 12/20] Updated servatrice.sql to reflect new sessions table field. --- servatrice/servatrice.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 80d517c4..2a552ee4 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_sessions` ( `ip_address` char(15) COLLATE utf8_unicode_ci NOT NULL, `start_time` datetime NOT NULL, `end_time` datetime DEFAULT NULL, + `clientid` varchar(15) NOT NULL, PRIMARY KEY (`id`), KEY `username` (`user_name`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; From f636c0ee19b214ed42b43d011b54c9337c719929 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Tue, 11 Aug 2015 14:20:40 -0400 Subject: [PATCH 13/20] Add script to validate/clear invalid country codes in the DB. --- servatrice/scripts/linux/maint_countrycodes | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 servatrice/scripts/linux/maint_countrycodes diff --git a/servatrice/scripts/linux/maint_countrycodes b/servatrice/scripts/linux/maint_countrycodes new file mode 100644 index 00000000..81af8943 --- /dev/null +++ b/servatrice/scripts/linux/maint_countrycodes @@ -0,0 +1,31 @@ +#!/bin/bash + +# THIS SCRIPT EXPECTS TO BE EXECUTED FROM THE GITHUB SOURCE FOLDER PATH STRUCTURE +# OTHERWISE, UPDATE THE 'COUNTRYCODEIMAGEPATH' TO POINT TO THE FOLDER CONTAINING THE COUNTRY CODE IMAGES +# USE THIS SCRIPT TO COMPARE EXISTING USER ACCOUNTS TO VALID COUNTRY CODES AND CLEAR INVALID COUNTRY CODE DATA + +MODE="report" #set this to correct to fix invalid country codes, otherwise it only reports +DBNAME="servatrice" #set this to the database name used +TABLEPREFIX="cockatrice" #set this to the prefix used for the table names in the database (do not inclue the _) +SQLCONFFILE="./mysql.cnf" #set this to the path that contains the mysql.cnf file +COUNTRYCODEIMAGEPATH='../../../cockatrice/resources/countries' +VALIDCOUNT=0 +INVALIDCOUNT=0 + +for i in `mysql --defaults-file=$SQLCONFFILE -h localhost -e "select distinct(country) from ""$DBNAME"".""$TABLEPREFIX""_users;"` +do + if [ "$i" != "country" ]; then + if [ -f "$COUNTRYCODEIMAGEPATH/$i.svg" ]; then + ((VALIDCOUNT++)) + else + ((INVALIDCOUNT++)) + + if [ "$MODE" == "correct" ]; then + echo "$i COUNTRY CODE INVALID, ATTEMPTING TO CORRECT" + mysql --defaults-file=$SQLCONFFILE -h localhost -e "update ""$DBNAME"".""$TABLEPREFIX""_users set country = '' where country = '$i';" + fi + fi + fi +done +echo "INVALID: $INVALIDCOUNT" +echo "VALID: $VALIDCOUNT" From 7c81f8a610b969e2ce09a0db47b7ed8ea86782e0 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Tue, 11 Aug 2015 15:46:59 -0400 Subject: [PATCH 14/20] Added lower case country code on correction. --- servatrice/scripts/linux/maint_countrycodes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/servatrice/scripts/linux/maint_countrycodes b/servatrice/scripts/linux/maint_countrycodes index 81af8943..32d78628 100644 --- a/servatrice/scripts/linux/maint_countrycodes +++ b/servatrice/scripts/linux/maint_countrycodes @@ -27,5 +27,10 @@ do fi fi done + +if [ "$MODE" == "correct" ]; then + mysql --defaults-file=$SQLCONFFILE -h localhost -e "update ""$DBNAME"".""$TABLEPREFIX""_users set country = lower(country);" +fi + echo "INVALID: $INVALIDCOUNT" echo "VALID: $VALIDCOUNT" From b57a316bfdd1e456c0115995a1f132bfbb1be9d7 Mon Sep 17 00:00:00 2001 From: Zach H Date: Tue, 11 Aug 2015 17:10:05 -0400 Subject: [PATCH 15/20] remove max screen default --- cockatrice/src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index cdeee600..5bd07ef9 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -222,8 +222,8 @@ int main(int argc, char *argv[]) settingsCache->setClientID(generateClientID()); qDebug() << "ClientID In Cache: " << settingsCache->getClientID(); - ui.showMaximized(); - qDebug("main(): ui.showMaximized() finished"); + ui.show(); + qDebug("main(): ui.show() finished"); app.exec(); } From d5c00b0cb50ae6484a8183d119144fc9ed7273b5 Mon Sep 17 00:00:00 2001 From: tooomm Date: Thu, 13 Aug 2015 16:21:10 +0200 Subject: [PATCH 16/20] remove arrows with link to top --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e1b2b9dc..d1177ca0 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,19 @@ Cockatrice is an open-source multiplatform software for playing card games, such as Magic: The Gathering, over a network. It is fully client-server based to prevent any kind of cheating, though it supports single-player games without -a network interface as well. Both client and server are written in Qt, supporting both Qt4 and Qt5.
[▲](https://github.com/Cockatrice/Cockatrice#readme) +a network interface as well. Both client and server are written in Qt, supporting both Qt4 and Qt5.
# Get Involved [![Gitter chat](https://badges.gitter.im/Cockatrice/Cockatrice.png)](https://gitter.im/Cockatrice/Cockatrice) Chat with the Cockatrice developers on Gitter. Come here to talk about the application, features, or just to hang out. For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.
-[▲](https://github.com/Cockatrice/Cockatrice#readme) + # Community Resources - [Cockatrice Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) - [reddit r/Cockatrice](http://reddit.com/r/cockatrice) - [Woogerworks](http://www.woogerworks.com) / [Chickatrice] (http://www.chickatrice.net/) / [Poixen](http://www.poixen.com/) (incomplete Serverlist)
-[▲](https://github.com/Cockatrice/Cockatrice#readme) # Translation Status [![Cockatrice on Transiflex](https://ds0k0en9abmn1.cloudfront.net/static/charts/images/tx-logo-micro.646b0065fce6.png)](https://www.transifex.com/projects/p/cockatrice/) @@ -36,7 +35,7 @@ Language statistics for `Cockatrice` *(on the left)* and `Oracle` *(on the right [![Cockatrice translations](https://www.transifex.com/projects/p/cockatrice/resource/cockatrice/chart/image_png)](https://www.transifex.com/projects/p/cockatrice/resource/cockatrice/)      [![Oracle translations](https://www.transifex.com/projects/p/cockatrice/resource/oracle/chart/image_png)](https://www.transifex.com/projects/p/cockatrice/resource/oracle/) Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information!
-[▲](https://github.com/Cockatrice/Cockatrice#readme) + # Building [![Build Status](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice) @@ -73,16 +72,16 @@ The following flags can be passed to `cmake`: #### Building servatrice Docker container `docker build -t servatrice .`
-[▲](https://github.com/Cockatrice/Cockatrice#readme) + # Running `oracle` fetches card data `cockatrice` is the game client `servatrice` is the server
-[▲](https://github.com/Cockatrice/Cockatrice#readme) + # License Cockatrice is free software, licensed under the GPLv2; see COPYING for details.
-[▲](https://github.com/Cockatrice/Cockatrice#readme) + From 50d67467dce9f7ed2e63822b6fbba075b327d61a Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 10 Aug 2015 00:49:16 -0400 Subject: [PATCH 17/20] Username Completer in server room Adds QCompleter in server room and a setting to enable/disable it. --- cockatrice/src/dlg_settings.cpp | 13 ++- cockatrice/src/dlg_settings.h | 1 + cockatrice/src/settingscache.cpp | 8 ++ cockatrice/src/settingscache.h | 4 + cockatrice/src/tab_room.cpp | 182 +++++++++++++++++++++++++++++-- cockatrice/src/tab_room.h | 32 +++++- 6 files changed, 221 insertions(+), 19 deletions(-) diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 02e84457..6c9bd25e 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -573,6 +573,9 @@ MessagesSettingsPage::MessagesSettingsPage() { chatMentionCheckBox.setChecked(settingsCache->getChatMention()); connect(&chatMentionCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setChatMention(int))); + + chatMentionCompleterCheckbox.setChecked(settingsCache->getChatMentionCompleter()); + connect(&chatMentionCompleterCheckbox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setChatMentionCompleter(int))); ignoreUnregUsersMainChat.setChecked(settingsCache->getIgnoreUnregisteredUsers()); ignoreUnregUserMessages.setChecked(settingsCache->getIgnoreUnregisteredUserMessages()); @@ -605,11 +608,12 @@ MessagesSettingsPage::MessagesSettingsPage() chatGrid->addWidget(&chatMentionCheckBox, 0, 0); chatGrid->addWidget(&invertMentionForeground, 0, 1); chatGrid->addWidget(mentionColor, 0, 2); - chatGrid->addWidget(&ignoreUnregUsersMainChat, 1, 0); + chatGrid->addWidget(&chatMentionCompleterCheckbox, 1, 0); + chatGrid->addWidget(&ignoreUnregUsersMainChat, 2, 0); chatGrid->addWidget(&hexLabel, 1, 2); - chatGrid->addWidget(&ignoreUnregUserMessages, 2, 0); - chatGrid->addWidget(&messagePopups, 3, 0); - chatGrid->addWidget(&mentionPopups, 4, 0); + chatGrid->addWidget(&ignoreUnregUserMessages, 3, 0); + chatGrid->addWidget(&messagePopups, 4, 0); + chatGrid->addWidget(&mentionPopups, 5, 0); chatGroupBox = new QGroupBox; chatGroupBox->setLayout(chatGrid); @@ -734,6 +738,7 @@ void MessagesSettingsPage::retranslateUi() chatGroupBox->setTitle(tr("Chat settings")); highlightGroupBox->setTitle(tr("Custom alert words")); chatMentionCheckBox.setText(tr("Enable chat mentions")); + chatMentionCompleterCheckbox.setText(tr("Enable mention completer")); messageShortcuts->setTitle(tr("In-game message macros")); ignoreUnregUsersMainChat.setText(tr("Ignore chat room messages sent by unregistered users")); ignoreUnregUserMessages.setText(tr("Ignore private messages sent by unregistered users")); diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index e3c8b158..56329f8c 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -168,6 +168,7 @@ private: QAction *aAdd; QAction *aRemove; QCheckBox chatMentionCheckBox; + QCheckBox chatMentionCompleterCheckbox; QCheckBox invertMentionForeground; QCheckBox invertHighlightForeground; QCheckBox ignoreUnregUsersMainChat; diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index b46746a5..dd7b34fd 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -77,6 +77,7 @@ SettingsCache::SettingsCache() minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 5).toInt(); tapAnimation = settings->value("cards/tapanimation", true).toBool(); chatMention = settings->value("chat/mention", true).toBool(); + chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool(); chatMentionForeground = settings->value("chat/mentionforeground", true).toBool(); chatHighlightForeground = settings->value("chat/highlightforeground", true).toBool(); chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString(); @@ -360,6 +361,13 @@ void SettingsCache::setChatMention(int _chatMention) { settings->setValue("chat/mention", chatMention); } +void SettingsCache::setChatMentionCompleter(const int _enableMentionCompleter) +{ + chatMentionCompleter = _enableMentionCompleter; + settings->setValue("chat/mentioncompleter", chatMentionCompleter); + emit chatMentionCompleterChanged(); +} + void SettingsCache::setChatMentionForeground(int _chatMentionForeground) { chatMentionForeground = _chatMentionForeground; settings->setValue("chat/mentionforeground", chatMentionForeground); diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index c3e5f86b..28d32e4b 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -43,6 +43,7 @@ signals: void ignoreUnregisteredUserMessagesChanged(); void pixmapCacheSizeChanged(int newSizeInMBs); void masterVolumeChanged(int value); + void chatMentionCompleterChanged(); private: QSettings *settings; @@ -65,6 +66,7 @@ private: int minPlayersForMultiColumnLayout; bool tapAnimation; bool chatMention; + bool chatMentionCompleter; QString chatMentionColor; QString chatHighlightColor; bool chatMentionForeground; @@ -136,6 +138,7 @@ public: int getMinPlayersForMultiColumnLayout() const { return minPlayersForMultiColumnLayout; } bool getTapAnimation() const { return tapAnimation; } bool getChatMention() const { return chatMention; } + bool getChatMentionCompleter() const { return chatMentionCompleter; } bool getChatMentionForeground() const { return chatMentionForeground; } bool getChatHighlightForeground() const { return chatHighlightForeground; } bool getZoneViewSortByName() const { return zoneViewSortByName; } @@ -218,6 +221,7 @@ public slots: void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); void setTapAnimation(int _tapAnimation); void setChatMention(int _chatMention); + void setChatMentionCompleter(int _chatMentionCompleter); void setChatMentionForeground(int _chatMentionForeground); void setChatHighlightForeground(int _chatHighlightForeground); void setZoneViewSortByName(int _zoneViewSortByName); diff --git a/cockatrice/src/tab_room.cpp b/cockatrice/src/tab_room.cpp index 545901ce..b49b6604 100644 --- a/cockatrice/src/tab_room.cpp +++ b/cockatrice/src/tab_room.cpp @@ -11,6 +11,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include "tab_supervisor.h" #include "tab_room.h" #include "tab_userlists.h" @@ -51,8 +59,9 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerI connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); + connect(settingsCache, SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged())); sayLabel = new QLabel; - sayEdit = new QLineEdit; + sayEdit = new CustomLineEdit; sayLabel->setBuddy(sayEdit); connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); @@ -103,13 +112,26 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerI setLayout(hbox); const int userListSize = info.user_list_size(); - for (int i = 0; i < userListSize; ++i) + for (int i = 0; i < userListSize; ++i){ userList->processUserInfo(info.user_list(i), true); + autocompleteUserList.append("@" + QString::fromStdString(info.user_list(i).name())); + } userList->sortItems(); const int gameListSize = info.game_list_size(); for (int i = 0; i < gameListSize; ++i) gameSelector->processGameInfo(info.game_list(i)); + + completer = new QCompleter(autocompleteUserList, sayEdit); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setMaxVisibleItems(5); + + #if QT_VERSION >= 0x050000 + completer->setFilterMode(Qt::MatchStartsWith); + #endif + + sayEdit->setCompleter(completer); + actCompleterChanged(); } TabRoom::~TabRoom() @@ -119,7 +141,7 @@ TabRoom::~TabRoom() void TabRoom::retranslateUi() { - gameSelector->retranslateUi(); + gameSelector->retranslateUi(); chatView->retranslateUi(); userList->retranslateUi(); sayLabel->setText(tr("&Say:")); @@ -166,16 +188,20 @@ QString TabRoom::sanitizeHtml(QString dirty) const void TabRoom::sendMessage() { - if (sayEdit->text().isEmpty()) - return; + if (sayEdit->text().isEmpty()){ + return; + }else if (completer->popup()->isVisible()){ + completer->popup()->hide(); + return; + }else{ + Command_RoomSay cmd; + cmd.set_message(sayEdit->text().toStdString()); - Command_RoomSay cmd; - cmd.set_message(sayEdit->text().toStdString()); - - PendingCommand *pend = prepareRoomCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(sayFinished(const Response &))); - sendRoomCommand(pend); - sayEdit->clear(); + PendingCommand *pend = prepareRoomCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(sayFinished(const Response &))); + sendRoomCommand(pend); + sayEdit->clear(); + } } void TabRoom::sayFinished(const Response &response) @@ -200,6 +226,11 @@ void TabRoom::actOpenChatSettings() { settings.exec(); } +void TabRoom::actCompleterChanged() +{ + settingsCache->getChatMentionCompleter() ? completer->setCompletionRole(2) : completer->setCompletionRole(1); +} + void TabRoom::processRoomEvent(const RoomEvent &event) { switch (static_cast(getPbExtension(event))) { @@ -222,11 +253,17 @@ void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event) { userList->processUserInfo(event.user_info(), true); userList->sortItems(); + if (!autocompleteUserList.contains("@" + QString::fromStdString(event.user_info().name()))){ + autocompleteUserList << "@" + QString::fromStdString(event.user_info().name()); + sayEdit->updateCompleterModel(autocompleteUserList); + } } void TabRoom::processLeaveRoomEvent(const Event_LeaveRoom &event) { userList->deleteUser(QString::fromStdString(event.name())); + autocompleteUserList.removeOne("@" + QString::fromStdString(event.name())); + sayEdit->updateCompleterModel(autocompleteUserList); } void TabRoom::processRoomSayEvent(const Event_RoomSay &event) @@ -259,3 +296,124 @@ void TabRoom::sendRoomCommand(PendingCommand *pend) { client->sendCommand(pend); } + +CustomLineEdit::CustomLineEdit(QWidget *parent) + : QLineEdit(parent) +{ +} + +void CustomLineEdit::focusOutEvent(QFocusEvent * e){ + QLineEdit::focusOutEvent(e); + if (c->popup()->isVisible()){ + //Remove Popup + c->popup()->hide(); + //Truncate the line to last space or whole string + QString textValue = text(); + int lastIndex = textValue.length(); + int lastWordStartIndex = textValue.lastIndexOf(" ") + 1; + int leftShift = qMin(lastIndex, lastWordStartIndex); + setText(textValue.left(leftShift)); + //Insert highlighted line from popup + insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " "); + //Set focus back to the textbox since tab was pressed + setFocus(); + } +} + +void CustomLineEdit::keyPressEvent(QKeyEvent * event) +{ + switch (event->key()){ + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Escape: + if (c->popup()->isVisible()){ + event->ignore(); + //Remove Popup + c->popup()->hide(); + //Truncate the line to last space or whole string + QString textValue = text(); + int lastIndexof = textValue.lastIndexOf(" "); + QString finalString = textValue.left(lastIndexof); + //Add a space if there's a word + if (finalString != "") + finalString += " "; + setText(finalString); + return; + } + break; + case Qt::Key_Space: + if (c->popup()->isVisible()){ + event->ignore(); + //Remove Popup + c->popup()->hide(); + //Truncate the line to last space or whole string + QString textValue = text(); + int lastIndex = textValue.length(); + int lastWordStartIndex = textValue.lastIndexOf(" ") + 1; + int leftShift = qMin(lastIndex, lastWordStartIndex); + setText(textValue.left(leftShift)); + //Insert highlighted line from popup + insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " "); + return; + } + break; + default: + break; + } + + QLineEdit::keyPressEvent(event); + //Wait until the first character after @ + if (!c || text().right(1).contains("@")) + return; + + //Set new completion prefix + c->setCompletionPrefix(cursorWord(text())); + if (c->completionPrefix().length() < 1){ + c->popup()->hide(); + return; + } + + //Draw completion box + QRect cr = cursorRect(); + cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width()); + c->complete(cr); + + //Select first item in the completion popup + QItemSelectionModel* sm = new QItemSelectionModel(c->completionModel()); + c->popup()->setSelectionModel(sm); + sm->select(c->completionModel()->index(0, 0), QItemSelectionModel::ClearAndSelect); + sm->setCurrentIndex(c->completionModel()->index(0, 0), QItemSelectionModel::NoUpdate); +} + +QString CustomLineEdit::cursorWord(const QString &line) const +{ + return line.mid(line.left(cursorPosition()).lastIndexOf(" ") + 1, + cursorPosition() - line.left(cursorPosition()).lastIndexOf(" ") - 1); +} + +void CustomLineEdit::insertCompletion(QString arg) +{ + QString s_arg = arg + " "; + setText(text().replace(text().left(cursorPosition()).lastIndexOf(" ") + 1, + cursorPosition() - text().left(cursorPosition()).lastIndexOf(" ") - 1, s_arg)); +} + +void CustomLineEdit::setCompleter(QCompleter* completer) +{ + c = completer; + c->setWidget(this); + connect(c, SIGNAL(activated(QString)),this, SLOT(insertCompletion(QString))); +} + +void CustomLineEdit::updateCompleterModel(QStringList completionList) +{ + if (!c || c->popup()->isVisible()) + return; + + QStringListModel *model; + model = (QStringListModel*)(c->model()); + if (model == NULL) + model = new QStringListModel(); + QStringList updatedList = completionList; + model->setStringList(updatedList); +} \ No newline at end of file diff --git a/cockatrice/src/tab_room.h b/cockatrice/src/tab_room.h index 377f357f..98a74d8f 100644 --- a/cockatrice/src/tab_room.h +++ b/cockatrice/src/tab_room.h @@ -4,6 +4,9 @@ #include "tab.h" #include #include +#include +#include +#include namespace google { namespace protobuf { class Message; } } class AbstractClient; @@ -13,6 +16,7 @@ class ChatView; class QLineEdit; class QPushButton; class QTextTable; +class QCompleter; class RoomEvent; class ServerInfo_Room; class ServerInfo_Game; @@ -24,6 +28,7 @@ class GameSelector; class Response; class PendingCommand; class ServerInfo_User; +class CustomLineEdit; class TabRoom : public Tab { Q_OBJECT @@ -38,14 +43,17 @@ private: UserList *userList; ChatView *chatView; QLabel *sayLabel; - QLineEdit *sayEdit; + CustomLineEdit *sayEdit; QGroupBox *chatGroupBox; QMenu *roomMenu; QAction *aLeaveRoom; QAction *aOpenChatSettings; - QAction * aClearChat; + QAction *aClearChat; QString sanitizeHtml(QString dirty) const; + + QStringList autocompleteUserList; + QCompleter *completer; signals: void roomClosing(TabRoom *tab); void openMessageDialog(const QString &userName, bool focus); @@ -59,7 +67,8 @@ private slots: void addMentionTag(QString mentionTag); void focusTab(); void actShowMentionPopup(QString &sender); - + void actCompleterChanged(); + void processListGamesEvent(const Event_ListGames &event); void processJoinRoomEvent(const Event_JoinRoom &event); void processLeaveRoomEvent(const Event_LeaveRoom &event); @@ -81,4 +90,21 @@ public: void sendRoomCommand(PendingCommand *pend); }; +class CustomLineEdit : public QLineEdit +{ + Q_OBJECT +private: + QString cursorWord(const QString& line) const; + QCompleter* c; +private slots: + void insertCompletion(QString); +protected: + void keyPressEvent(QKeyEvent * event); + void focusOutEvent(QFocusEvent * e); +public: + explicit CustomLineEdit(QWidget *parent = 0); + void setCompleter(QCompleter*); + void updateCompleterModel(QStringList); +}; + #endif From 386716b8dbc6efef50b8bf45a2bb6a0a99e9a7c5 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 13 Aug 2015 23:06:07 -0400 Subject: [PATCH 18/20] Add option to open customsets folder --- cockatrice/src/tab_deck_editor.cpp | 33 ++++++++++++++++++++++++++++++ cockatrice/src/tab_deck_editor.h | 3 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index 390acd42..c5a40ab0 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include "tab_deck_editor.h" #include "window_sets.h" #include "carddatabase.h" @@ -280,6 +282,8 @@ void TabDeckEditor::createMenus() connect(aClose, SIGNAL(triggered()), this, SLOT(closeRequest())); aOpenCustomFolder = new QAction(QString(), this); connect(aOpenCustomFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomFolder())); + aOpenCustomsetsFolder = new QAction(QString(), this); + connect(aOpenCustomsetsFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomsetsFolder())); aEditSets = new QAction(QString(), this); connect(aEditSets, SIGNAL(triggered()), this, SLOT(actEditSets())); @@ -324,6 +328,7 @@ void TabDeckEditor::createMenus() #if defined(Q_OS_WIN) || defined(Q_OS_MAC) dbMenu->addSeparator(); dbMenu->addAction(aOpenCustomFolder); + dbMenu->addAction(aOpenCustomsetsFolder); #endif addTabMenu(dbMenu); } @@ -532,6 +537,7 @@ void TabDeckEditor::retranslateUi() aPrintDeck->setText(tr("&Print deck...")); aAnalyzeDeck->setText(tr("&Analyze deck on deckstats.net")); aOpenCustomFolder->setText(tr("Open custom image folder")); + aOpenCustomsetsFolder->setText(tr("Open custom sets folder")); aClose->setText(tr("&Close")); aClose->setShortcut(QKeySequence("Ctrl+Q")); @@ -774,6 +780,33 @@ void TabDeckEditor::actOpenCustomFolder() { } +void TabDeckEditor::actOpenCustomsetsFolder() { +#if QT_VERSION < 0x050000 + QString dataDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); +#else + QString dataDir = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); +#endif + +#if defined(Q_OS_MAC) + + QStringList scriptArgs; + scriptArgs << QLatin1String("-e"); + scriptArgs << QString::fromLatin1("tell application \"Finder\" to open POSIX file \"%1\"").arg(dataDir + "/customsets/"); + scriptArgs << QLatin1String("-e"); + scriptArgs << QLatin1String("tell application \"Finder\" to activate"); + + QProcess::execute("/usr/bin/osascript", scriptArgs); +#endif +#if defined(Q_OS_WIN) + QStringList args; + dataDir.append("/customsets"); + args << QDir::toNativeSeparators(dataDir); + aOpenCustomsetsFolder->setText(dataDir); + QProcess::startDetached("explorer", args); +#endif + +} + void TabDeckEditor::actEditSets() { WndSets *w = new WndSets; diff --git a/cockatrice/src/tab_deck_editor.h b/cockatrice/src/tab_deck_editor.h index 1398bff8..19591e3f 100644 --- a/cockatrice/src/tab_deck_editor.h +++ b/cockatrice/src/tab_deck_editor.h @@ -54,6 +54,7 @@ class TabDeckEditor : public Tab { void actPrintDeck(); void actAnalyzeDeck(); void actOpenCustomFolder(); + void actOpenCustomsetsFolder(); void actEditSets(); void actEditTokens(); @@ -112,7 +113,7 @@ private: QWidget *filterBox; QMenu *deckMenu, *dbMenu; - QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard, *aPrintDeck, *aAnalyzeDeck, *aClose, *aOpenCustomFolder; + QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard, *aPrintDeck, *aAnalyzeDeck, *aClose, *aOpenCustomFolder, *aOpenCustomsetsFolder; QAction *aEditSets, *aEditTokens, *aClearFilterAll, *aClearFilterOne; QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement;// *aUpdatePrices; QAction *aResetLayout; From b102a05a3655a6bc03b6fef1971d04f983b8b956 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Wed, 12 Aug 2015 21:55:40 -0400 Subject: [PATCH 19/20] Add ability to ban by client id --- cockatrice/src/localserver.cpp | 2 +- cockatrice/src/localserver.h | 2 +- cockatrice/src/remoteclient.cpp | 2 + cockatrice/src/user_context_menu.cpp | 1 + cockatrice/src/userlist.cpp | 17 +++++- cockatrice/src/userlist.h | 5 +- common/pb/moderator_commands.proto | 1 + common/pb/serverinfo_user.proto | 1 + common/pb/session_commands.proto | 1 + common/server.cpp | 2 +- common/server_database_interface.h | 4 +- common/serverinfo_user_container.cpp | 1 + .../migrations/servatrice_0004_to_0005.sql | 5 ++ servatrice/servatrice.sql | 3 +- .../src/servatrice_database_interface.cpp | 53 ++++++++++++++++--- .../src/servatrice_database_interface.h | 9 ++-- servatrice/src/serversocketinterface.cpp | 29 ++++++++-- 17 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 servatrice/migrations/servatrice_0004_to_0005.sql diff --git a/cockatrice/src/localserver.cpp b/cockatrice/src/localserver.cpp index a7eda3d1..613964fd 100644 --- a/cockatrice/src/localserver.cpp +++ b/cockatrice/src/localserver.cpp @@ -33,7 +33,7 @@ ServerInfo_User LocalServer_DatabaseInterface::getUserData(const QString &name, return result; } -AuthenticationResult LocalServer_DatabaseInterface::checkUserPassword(Server_ProtocolHandler * /* handler */, const QString & /* user */, const QString & /* password */, QString & /* reasonStr */, int & /* secondsLeft */) +AuthenticationResult LocalServer_DatabaseInterface::checkUserPassword(Server_ProtocolHandler * /* handler */, const QString & /* user */, const QString & /* password */, const QString & /* clientId */, QString & /* reasonStr */, int & /* secondsLeft */) { return UnknownUser; } diff --git a/cockatrice/src/localserver.h b/cockatrice/src/localserver.h index 276ebf90..221318c7 100644 --- a/cockatrice/src/localserver.h +++ b/cockatrice/src/localserver.h @@ -24,7 +24,7 @@ protected: ServerInfo_User getUserData(const QString &name, bool withId = false); public: LocalServer_DatabaseInterface(LocalServer *_localServer); - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft); int getNextGameId() { return localServer->getNextLocalGameId(); } int getNextReplayId() { return -1; } int getActiveUserCount() { return 0; } diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 68122d2a..96ee1eea 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -11,6 +11,7 @@ #include "pb/server_message.pb.h" #include "pb/event_server_identification.pb.h" #include "settingscache.h" +#include "main.h" static const unsigned int protocolVersion = 14; @@ -78,6 +79,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdRegister.set_gender((ServerInfo_User_Gender) gender); cmdRegister.set_country(country.toStdString()); cmdRegister.set_real_name(realName.toStdString()); + cmdRegister.set_clientid(settingsCache->getClientID().toStdString()); PendingCommand *pend = prepareSessionCommand(cmdRegister); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); diff --git a/cockatrice/src/user_context_menu.cpp b/cockatrice/src/user_context_menu.cpp index ace5a44b..758f99e1 100644 --- a/cockatrice/src/user_context_menu.cpp +++ b/cockatrice/src/user_context_menu.cpp @@ -99,6 +99,7 @@ void UserContextMenu::banUser_dialogFinished() cmd.set_minutes(dlg->getMinutes()); cmd.set_reason(dlg->getReason().toStdString()); cmd.set_visible_reason(dlg->getVisibleReason().toStdString()); + cmd.set_clientid(dlg->getBanId().toStdString()); client->sendCommand(client->prepareModeratorCommand(cmd)); } diff --git a/cockatrice/src/userlist.cpp b/cockatrice/src/userlist.cpp index 6afbc610..59717608 100644 --- a/cockatrice/src/userlist.cpp +++ b/cockatrice/src/userlist.cpp @@ -36,11 +36,19 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) ipBanCheckBox = new QCheckBox(tr("ban &IP address")); ipBanCheckBox->setChecked(true); ipBanEdit = new QLineEdit(QString::fromStdString(info.address())); + idBanCheckBox = new QCheckBox(tr("ban client I&D")); + idBanCheckBox->setChecked(true); + idBanEdit = new QLineEdit(QString::fromStdString(info.clientid())); + if (QString::fromStdString(info.clientid()).isEmpty()) + idBanCheckBox->setChecked(false); + QGridLayout *banTypeGrid = new QGridLayout; banTypeGrid->addWidget(nameBanCheckBox, 0, 0); banTypeGrid->addWidget(nameBanEdit, 0, 1); banTypeGrid->addWidget(ipBanCheckBox, 1, 0); banTypeGrid->addWidget(ipBanEdit, 1, 1); + banTypeGrid->addWidget(idBanCheckBox, 2, 0); + banTypeGrid->addWidget(idBanEdit, 2, 1); QGroupBox *banTypeGroupBox = new QGroupBox(tr("Ban type")); banTypeGroupBox->setLayout(banTypeGrid); @@ -110,8 +118,8 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) void BanDialog::okClicked() { - if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked()) { - QMessageBox::critical(this, tr("Error"), tr("You have to select a name-based or IP-based ban, or both.")); + if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked() && !idBanCheckBox->isChecked()) { + QMessageBox::critical(this, tr("Error"), tr("You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban.")); return; } accept(); @@ -127,6 +135,11 @@ void BanDialog::enableTemporaryEdits(bool enabled) minutesEdit->setEnabled(enabled); } +QString BanDialog::getBanId() const +{ + return idBanCheckBox->isChecked() ? idBanEdit->text() : QString(); +} + QString BanDialog::getBanName() const { return nameBanCheckBox->isChecked() ? nameBanEdit->text() : QString(); diff --git a/cockatrice/src/userlist.h b/cockatrice/src/userlist.h index fc043b1d..3ac47a08 100644 --- a/cockatrice/src/userlist.h +++ b/cockatrice/src/userlist.h @@ -24,8 +24,8 @@ class BanDialog : public QDialog { Q_OBJECT private: QLabel *daysLabel, *hoursLabel, *minutesLabel; - QCheckBox *nameBanCheckBox, *ipBanCheckBox; - QLineEdit *nameBanEdit, *ipBanEdit; + QCheckBox *nameBanCheckBox, *ipBanCheckBox, *idBanCheckBox; + QLineEdit *nameBanEdit, *ipBanEdit, *idBanEdit; QSpinBox *daysEdit, *hoursEdit, *minutesEdit; QRadioButton *permanentRadio, *temporaryRadio; QPlainTextEdit *reasonEdit, *visibleReasonEdit; @@ -36,6 +36,7 @@ public: BanDialog(const ServerInfo_User &info, QWidget *parent = 0); QString getBanName() const; QString getBanIP() const; + QString getBanId() const; int getMinutes() const; QString getReason() const; QString getVisibleReason() const; diff --git a/common/pb/moderator_commands.proto b/common/pb/moderator_commands.proto index 18324ef1..5fe8a847 100644 --- a/common/pb/moderator_commands.proto +++ b/common/pb/moderator_commands.proto @@ -14,4 +14,5 @@ message Command_BanFromServer { optional uint32 minutes = 3; optional string reason = 4; optional string visible_reason = 5; + optional string clientid = 6; } diff --git a/common/pb/serverinfo_user.proto b/common/pb/serverinfo_user.proto index e93d09fe..71e5849e 100644 --- a/common/pb/serverinfo_user.proto +++ b/common/pb/serverinfo_user.proto @@ -23,4 +23,5 @@ message ServerInfo_User { optional uint64 session_id = 10; optional uint64 accountage_secs = 11; optional string email = 12; + optional string clientid = 13; } diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index fda1d8bc..d5564f3e 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -119,6 +119,7 @@ message Command_Register { // Country code of the user. 2 letter ISO format optional string country = 5; optional string real_name = 6; + optional string clientid = 7; } // User wants to activate an account diff --git a/common/server.cpp b/common/server.cpp index 543a2d1c..f8ea1ee4 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -112,7 +112,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString QWriteLocker locker(&clientsLock); - AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); + AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, clientid, reasonStr, secondsLeft); if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid || authState == UserIsInactive) return authState; diff --git a/common/server_database_interface.h b/common/server_database_interface.h index ebd944bc..58a50293 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -12,8 +12,8 @@ public: Server_DatabaseInterface(QObject *parent = 0) : QObject(parent) { } - virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; - virtual bool checkUserIsBanned(const QString & /* ipAddress */, const QString & /* userName */, QString & /* banReason */, int & /* banSecondsRemaining */) { return false; } + virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft) = 0; + virtual bool checkUserIsBanned(const QString & /* ipAddress */, const QString & /* userName */, const QString & /* clientId */, QString & /* banReason */, int & /* banSecondsRemaining */) { return false; } virtual bool activeUserExists(const QString & /* user */) { return false; } virtual bool userExists(const QString & /* user */) { return false; } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } diff --git a/common/serverinfo_user_container.cpp b/common/serverinfo_user_container.cpp index bf53a3f0..a0fd05c9 100644 --- a/common/serverinfo_user_container.cpp +++ b/common/serverinfo_user_container.cpp @@ -36,6 +36,7 @@ ServerInfo_User &ServerInfo_User_Container::copyUserInfo(ServerInfo_User &result if (!sessionInfo) { result.clear_session_id(); result.clear_address(); + result.clear_clientid(); } if (!internalInfo) { diff --git a/servatrice/migrations/servatrice_0004_to_0005.sql b/servatrice/migrations/servatrice_0004_to_0005.sql new file mode 100644 index 00000000..2d4ecf00 --- /dev/null +++ b/servatrice/migrations/servatrice_0004_to_0005.sql @@ -0,0 +1,5 @@ +-- Servatrice db migration from version 4 to version 5 + +alter table cockatrice_bans add clientid varchar(15) not null; + +UPDATE cockatrice_schema_version SET version=5 WHERE version=4; diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 2a552ee4..4ffe8753 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` ( PRIMARY KEY (`version`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO cockatrice_schema_version VALUES(4); +INSERT INTO cockatrice_schema_version VALUES(5); CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, @@ -132,6 +132,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_bans` ( `minutes` int(6) NOT NULL, `reason` text NOT NULL, `visible_reason` text NOT NULL, + `clientid` varchar(15) NOT NULL, PRIMARY KEY (`user_name`,`time_from`), KEY `time_from` (`time_from`,`ip_address`), KEY `ip_address` (`ip_address`) diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index b1d75e0d..15dee6a2 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -229,7 +229,7 @@ QChar Servatrice_DatabaseInterface::getGenderChar(ServerInfo_User_Gender const & } } -AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) +AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &banSecondsLeft) { switch (server->getAuthenticationMethod()) { case Servatrice::AuthenticationNone: return UnknownUser; @@ -247,7 +247,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (!usernameIsValid(user, reasonStr)) return UsernameInvalid; - if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) + if (checkUserIsBanned(handler->getAddress(), user, clientId, reasonStr, banSecondsLeft)) return UserIsBanned; QSqlQuery *passwordQuery = prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); @@ -280,7 +280,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot return UnknownUser; } -bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) +bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, const QString &userName, const QString &clientId, QString &banReason, int &banSecondsRemaining) { if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) return false; @@ -291,11 +291,48 @@ bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, c } return - checkUserIsIpBanned(ipAddress, banReason, banSecondsRemaining) - || checkUserIsNameBanned(userName, banReason, banSecondsRemaining); + checkUserIsIpBanned(ipAddress, banReason, banSecondsRemaining) || checkUserIsNameBanned(userName, banReason, banSecondsRemaining) || checkUserIsIdBanned(clientId, banReason, banSecondsRemaining); } +bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, QString &banReason, int &banSecondsRemaining) +{ + if (clientId.isEmpty()) + return false; + + QSqlQuery *idBanQuery = prepareQuery( + "select" + " timestampdiff(second, now(), date_add(b.time_from, interval b.minutes minute))," + " b.minutes <=> 0," + " b.visible_reason" + " from {prefix}_bans b" + " where" + " b.time_from = (select max(c.time_from)" + " from {prefix}_bans c" + " where c.clientid = :id)" + " and b.clientid = :id2"); + + idBanQuery->bindValue(":id", clientId); + idBanQuery->bindValue(":id2", clientId); + if (!execSqlQuery(idBanQuery)) { + qDebug() << "Id ban check failed: SQL error." << idBanQuery->lastError(); + return false; + } + + if (idBanQuery->next()) { + const int secondsLeft = idBanQuery->value(0).toInt(); + const bool permanentBan = idBanQuery->value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + banReason = idBanQuery->value(2).toString(); + banSecondsRemaining = permanentBan ? 0 : secondsLeft; + qDebug() << "User is banned by client id" << clientId; + return true; + } + } + return false; +} + + bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName, QString &banReason, int &banSecondsRemaining) { QSqlQuery *nameBanQuery = prepareQuery("select timestampdiff(second, now(), date_add(b.time_from, interval b.minutes minute)), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.user_name = :name2) and b.user_name = :name1"); @@ -477,6 +514,10 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer const QString email = query->value(8).toString(); if (!email.isEmpty()) result.set_email(email.toStdString()); + + const QString clientid = query->value(9).toString(); + if (!clientid.isEmpty()) + result.set_clientid(clientid.toStdString()); } return result; } @@ -491,7 +532,7 @@ ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, b if (!checkSql()) return result; - QSqlQuery *query = prepareQuery("select id, name, admin, country, gender, realname, avatar_bmp, registrationDate, email from {prefix}_users where name = :name and active = 1"); + QSqlQuery *query = prepareQuery("select id, name, admin, country, gender, realname, avatar_bmp, registrationDate, email, clientid from {prefix}_users where name = :name and active = 1"); query->bindValue(":name", name); if (!execSqlQuery(query)) return result; diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 89e0dd3e..7e05fe8e 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -9,7 +9,7 @@ #include "server.h" #include "server_database_interface.h" -#define DATABASE_SCHEMA_VERSION 4 +#define DATABASE_SCHEMA_VERSION 5 class Servatrice; @@ -22,13 +22,14 @@ private: Servatrice *server; ServerInfo_User evalUserQueryResult(const QSqlQuery *query, bool complete, bool withId = false); /** Must be called after checkSql and server is known to be in auth mode. */ + bool checkUserIsIdBanned(const QString &clientId, QString &banReason, int &banSecondsRemaining); + /** Must be called after checkSql and server is known to be in auth mode. */ bool checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining); /** Must be called after checkSql and server is known to be in auth mode. */ bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); protected: - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, - const QString &password, QString &reasonStr, int &secondsLeft); + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft); public slots: void initDatabase(const QSqlDatabase &_sqlDatabase); @@ -66,7 +67,7 @@ public: void unlockSessionTables(); bool userSessionExists(const QString &userName); bool usernameIsValid(const QString &user, QString & error); - bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); + bool checkUserIsBanned(const QString &ipAddress, const QString &userName, const QString &clientId, QString &banReason, int &banSecondsRemaining); bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active = false); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index abb2997a..db87c70d 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -756,20 +756,40 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban if (trustedSources.contains(address,Qt::CaseInsensitive)) address = ""; - QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_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)"); + QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason, clientid) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason, :visible_reason, :client_id)"); query->bindValue(":user_name", userName); query->bindValue(":ip_address", address); query->bindValue(":id_admin", userInfo->id()); query->bindValue(":minutes", minutes); query->bindValue(":reason", QString::fromStdString(cmd.reason())); query->bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason())); + query->bindValue(":client_id", QString::fromStdString(cmd.clientid())); sqlInterface->execSqlQuery(query); servatrice->clientsLock.lockForRead(); QList userList = servatrice->getUsersWithAddressAsList(QHostAddress(address)); - ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); - if (user && !userList.contains(user)) + + if (!userName.isEmpty()) { + ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); userList.append(user); + } + + if (userName.isEmpty() && address.isEmpty()) { + QSqlQuery *query = sqlInterface->prepareQuery("select name from {prefix}_users where clientid = :client_id"); + query->bindValue(":client_id", QString::fromStdString(cmd.clientid())); + sqlInterface->execSqlQuery(query); + if (!sqlInterface->execSqlQuery(query)){ + qDebug("ClientID username ban lookup failed: SQL Error"); + } else { + while (query->next()) { + userName = query->value(0).toString(); + ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); + if (user && !userList.contains(user)) + userList.append(user); + } + } + } + if (!userList.isEmpty()) { Event_ConnectionClosed event; event.set_reason(Event_ConnectionClosed::BANNED); @@ -792,6 +812,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban Response::ResponseCode ServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) { QString userName = QString::fromStdString(cmd.user_name()); + QString clientId = QString::fromStdString(cmd.clientid()); qDebug() << "Got register command: " << userName; bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); @@ -822,7 +843,7 @@ Response::ResponseCode ServerSocketInterface::cmdRegisterAccount(const Command_R QString banReason; int banSecondsRemaining; - if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, banReason, banSecondsRemaining)) + if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, clientId, banReason, banSecondsRemaining)) { Response_Register *re = new Response_Register; re->set_denied_reason_str(banReason.toStdString()); From bb0b626ceeed54a4260ec9dac1bcf7623226ba61 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Sat, 15 Aug 2015 02:56:10 -0400 Subject: [PATCH 20/20] Add last_login column to user table and populate upon login --- common/server.cpp | 1 + common/server_database_interface.h | 1 + servatrice/migrations/servatrice_0005_to_0006.sql | 5 +++++ servatrice/servatrice.sql | 3 ++- servatrice/src/servatrice_database_interface.cpp | 11 +++++++++++ servatrice/src/servatrice_database_interface.h | 3 ++- 6 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 servatrice/migrations/servatrice_0005_to_0006.sql diff --git a/common/server.cpp b/common/server.cpp index f8ea1ee4..2fb064bc 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -179,6 +179,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString databaseInterface->updateUsersClientID(name, clientid); } + databaseInterface->updateUsersLastLoginTime(name); se = Server_ProtocolHandler::prepareSessionEvent(event); sendIsl_SessionEvent(*se); delete se; diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 58a50293..224ae2c7 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -42,6 +42,7 @@ public: virtual bool registerUser(const QString & /* userName */, const QString & /* realName */, ServerInfo_User_Gender const & /* gender */, const QString & /* password */, const QString & /* emailAddress */, const QString & /* country */, bool /* active = false */) { return false; } virtual bool activateUser(const QString & /* userName */, const QString & /* token */) { return false; } virtual void updateUsersClientID(const QString & /* userName */, const QString & /* userClientID */) { } + virtual void updateUsersLastLoginTime(const QString & /* userName */) { } enum LogMessage_TargetType { MessageTargetRoom, MessageTargetGame, MessageTargetChat, MessageTargetIslRoom }; virtual void logMessage(const int /* senderId */, const QString & /* senderName */, const QString & /* senderIp */, const QString & /* logMessage */, LogMessage_TargetType /* targetType */, const int /* targetId */, const QString & /* targetName */) { }; diff --git a/servatrice/migrations/servatrice_0005_to_0006.sql b/servatrice/migrations/servatrice_0005_to_0006.sql new file mode 100644 index 00000000..3f9552b2 --- /dev/null +++ b/servatrice/migrations/servatrice_0005_to_0006.sql @@ -0,0 +1,5 @@ +-- Servatrice db migration from version 5 to version 6 + +alter table cockatrice_users add last_login datetime not null; + +UPDATE cockatrice_schema_version SET version=6 WHERE version=5; diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 4ffe8753..27280167 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` ( PRIMARY KEY (`version`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO cockatrice_schema_version VALUES(5); +INSERT INTO cockatrice_schema_version VALUES(6); CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, @@ -84,6 +84,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` ( `active` tinyint(1) NOT NULL, `token` binary(16) NOT NULL, `clientid` varchar(15) NOT NULL, + `last_login` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `token` (`token`), diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 15dee6a2..ae8279fa 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -880,3 +880,14 @@ void Servatrice_DatabaseInterface::updateUsersClientID(const QString &userName, execSqlQuery(query); } + +void Servatrice_DatabaseInterface::updateUsersLastLoginTime(const QString &userName) +{ + if (!checkSql()) + return; + + QSqlQuery *query = prepareQuery("update {prefix}_users set last_login = NOW() where name = :user_name"); + query->bindValue(":user_name", userName); + execSqlQuery(query); + +} diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 7e05fe8e..d082b2f3 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -9,7 +9,7 @@ #include "server.h" #include "server_database_interface.h" -#define DATABASE_SCHEMA_VERSION 5 +#define DATABASE_SCHEMA_VERSION 6 class Servatrice; @@ -73,6 +73,7 @@ public: const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active = false); bool activateUser(const QString &userName, const QString &token); void updateUsersClientID(const QString &userName, const QString &userClientID); + void updateUsersLastLoginTime(const QString &userName); void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); bool changeUserPassword(const QString &user, const QString &oldPassword, const QString &newPassword);