Added server/client feature set communication

This commit is contained in:
woogerboy21 2015-08-27 00:10:41 -04:00
parent baa61d0571
commit 044c2356ff
26 changed files with 225 additions and 22 deletions

View file

@ -19,6 +19,7 @@
#include "get_pb_extension.h" #include "get_pb_extension.h"
#include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.h>
#include "client_metatypes.h" #include "client_metatypes.h"
#include "featureset.h"
AbstractClient::AbstractClient(QObject *parent) AbstractClient::AbstractClient(QObject *parent)
: QObject(parent), nextCmdId(0), status(StatusDisconnected) : QObject(parent), nextCmdId(0), status(StatusDisconnected)
@ -45,6 +46,10 @@ AbstractClient::AbstractClient(QObject *parent)
qRegisterMetaType<ServerInfo_User>("ServerInfo_User"); qRegisterMetaType<ServerInfo_User>("ServerInfo_User");
qRegisterMetaType<QList<ServerInfo_User> >("QList<ServerInfo_User>"); qRegisterMetaType<QList<ServerInfo_User> >("QList<ServerInfo_User>");
qRegisterMetaType<Event_ReplayAdded>("Event_ReplayAdded"); qRegisterMetaType<Event_ReplayAdded>("Event_ReplayAdded");
qRegisterMetaType<QList<QString> >("missingFeatures");
FeatureSet features;
features.initalizeFeatureList(clientFeatures);
connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *))); connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *)));
} }

View file

@ -25,6 +25,7 @@ class Event_NotifyUser;
class Event_ConnectionClosed; class Event_ConnectionClosed;
class Event_ServerShutdown; class Event_ServerShutdown;
class Event_ReplayAdded; class Event_ReplayAdded;
class FeatureSet;
enum ClientStatus { enum ClientStatus {
StatusDisconnected, StatusDisconnected,
@ -96,6 +97,8 @@ public:
static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId); static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId);
static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd); static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd);
static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd); static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd);
QMap<QString, bool> clientFeatures;
}; };
#endif #endif

View file

@ -24,6 +24,6 @@ Q_DECLARE_METATYPE(Event_UserMessage)
Q_DECLARE_METATYPE(ServerInfo_User) Q_DECLARE_METATYPE(ServerInfo_User)
Q_DECLARE_METATYPE(QList<ServerInfo_User>) Q_DECLARE_METATYPE(QList<ServerInfo_User>)
Q_DECLARE_METATYPE(Event_ReplayAdded) Q_DECLARE_METATYPE(Event_ReplayAdded)
Q_DECLARE_METATYPE(QList<QString>)
#endif #endif

View file

@ -45,6 +45,7 @@ GeneralSettingsPage::GeneralSettingsPage()
picDownloadCheckBox.setChecked(settingsCache->getPicDownload()); picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq()); picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq());
updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates());
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
// 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size) // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size)
@ -64,19 +65,21 @@ GeneralSettingsPage::GeneralSettingsPage()
connect(&picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int))); connect(&picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int)));
connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int))); connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
connect(&picDownloadHqCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool))); connect(&picDownloadHqCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
connect(highQualityURLEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlHq(QString))); connect(highQualityURLEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlHq(QString)));
QGridLayout *personalGrid = new QGridLayout; QGridLayout *personalGrid = new QGridLayout;
personalGrid->addWidget(&languageLabel, 0, 0); personalGrid->addWidget(&languageLabel, 0, 0);
personalGrid->addWidget(&languageBox, 0, 1); personalGrid->addWidget(&languageBox, 0, 1);
personalGrid->addWidget(&pixmapCacheLabel, 1, 0, 1, 1); personalGrid->addWidget(&pixmapCacheLabel, 1, 0);
personalGrid->addWidget(&pixmapCacheEdit, 1, 1, 1, 1); personalGrid->addWidget(&pixmapCacheEdit, 1, 1);
personalGrid->addWidget(&picDownloadCheckBox, 2, 0, 1, 2); personalGrid->addWidget(&updateNotificationCheckBox, 2, 0);
personalGrid->addWidget(&picDownloadHqCheckBox, 3, 0, 1, 2); personalGrid->addWidget(&picDownloadCheckBox, 3, 0);
personalGrid->addWidget(&clearDownloadedPicsButton, 4, 0, 1, 1); personalGrid->addWidget(&picDownloadHqCheckBox, 4, 0);
personalGrid->addWidget(&highQualityURLLabel, 5, 0, 1, 1); personalGrid->addWidget(&highQualityURLLabel, 5, 0);
personalGrid->addWidget(highQualityURLEdit, 5, 1, 1, 1); personalGrid->addWidget(highQualityURLEdit, 5, 1);
personalGrid->addWidget(&highQualityURLLinkLabel, 6, 1, 1, 1); personalGrid->addWidget(&highQualityURLLinkLabel, 6, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 6, 0);
highQualityURLLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); highQualityURLLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
highQualityURLLinkLabel.setOpenExternalLinks(true); highQualityURLLinkLabel.setOpenExternalLinks(true);
@ -246,6 +249,7 @@ void GeneralSettingsPage::retranslateUi()
highQualityURLLabel.setText(tr("Custom Card Download URL:")); highQualityURLLabel.setText(tr("Custom Card Download URL:"));
highQualityURLLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ"))); highQualityURLLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ")));
clearDownloadedPicsButton.setText(tr("Reset/Clear Downloaded Pictures")); clearDownloadedPicsButton.setText(tr("Reset/Clear Downloaded Pictures"));
updateNotificationCheckBox.setText(tr("Notify when new client features are available"));
} }
void GeneralSettingsPage::setEnabledStatus(bool status) void GeneralSettingsPage::setEnabledStatus(bool status)
@ -914,3 +918,4 @@ void DlgSettings::retranslateUi()
for (int i = 0; i < pagesWidget->count(); i++) for (int i = 0; i < pagesWidget->count(); i++)
dynamic_cast<AbstractSettingsPage *>(pagesWidget->widget(i))->retranslateUi(); dynamic_cast<AbstractSettingsPage *>(pagesWidget->widget(i))->retranslateUi();
} }

View file

@ -60,6 +60,7 @@ private:
QComboBox languageBox; QComboBox languageBox;
QCheckBox picDownloadCheckBox; QCheckBox picDownloadCheckBox;
QCheckBox picDownloadHqCheckBox; QCheckBox picDownloadHqCheckBox;
QCheckBox updateNotificationCheckBox;
QLabel languageLabel; QLabel languageLabel;
QLabel pixmapCacheLabel; QLabel pixmapCacheLabel;
QLabel deckPathLabel; QLabel deckPathLabel;

View file

@ -33,7 +33,6 @@
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include "QtNetwork/QNetworkInterface" #include "QtNetwork/QNetworkInterface"
#include <QCryptographicHash> #include <QCryptographicHash>
#include "main.h" #include "main.h"
#include "window_main.h" #include "window_main.h"
#include "dlg_settings.h" #include "dlg_settings.h"
@ -43,6 +42,7 @@
#include "pixmapgenerator.h" #include "pixmapgenerator.h"
#include "rng_sfmt.h" #include "rng_sfmt.h"
#include "soundengine.h" #include "soundengine.h"
#include "featureset.h"
//Q_IMPORT_PLUGIN(qjpeg) //Q_IMPORT_PLUGIN(qjpeg)

View file

@ -1,3 +1,4 @@
#include <QList>
#include <QTimer> #include <QTimer>
#include <QThread> #include <QThread>
#include "remoteclient.h" #include "remoteclient.h"
@ -81,7 +82,6 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica
cmdRegister.set_country(country.toStdString()); cmdRegister.set_country(country.toStdString());
cmdRegister.set_real_name(realName.toStdString()); cmdRegister.set_real_name(realName.toStdString());
cmdRegister.set_clientid(settingsCache->getClientID().toStdString()); cmdRegister.set_clientid(settingsCache->getClientID().toStdString());
PendingCommand *pend = prepareSessionCommand(cmdRegister); PendingCommand *pend = prepareSessionCommand(cmdRegister);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response)));
sendCommand(pend); sendCommand(pend);
@ -105,8 +105,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica
doLogin(); doLogin();
} }
void RemoteClient::doLogin() void RemoteClient::doLogin() {
{
setStatus(StatusLoggingIn); setStatus(StatusLoggingIn);
Command_Login cmdLogin; Command_Login cmdLogin;
@ -114,6 +113,13 @@ void RemoteClient::doLogin()
cmdLogin.set_password(password.toStdString()); cmdLogin.set_password(password.toStdString());
cmdLogin.set_clientid(settingsCache->getClientID().toStdString()); cmdLogin.set_clientid(settingsCache->getClientID().toStdString());
cmdLogin.set_clientver(VERSION_STRING); cmdLogin.set_clientver(VERSION_STRING);
if (!clientFeatures.isEmpty()) {
QMap<QString, bool>::iterator i;
for (i = clientFeatures.begin(); i != clientFeatures.end(); ++i)
cmdLogin.add_clientfeatures(i.key().toStdString().c_str());
}
PendingCommand *pend = prepareSessionCommand(cmdLogin); PendingCommand *pend = prepareSessionCommand(cmdLogin);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response))); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response)));
sendCommand(pend); sendCommand(pend);
@ -140,8 +146,17 @@ void RemoteClient::loginResponse(const Response &response)
for (int i = resp.ignore_list_size() - 1; i >= 0; --i) for (int i = resp.ignore_list_size() - 1; i >= 0; --i)
ignoreList.append(resp.ignore_list(i)); ignoreList.append(resp.ignore_list(i));
emit ignoreListReceived(ignoreList); emit ignoreListReceived(ignoreList);
if (resp.missing_features_size() > 0 && settingsCache->getNotifyAboutUpdates())
emit notifyUserAboutUpdate();
} else { } else {
emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); QList<QString> missingFeatures;
if (resp.missing_features_size() > 0) {
for (int i = 0; i < resp.missing_features_size(); ++i)
missingFeatures << QString::fromStdString(resp.missing_features(i));
}
emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time(), missingFeatures);
setStatus(StatusDisconnecting); setStatus(StatusDisconnecting);
} }
} }

View file

@ -11,7 +11,7 @@ class RemoteClient : public AbstractClient {
signals: signals:
void maxPingTime(int seconds, int maxSeconds); void maxPingTime(int seconds, int maxSeconds);
void serverTimeout(); void serverTimeout();
void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime, QList<QString> missingFeatures);
void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime);
void activateError(); void activateError();
void socketError(const QString &errorString); void socketError(const QString &errorString);
@ -21,6 +21,7 @@ signals:
void sigRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); void sigRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname);
void sigActivateToServer(const QString &_token); void sigActivateToServer(const QString &_token);
void sigDisconnectFromServer(); void sigDisconnectFromServer();
void notifyUserAboutUpdate();
private slots: private slots:
void slotConnected(); void slotConnected();
void readData(); void readData();

View file

@ -130,6 +130,7 @@ SettingsCache::SettingsCache()
if(!QFile(settingsPath+"global.ini").exists()) if(!QFile(settingsPath+"global.ini").exists())
translateLegacySettings(); translateLegacySettings();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
lang = settings->value("personal/lang").toString(); lang = settings->value("personal/lang").toString();
keepalive = settings->value("personal/keepalive", 5).toInt(); keepalive = settings->value("personal/keepalive", 5).toInt();
deckPath = settings->value("paths/decks").toString(); deckPath = settings->value("paths/decks").toString();
@ -620,4 +621,10 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
{ {
rememberGameSettings = _rememberGameSettings; rememberGameSettings = _rememberGameSettings;
settings->setValue("game/remembergamesettings", rememberGameSettings); settings->setValue("game/remembergamesettings", rememberGameSettings);
}
void SettingsCache::setNotifyAboutUpdate(int _notifyaboutupdate)
{
notifyAboutUpdates = _notifyaboutupdate;
settings->setValue("personal/updatenotification", notifyAboutUpdates);
} }

View file

@ -58,6 +58,7 @@ private:
QByteArray mainWindowGeometry; QByteArray mainWindowGeometry;
QString lang; QString lang;
QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath, themeName; QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates;
bool picDownload; bool picDownload;
bool picDownloadHq; bool picDownloadHq;
bool notificationsEnabled; bool notificationsEnabled;
@ -129,6 +130,7 @@ public:
bool getPicDownloadHq() const { return picDownloadHq; } bool getPicDownloadHq() const { return picDownloadHq; }
bool getNotificationsEnabled() const { return notificationsEnabled; } bool getNotificationsEnabled() const { return notificationsEnabled; }
bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; } bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; }
bool getNotifyAboutUpdates() const { return notifyAboutUpdates; }
bool getDoubleClickToPlay() const { return doubleClickToPlay; } bool getDoubleClickToPlay() const { return doubleClickToPlay; }
bool getPlayToStack() const { return playToStack; } bool getPlayToStack() const { return playToStack; }
@ -251,6 +253,7 @@ public slots:
void setSpectatorsCanTalk(const bool _spectatorsCanTalk); void setSpectatorsCanTalk(const bool _spectatorsCanTalk);
void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything);
void setRememberGameSettings(const bool _rememberGameSettings); void setRememberGameSettings(const bool _rememberGameSettings);
void setNotifyAboutUpdate(int _notifyaboutupdate);
}; };
extern SettingsCache *settingsCache; extern SettingsCache *settingsCache;

View file

@ -30,6 +30,7 @@
#include <QDateTime> #include <QDateTime>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QApplication> #include <QApplication>
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
#include <QtGui/qtextdocument.h> // for Qt::escape() #include <QtGui/qtextdocument.h> // for Qt::escape()
#endif #endif
@ -293,9 +294,23 @@ void MainWindow::serverTimeout()
actConnect(); actConnect();
} }
void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime) void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList<QString> missingFeatures)
{ {
switch (r) { switch (r) {
case Response::RespClientUpdateRequired: {
QString formatedMissingFeatures;
formatedMissingFeatures = "Missing Features: ";
for (int i = 0; i < missingFeatures.size(); ++i)
formatedMissingFeatures.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i) );
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setWindowTitle(tr("Failed Login"));
msgBox.setText(tr("Your client does not support features that the server requires, please update your client and try again."));
msgBox.setDetailedText(formatedMissingFeatures);
msgBox.exec();
break;
}
case Response::RespWrongPassword: case Response::RespWrongPassword:
QMessageBox::critical(this, tr("Error"), tr("Incorrect username or password. Please check your authentication information and try again.")); QMessageBox::critical(this, tr("Error"), tr("Incorrect username or password. Please check your authentication information and try again."));
break; break;
@ -561,13 +576,13 @@ MainWindow::MainWindow(QWidget *parent)
client = new RemoteClient; client = new RemoteClient;
connect(client, SIGNAL(connectionClosedEventReceived(const Event_ConnectionClosed &)), this, SLOT(processConnectionClosedEvent(const Event_ConnectionClosed &))); connect(client, SIGNAL(connectionClosedEventReceived(const Event_ConnectionClosed &)), this, SLOT(processConnectionClosedEvent(const Event_ConnectionClosed &)));
connect(client, SIGNAL(serverShutdownEventReceived(const Event_ServerShutdown &)), this, SLOT(processServerShutdownEvent(const Event_ServerShutdown &))); connect(client, SIGNAL(serverShutdownEventReceived(const Event_ServerShutdown &)), this, SLOT(processServerShutdownEvent(const Event_ServerShutdown &)));
connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32)), this, SLOT(loginError(Response::ResponseCode, QString, quint32))); connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32, QList<QString>)), this, SLOT(loginError(Response::ResponseCode, QString, quint32, QList<QString>)));
connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &))); connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &)));
connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout()));
connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus)));
connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int)));
connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection);
connect(client, SIGNAL(notifyUserAboutUpdate()), this, SLOT(notifyUserAboutUpdate()));
connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted())); connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted()));
connect(client, SIGNAL(registerAcceptedNeedsActivate()), this, SLOT(registerAcceptedNeedsActivate())); connect(client, SIGNAL(registerAcceptedNeedsActivate()), this, SLOT(registerAcceptedNeedsActivate()));
connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, SLOT(registerError(Response::ResponseCode, QString, quint32))); connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, SLOT(registerError(Response::ResponseCode, QString, quint32)));
@ -795,3 +810,8 @@ void MainWindow::refreshShortcuts()
aExit->setShortcuts(settingsCache->shortcuts().getShortcut("MainWindow/aExit")); aExit->setShortcuts(settingsCache->shortcuts().getShortcut("MainWindow/aExit"));
aCheckCardUpdates->setShortcuts(settingsCache->shortcuts().getShortcut("MainWindow/aCheckCardUpdates")); aCheckCardUpdates->setShortcuts(settingsCache->shortcuts().getShortcut("MainWindow/aCheckCardUpdates"));
} }
void MainWindow::notifyUserAboutUpdate()
{
QMessageBox::information(this, tr("Information"), tr("Your client appears to be missing features that the server supports.\nThis usually means that your client version is out of date,pleae check to see if there is a new client available for download."));
}

View file

@ -20,6 +20,7 @@
#ifndef WINDOW_H #ifndef WINDOW_H
#define WINDOW_H #define WINDOW_H
#include <QList>
#include <QMainWindow> #include <QMainWindow>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QProcess> #include <QProcess>
@ -42,7 +43,7 @@ private slots:
void processConnectionClosedEvent(const Event_ConnectionClosed &event); void processConnectionClosedEvent(const Event_ConnectionClosed &event);
void processServerShutdownEvent(const Event_ServerShutdown &event); void processServerShutdownEvent(const Event_ServerShutdown &event);
void serverTimeout(); void serverTimeout();
void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList<QString> missingFeatures);
void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime); void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime);
void activateError(); void activateError();
void socketError(const QString &errorStr); void socketError(const QString &errorStr);
@ -53,7 +54,7 @@ private slots:
void activateAccepted(); void activateAccepted();
void localGameEnded(); void localGameEnded();
void pixmapCacheSizeChanged(int newSizeInMBs); void pixmapCacheSizeChanged(int newSizeInMBs);
void notifyUserAboutUpdate();
void actConnect(); void actConnect();
void actDisconnect(); void actDisconnect();
void actSinglePlayer(); void actSinglePlayer();

View file

@ -7,6 +7,7 @@ add_subdirectory(pb)
SET(common_SOURCES SET(common_SOURCES
decklist.cpp decklist.cpp
featureset.cpp
get_pb_extension.cpp get_pb_extension.cpp
rng_abstract.cpp rng_abstract.cpp
rng_sfmt.cpp rng_sfmt.cpp

55
common/featureset.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "featureset.h"
#include <QMap>
#include <QDebug>
FeatureSet::FeatureSet()
{
}
QMap<QString, bool> FeatureSet::getDefaultFeatureList() {
initalizeFeatureList(featureList);
return featureList;
}
void FeatureSet::initalizeFeatureList(QMap<QString, bool> &featureList){
featureList.insert("client_id", false);
featureList.insert("client_ver", false);
featureList.insert("feature_set", false);
}
void FeatureSet::enableRequiredFeature(QMap<QString, bool> &featureList, QString featureName){
if (featureList.contains(featureName))
featureList.insert(featureName,true);
}
void FeatureSet::disableRequiredFeature(QMap<QString, bool> &featureList, QString featureName){
if (featureList.contains(featureName))
featureList.insert(featureName,false);
}
QMap<QString, bool> FeatureSet::addFeature(QMap<QString, bool> &featureList, QString featureName, bool isFeatureRequired){
featureList.insert(featureName,isFeatureRequired);
return featureList;
}
QMap<QString, bool> FeatureSet::identifyMissingFeatures(QMap<QString, bool> suppliedFeatures, QMap<QString, bool> requiredFeatures){
QMap<QString, bool> missingList;
QMap<QString, bool>::iterator i;
for (i = requiredFeatures.begin(); i != requiredFeatures.end(); ++i) {
if (!suppliedFeatures.contains(i.key())) {
missingList.insert(i.key(), i.value());
}
}
return missingList;
}
bool FeatureSet::isRequiredFeaturesMissing(QMap<QString, bool> suppliedFeatures, QMap<QString, bool> requiredFeatures) {
QMap<QString, bool>::iterator i;
for (i = requiredFeatures.begin(); i != requiredFeatures.end(); ++i) {
if (i.value() && suppliedFeatures.contains(i.key())) {
return true;
}
}
return false;
}

24
common/featureset.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef FEATURESET_H
#define FEATURESET_H
#include <QMap>
#include <QSet>
#include <QString>
class FeatureSet
{
public:
FeatureSet();
QMap<QString, bool> getDefaultFeatureList();
void initalizeFeatureList(QMap<QString, bool> &featureList);
void enableRequiredFeature(QMap<QString, bool> &featureList, QString featureName);
void disableRequiredFeature(QMap<QString, bool> &featureList, QString featureName);
QMap<QString, bool> addFeature(QMap<QString, bool> &featureList, QString featureName, bool isFeatureRequired);
QMap<QString, bool> identifyMissingFeatures(QMap<QString, bool> featureListToCheck, QMap<QString, bool> featureListToCompareTo);
bool isRequiredFeaturesMissing(QMap<QString, bool> featureListToCheck, QMap<QString, bool> featureListToCompareTo);
private:
QMap<QString, bool> featureList;
};
#endif // FEEATURESET_H

View file

@ -37,6 +37,7 @@ message Response {
RespActivationFailed = 32; // Server didn't accept a reg user activation token RespActivationFailed = 32; // Server didn't accept a reg user activation token
RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation
RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access
RespClientUpdateRequired = 35; // Client is missing features that the server is requiring
} }
enum ResponseType { enum ResponseType {
JOIN_ROOM = 1000; JOIN_ROOM = 1000;

View file

@ -11,4 +11,5 @@ message Response_Login {
repeated ServerInfo_User ignore_list = 3; repeated ServerInfo_User ignore_list = 3;
optional string denied_reason_str = 4; optional string denied_reason_str = 4;
optional uint64 denied_end_time = 5; optional uint64 denied_end_time = 5;
repeated string missing_features = 6;
} }

View file

@ -46,6 +46,7 @@ message Command_Login {
optional string password = 2; optional string password = 2;
optional string clientid = 3; optional string clientid = 3;
optional string clientver = 4; optional string clientver = 4;
repeated string clientfeatures = 5;
} }
message Command_Message { message Command_Message {

View file

@ -32,6 +32,7 @@
#include "pb/session_event.pb.h" #include "pb/session_event.pb.h"
#include "pb/event_connection_closed.pb.h" #include "pb/event_connection_closed.pb.h"
#include "pb/isl_message.pb.h" #include "pb/isl_message.pb.h"
#include "featureset.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QThread> #include <QThread>
#include <QDebug> #include <QDebug>

View file

@ -51,10 +51,10 @@ public:
Server_AbstractUserInterface *findUser(const QString &userName) const; Server_AbstractUserInterface *findUser(const QString &userName) const;
const QMap<QString, Server_ProtocolHandler *> &getUsers() const { return users; } const QMap<QString, Server_ProtocolHandler *> &getUsers() const { return users; }
const QMap<qint64, Server_ProtocolHandler *> &getUsersBySessionId() const { return usersBySessionId; } const QMap<qint64, Server_ProtocolHandler *> &getUsersBySessionId() const { return usersBySessionId; }
virtual QMap<QString, bool> getServerRequiredFeatureList() const { return QMap<QString, bool>(); }
void addClient(Server_ProtocolHandler *player); void addClient(Server_ProtocolHandler *player);
void removeClient(Server_ProtocolHandler *player); void removeClient(Server_ProtocolHandler *player);
virtual QString getLoginMessage() const { return QString(); } virtual QString getLoginMessage() const { return QString(); }
virtual bool permitUnregisteredUsers() const { return true; } virtual bool permitUnregisteredUsers() const { return true; }
virtual bool getGameShouldPing() const { return false; } virtual bool getGameShouldPing() const { return false; }
virtual bool getClientIdRequired() const { return false; } virtual bool getClientIdRequired() const { return false; }
@ -94,6 +94,7 @@ private:
mutable QReadWriteLock persistentPlayersLock; mutable QReadWriteLock persistentPlayersLock;
int nextLocalGameId; int nextLocalGameId;
QMutex nextLocalGameIdMutex; QMutex nextLocalGameIdMutex;
protected slots: protected slots:
void externalUserJoined(const ServerInfo_User &userInfo); void externalUserJoined(const ServerInfo_User &userInfo);
void externalUserLeft(const QString &userName); void externalUserLeft(const QString &userName);

View file

@ -19,6 +19,8 @@
#include "pb/event_game_joined.pb.h" #include "pb/event_game_joined.pb.h"
#include "pb/event_room_say.pb.h" #include "pb/event_room_say.pb.h"
#include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.h>
#include "featureset.h"
Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent) Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent)
: QObject(parent), : QObject(parent),
@ -382,10 +384,32 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
{ {
QString userName = QString::fromStdString(cmd.user_name()).simplified(); QString userName = QString::fromStdString(cmd.user_name()).simplified();
QString clientId = QString::fromStdString(cmd.clientid()).simplified(); QString clientId = QString::fromStdString(cmd.clientid()).simplified();
if (userName.isEmpty() || (userInfo != 0)) if (userName.isEmpty() || (userInfo != 0))
return Response::RespContextError; return Response::RespContextError;
// check client feature set against server feature set
FeatureSet features;
QMap<QString, bool> receivedClientFeatures;
QMap<QString, bool> missingClientFeatures;
for (int i = 0; i < cmd.clientfeatures().size(); ++i)
receivedClientFeatures.insert(QString::fromStdString(cmd.clientfeatures(i)).simplified(), false);
missingClientFeatures = features.identifyMissingFeatures(receivedClientFeatures, server->getServerRequiredFeatureList());
if (!missingClientFeatures.isEmpty()) {
if (features.isRequiredFeaturesMissing(missingClientFeatures, server->getServerRequiredFeatureList())) {
Response_Login *re = new Response_Login;
re->set_denied_reason_str("Client upgrade required");
QMap<QString, bool>::iterator i;
for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i)
re->add_missing_features(i.key().toStdString().c_str());
rc.setResponseExtension(re);
return Response::RespClientUpdateRequired;
}
}
QString reasonStr; QString reasonStr;
int banSecondsLeft = 0; int banSecondsLeft = 0;
AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft, clientId); AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft, clientId);
@ -430,6 +454,13 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); re->add_ignore_list()->CopyFrom(ignoreIterator.next().value());
} }
// return to client any missing features the server has that the client does not
if (!missingClientFeatures.isEmpty()) {
QMap<QString, bool>::iterator i;
for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i)
re->add_missing_features(i.key().toStdString().c_str());
}
joinPersistentGames(rc); joinPersistentGames(rc);
rc.setResponseExtension(re); rc.setResponseExtension(re);

View file

@ -8,11 +8,13 @@
#include "pb/response.pb.h" #include "pb/response.pb.h"
#include "pb/server_message.pb.h" #include "pb/server_message.pb.h"
class Features;
class Server_DatabaseInterface; class Server_DatabaseInterface;
class Server_Player; class Server_Player;
class ServerInfo_User; class ServerInfo_User;
class Server_Room; class Server_Room;
class QTimer; class QTimer;
class FeatureSet;
class ServerMessage; class ServerMessage;
class Response; class Response;

View file

@ -50,6 +50,12 @@ max_player_inactivity_time=15
' require that clients report the client ID in order to log into the server. Default is false ' require that clients report the client ID in order to log into the server. Default is false
requireclientid=false requireclientid=false
; You can limit the types of clients that connect to the server by requiring different features be available
; on the client. This setting can contain a comma-seperated list of features. if any of the features
; listed in this line are not available on the client the client will be denied access to the server upon
; attempting to log in. Example: "client_id,client_ver"
requiredfeatures=""
[authentication] [authentication]
; Servatrice can authenticate users connecting. It currently supports 3 different authentication methods: ; Servatrice can authenticate users connecting. It currently supports 3 different authentication methods:

View file

@ -198,6 +198,7 @@ int main(int argc, char *argv[])
#else #else
qInstallMessageHandler(myMessageOutput); qInstallMessageHandler(myMessageOutput);
#endif #endif
retval = app.exec(); retval = app.exec();
std::cerr << "Server quit." << std::endl; std::cerr << "Server quit." << std::endl;

View file

@ -38,6 +38,7 @@
#include "pb/event_server_message.pb.h" #include "pb/event_server_message.pb.h"
#include "pb/event_server_shutdown.pb.h" #include "pb/event_server_shutdown.pb.h"
#include "pb/event_connection_closed.pb.h" #include "pb/event_connection_closed.pb.h"
#include "featureset.h"
Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent) Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent)
: QTcpServer(parent), : QTcpServer(parent),
@ -179,6 +180,16 @@ bool Servatrice::initServer()
if (registrationEnabled) if (registrationEnabled)
qDebug() << "Require email address to register: " << requireEmailForRegistration; qDebug() << "Require email address to register: " << requireEmailForRegistration;
FeatureSet features;
features.initalizeFeatureList(serverRequiredFeatureList);
requiredFeatures = settingsCache->value("server/requiredfeatures","").toString();
QStringList listReqFeatures = requiredFeatures.split(",", QString::SkipEmptyParts);
if (!listReqFeatures.isEmpty())
foreach(QString reqFeature, listReqFeatures)
features.enableRequiredFeature(serverRequiredFeatureList,reqFeature);
qDebug() << "Required client features: " << serverRequiredFeatureList;
QString dbTypeStr = settingsCache->value("database/type").toString(); QString dbTypeStr = settingsCache->value("database/type").toString();
if (dbTypeStr == "mysql") if (dbTypeStr == "mysql")
databaseType = DatabaseMySql; databaseType = DatabaseMySql;

View file

@ -30,6 +30,7 @@
#include <QMetaType> #include <QMetaType>
#include "server.h" #include "server.h"
Q_DECLARE_METATYPE(QSqlDatabase) Q_DECLARE_METATYPE(QSqlDatabase)
class QSqlQuery; class QSqlQuery;
@ -41,6 +42,7 @@ class Servatrice_ConnectionPool;
class Servatrice_DatabaseInterface; class Servatrice_DatabaseInterface;
class ServerSocketInterface; class ServerSocketInterface;
class IslInterface; class IslInterface;
class FeatureSet;
class Servatrice_GameServer : public QTcpServer { class Servatrice_GameServer : public QTcpServer {
Q_OBJECT Q_OBJECT
@ -109,6 +111,8 @@ private:
mutable QMutex loginMessageMutex; mutable QMutex loginMessageMutex;
QString loginMessage; QString loginMessage;
QString dbPrefix; QString dbPrefix;
QString requiredFeatures;
QMap<QString, bool> serverRequiredFeatureList;
Servatrice_DatabaseInterface *servatriceDatabaseInterface; Servatrice_DatabaseInterface *servatriceDatabaseInterface;
int serverId; int serverId;
int uptime; int uptime;
@ -134,8 +138,10 @@ public:
Servatrice(QObject *parent = 0); Servatrice(QObject *parent = 0);
~Servatrice(); ~Servatrice();
bool initServer(); bool initServer();
QMap<QString, bool> getServerRequiredFeatureList() const { return serverRequiredFeatureList; }
QString getServerName() const { return serverName; } QString getServerName() const { return serverName; }
QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; } QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; }
QString getRequiredFeatures() const { return requiredFeatures; }
bool permitUnregisteredUsers() const { return authenticationMethod != AuthenticationNone; } bool permitUnregisteredUsers() const { return authenticationMethod != AuthenticationNone; }
bool getGameShouldPing() const { return true; } bool getGameShouldPing() const { return true; }
bool getClientIdRequired() const { return clientIdRequired; } bool getClientIdRequired() const { return clientIdRequired; }