Major Directory Refactoring (#5118)

* refactored cardzone.cpp, added doc and changed if to switch case

* started moving every files into different folders

* remove undercase to match with other files naming convention

* refactored dialog files

* ran format.sh

* refactored client/tabs folder

* refactored client/tabs folder

* refactored client/tabs folder

* refactored client folder

* refactored carddbparser

* refactored dialogs

* Create sonar-project.properties

temporary file for lint

* Create build.yml

temporary file for lint

* removed all files from root directory

* removed all files from root directory

* added current branch to workflow

* fixed most broken import

* fixed issues while renaming files

* fixed oracle importer

* fixed dbconverter

* updated translations

* made sub-folders for client

* removed linter

* removed linter folder

* fixed oracle import

* revert card_zone documentation

* renamed db parser files name and deck_view imports

* fixed dlg file issue

* ran format file and fixed test file

* fixed carddb test files

* moved player folder in game

* updated translations and format files

* fixed peglib import

* format cmake files

* removing vcpkg to try to add it back later

* tried fixing vcpkg file

* renamed filter to filters and moved database parser to cards folder

* reverted translation files

* reverted oracle translated

* Update cockatrice/src/dialogs/dlg_register.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Update cockatrice/src/client/ui/window_main.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* removed empty line at file start

* removed useless include from tab_supervisor.cpp

* refactored cardzone.cpp, added doc and changed if to switch case

* started moving every files into different folders

* remove undercase to match with other files naming convention

* refactored dialog files

* ran format.sh

* refactored client/tabs folder

* refactored client folder

* refactored carddbparser

* refactored dialogs

* removed all files from root directory

* Create sonar-project.properties

temporary file for lint

* Create build.yml

temporary file for lint

* added current branch to workflow

* fixed most broken import

* fixed issues while renaming files

* fixed oracle importer

* fixed dbconverter

* updated translations

* made sub-folders for client

* removed linter

* removed linter folder

* fixed oracle import

* revert card_zone documentation

* renamed db parser files name and deck_view imports

* fixed dlg file issue

* ran format file and fixed test file

* fixed carddb test files

* moved player folder in game

* updated translations and format files

* fixed peglib import

* reverted translation files

* format cmake files

* removing vcpkg to try to add it back later

* tried fixing vcpkg file

* pre-updating of cockatrice changes

* removed empty line at file start

* reverted oracle translated

* Update cockatrice/src/dialogs/dlg_register.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Update cockatrice/src/client/ui/window_main.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* removed useless include from tab_supervisor.cpp

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
This commit is contained in:
LunaticCat 2024-10-20 16:11:35 +02:00 committed by GitHub
parent d1e0f9dfc5
commit fa999880ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
261 changed files with 735 additions and 729 deletions

View file

@ -0,0 +1,729 @@
#include "remote_client.h"
#include "../../main.h"
#include "../../settings/cache_settings.h"
#include "../pending_command.h"
#include "debug_pb_message.h"
#include "passwordhasher.h"
#include "pb/event_server_identification.pb.h"
#include "pb/response_activate.pb.h"
#include "pb/response_forgotpasswordrequest.pb.h"
#include "pb/response_login.pb.h"
#include "pb/response_password_salt.pb.h"
#include "pb/response_register.pb.h"
#include "pb/server_message.pb.h"
#include "pb/session_commands.pb.h"
#include "version_string.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QHostAddress>
#include <QHostInfo>
#include <QList>
#include <QThread>
#include <QTimer>
#include <QWebSocket>
static const unsigned int protocolVersion = 14;
RemoteClient::RemoteClient(QObject *parent)
: AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false),
usingWebSocket(false), messageLength(0), hashedPassword()
{
clearNewClientFeatures();
maxTimeout = SettingsCache::instance().getTimeOut();
int keepalive = SettingsCache::instance().getKeepAlive();
timer = new QTimer(this);
timer->setInterval(keepalive * 1000);
connect(timer, SIGNAL(timeout()), this, SLOT(ping()));
socket = new QTcpSocket(this);
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
connect(socket, SIGNAL(connected()), this, SLOT(slotConnected()));
connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
connect(socket, &QTcpSocket::errorOccurred, this, &RemoteClient::slotSocketError);
#else
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(slotSocketError(QAbstractSocket::SocketError)));
#endif
websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived);
connect(websocket, &QWebSocket::connected, this, &RemoteClient::slotConnected);
connect(websocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(slotWebSocketError(QAbstractSocket::SocketError)));
connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this,
SLOT(processServerIdentificationEvent(const Event_ServerIdentification &)));
connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this,
SLOT(processConnectionClosedEvent(Event_ConnectionClosed)));
connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this,
SLOT(doConnectToServer(QString, unsigned int, QString, QString)));
connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer()));
connect(this, SIGNAL(sigRegisterToServer(QString, unsigned int, QString, QString, QString, QString, QString)), this,
SLOT(doRegisterToServer(QString, unsigned int, QString, QString, QString, QString, QString)));
connect(this, SIGNAL(sigActivateToServer(QString)), this, SLOT(doActivateToServer(QString)));
connect(this, SIGNAL(sigRequestForgotPasswordToServer(QString, unsigned int, QString)), this,
SLOT(doRequestForgotPasswordToServer(QString, unsigned int, QString)));
connect(this, SIGNAL(sigSubmitForgotPasswordResetToServer(QString, unsigned int, QString, QString, QString)), this,
SLOT(doSubmitForgotPasswordResetToServer(QString, unsigned int, QString, QString, QString)));
connect(this, SIGNAL(sigSubmitForgotPasswordChallengeToServer(QString, unsigned int, QString, QString)), this,
SLOT(doSubmitForgotPasswordChallengeToServer(QString, unsigned int, QString, QString)));
}
RemoteClient::~RemoteClient()
{
doDisconnectFromServer();
thread()->quit();
}
void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/)
{
QString errorString = socket->errorString();
doDisconnectFromServer();
emit socketError(errorString);
}
void RemoteClient::slotWebSocketError(QAbstractSocket::SocketError /*error*/)
{
QString errorString = websocket->errorString();
if (getStatus() != ClientStatus::StatusDisconnected) {
doDisconnectFromServer();
emit socketError(errorString);
}
}
void RemoteClient::slotConnected()
{
timeRunning = lastDataReceived = 0;
timer->start();
if (!usingWebSocket) {
// dirty hack to be compatible with v14 server
sendCommandContainer(CommandContainer());
getNewCmdId();
// end of hack
}
}
void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event)
{
if (event.protocol_version() != protocolVersion) {
emit protocolVersionMismatch(protocolVersion, event.protocol_version());
setStatus(StatusDisconnecting);
return;
}
serverSupportsPasswordHash = event.server_options() & Event_ServerIdentification::SupportsPasswordHash;
if (getStatus() == StatusRequestingForgotPassword) {
Command_ForgotPasswordRequest cmdForgotPasswordRequest;
cmdForgotPasswordRequest.set_user_name(userName.toStdString());
cmdForgotPasswordRequest.set_clientid(getSrvClientID(lastHostname).toStdString());
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordRequest);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(requestForgotPasswordResponse(Response)));
sendCommand(pend);
return;
}
if (getStatus() == StatusSubmitForgotPasswordReset) {
Command_ForgotPasswordReset cmdForgotPasswordReset;
cmdForgotPasswordReset.set_user_name(userName.toStdString());
cmdForgotPasswordReset.set_clientid(getSrvClientID(lastHostname).toStdString());
cmdForgotPasswordReset.set_token(token.toStdString());
if (!password.isEmpty() && serverSupportsPasswordHash) {
auto passwordSalt = PasswordHasher::generateRandomSalt();
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString());
} else if (!password.isEmpty()) {
qWarning() << "using plain text password to reset password";
cmdForgotPasswordReset.set_new_password(password.toStdString());
}
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordReset);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(submitForgotPasswordResetResponse(Response)));
sendCommand(pend);
return;
}
if (getStatus() == StatusSubmitForgotPasswordChallenge) {
Command_ForgotPasswordChallenge cmdForgotPasswordChallenge;
cmdForgotPasswordChallenge.set_user_name(userName.toStdString());
cmdForgotPasswordChallenge.set_clientid(getSrvClientID(lastHostname).toStdString());
cmdForgotPasswordChallenge.set_email(email.toStdString());
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordChallenge);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(submitForgotPasswordChallengeResponse(Response)));
sendCommand(pend);
return;
}
if (getStatus() == StatusRegistering) {
Command_Register cmdRegister;
cmdRegister.set_user_name(userName.toStdString());
if (!password.isEmpty() && serverSupportsPasswordHash) {
auto passwordSalt = PasswordHasher::generateRandomSalt();
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
cmdRegister.set_hashed_password(hashedPassword.toStdString());
} else if (!password.isEmpty()) {
qWarning() << "using plain text password to register";
cmdRegister.set_password(password.toStdString());
}
cmdRegister.set_email(email.toStdString());
cmdRegister.set_country(country.toStdString());
cmdRegister.set_real_name(realName.toStdString());
cmdRegister.set_clientid(getSrvClientID(lastHostname).toStdString());
PendingCommand *pend = prepareSessionCommand(cmdRegister);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response)));
sendCommand(pend);
return;
}
if (getStatus() == StatusActivating) {
Command_Activate cmdActivate;
cmdActivate.set_user_name(userName.toStdString());
cmdActivate.set_token(token.toStdString());
cmdActivate.set_clientid(getSrvClientID(lastHostname).toStdString());
PendingCommand *pend = prepareSessionCommand(cmdActivate);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(activateResponse(Response)));
sendCommand(pend);
return;
}
doLogin();
}
void RemoteClient::doRequestPasswordSalt()
{
setStatus(StatusGettingPasswordSalt);
Command_RequestPasswordSalt cmdRqSalt;
cmdRqSalt.set_user_name(userName.toStdString());
PendingCommand *pend = prepareSessionCommand(cmdRqSalt);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(passwordSaltResponse(Response)));
sendCommand(pend);
}
Command_Login RemoteClient::generateCommandLogin()
{
Command_Login cmdLogin;
cmdLogin.set_user_name(userName.toStdString());
cmdLogin.set_clientid(getSrvClientID(lastHostname).toStdString());
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());
}
return cmdLogin;
}
void RemoteClient::doLogin()
{
if (!password.isEmpty() && serverSupportsPasswordHash) {
// TODO store and log in using stored hashed password
if (hashedPassword.isEmpty()) {
doRequestPasswordSalt(); // ask salt to create hashedPassword, then log in
} else {
doHashedLogin(); // log in using hashed password instead
}
} else {
// TODO add setting for client to reject unhashed logins
setStatus(StatusLoggingIn);
Command_Login cmdLogin = generateCommandLogin();
if (!password.isEmpty()) {
qWarning() << "using plain text password to log in";
cmdLogin.set_password(password.toStdString());
}
PendingCommand *pend = prepareSessionCommand(cmdLogin);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response)));
sendCommand(pend);
}
}
void RemoteClient::doHashedLogin()
{
setStatus(StatusLoggingIn);
Command_Login cmdLogin = generateCommandLogin();
cmdLogin.set_hashed_password(hashedPassword.toStdString());
PendingCommand *pend = prepareSessionCommand(cmdLogin);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response)));
sendCommand(pend);
}
void RemoteClient::processConnectionClosedEvent(const Event_ConnectionClosed & /*event*/)
{
doDisconnectFromServer();
}
void RemoteClient::passwordSaltResponse(const Response &response)
{
if (response.response_code() == Response::RespOk) {
const Response_PasswordSalt &resp = response.GetExtension(Response_PasswordSalt::ext);
auto passwordSalt = QString::fromStdString(resp.password_salt());
if (passwordSalt.isEmpty()) { // the server does not recognize the user but allows them to enter unregistered
password.clear(); // the password will not be used
doLogin();
} else {
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
doHashedLogin();
}
} else if (response.response_code() != Response::RespNotConnected) {
emit loginError(response.response_code(), {}, 0, {});
}
}
void RemoteClient::loginResponse(const Response &response)
{
const Response_Login &resp = response.GetExtension(Response_Login::ext);
QString possibleMissingFeatures;
if (resp.missing_features_size() > 0) {
for (int i = 0; i < resp.missing_features_size(); ++i)
possibleMissingFeatures.append("," + QString::fromStdString(resp.missing_features(i)));
}
if (response.response_code() == Response::RespOk) {
setStatus(StatusLoggedIn);
emit userInfoChanged(resp.user_info());
QList<ServerInfo_User> buddyList;
for (int i = resp.buddy_list_size() - 1; i >= 0; --i)
buddyList.append(resp.buddy_list(i));
emit buddyListReceived(buddyList);
QList<ServerInfo_User> ignoreList;
for (int i = resp.ignore_list_size() - 1; i >= 0; --i)
ignoreList.append(resp.ignore_list(i));
emit ignoreListReceived(ignoreList);
if (newMissingFeatureFound(possibleMissingFeatures) && resp.missing_features_size() > 0 &&
SettingsCache::instance().getNotifyAboutUpdates()) {
SettingsCache::instance().setKnownMissingFeatures(possibleMissingFeatures);
emit notifyUserAboutUpdate();
}
} else if (response.response_code() != Response::RespNotConnected) {
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()),
static_cast<quint32>(resp.denied_end_time()), missingFeatures);
setStatus(StatusDisconnecting);
}
}
void RemoteClient::registerResponse(const Response &response)
{
const Response_Register &resp = response.GetExtension(Response_Register::ext);
switch (response.response_code()) {
case Response::RespRegistrationAccepted:
emit registerAccepted();
doLogin();
break;
case Response::RespRegistrationAcceptedNeedsActivation:
emit registerAcceptedNeedsActivate();
doLogin();
break;
default:
emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
static_cast<quint32>(resp.denied_end_time()));
setStatus(StatusDisconnecting);
doDisconnectFromServer();
break;
}
}
void RemoteClient::activateResponse(const Response &response)
{
if (response.response_code() == Response::RespActivationAccepted) {
emit activateAccepted();
doLogin();
} else {
emit activateError();
}
}
void RemoteClient::readData()
{
lastDataReceived = timeRunning;
QByteArray data = socket->readAll();
inputBuffer.append(data);
do {
if (!messageInProgress) {
if (inputBuffer.size() >= 4) {
// dirty hack to be compatible with v14 server that sends 60 bytes of garbage at the beginning
if (!handshakeStarted) {
handshakeStarted = true;
if (inputBuffer.startsWith("<?xm")) {
messageInProgress = true;
messageLength = 60;
}
} else {
// end of hack
messageLength = (((quint32)(unsigned char)inputBuffer[0]) << 24) +
(((quint32)(unsigned char)inputBuffer[1]) << 16) +
(((quint32)(unsigned char)inputBuffer[2]) << 8) +
((quint32)(unsigned char)inputBuffer[3]);
inputBuffer.remove(0, 4);
messageInProgress = true;
}
} else
return;
}
if (inputBuffer.size() < messageLength)
return;
ServerMessage newServerMessage;
newServerMessage.ParseFromArray(inputBuffer.data(), messageLength);
#ifdef QT_DEBUG
qDebug().noquote() << "IN" << getSafeDebugString(newServerMessage);
#endif
inputBuffer.remove(0, messageLength);
messageInProgress = false;
processProtocolItem(newServerMessage);
if (getStatus() == StatusDisconnecting) // use thread-safe getter
doDisconnectFromServer();
} while (!inputBuffer.isEmpty());
}
void RemoteClient::websocketMessageReceived(const QByteArray &message)
{
lastDataReceived = timeRunning;
ServerMessage newServerMessage;
newServerMessage.ParseFromArray(message.data(), message.length());
#ifdef QT_DEBUG
qDebug().noquote() << "IN" << getSafeDebugString(newServerMessage);
#endif
processProtocolItem(newServerMessage);
}
void RemoteClient::sendCommandContainer(const CommandContainer &cont)
{
#if GOOGLE_PROTOBUF_VERSION > 3001000
auto size = static_cast<unsigned int>(cont.ByteSizeLong());
#else
auto size = static_cast<unsigned int>(cont.ByteSize());
#endif
#ifdef QT_DEBUG
qDebug().noquote() << "OUT" << getSafeDebugString(cont);
#endif
QByteArray buf;
if (usingWebSocket) {
buf.resize(size);
cont.SerializeToArray(buf.data(), size);
websocket->sendBinaryMessage(buf);
} else {
buf.resize(size + 4);
cont.SerializeToArray(buf.data() + 4, size);
buf.data()[3] = (unsigned char)size;
buf.data()[2] = (unsigned char)(size >> 8);
buf.data()[1] = (unsigned char)(size >> 16);
buf.data()[0] = (unsigned char)(size >> 24);
socket->write(buf);
}
}
void RemoteClient::connectToHost(const QString &hostname, unsigned int port)
{
usingWebSocket = port == 443 || port == 80 || port == 4748 || port == 8080;
if (usingWebSocket) {
QUrl url(QString("%1://%2:%3/servatrice").arg(port == 443 ? "wss" : "ws").arg(hostname).arg(port));
websocket->open(url);
} else {
socket->connectToHost(hostname, static_cast<quint16>(port));
}
}
void RemoteClient::doConnectToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password)
{
doDisconnectFromServer();
userName = _userName;
password = _password;
lastHostname = hostname;
lastPort = port;
hashedPassword.clear();
connectToHost(hostname, port);
setStatus(StatusConnecting);
}
void RemoteClient::doRegisterToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password,
const QString &_email,
const QString &_country,
const QString &_realname)
{
doDisconnectFromServer();
userName = _userName;
password = _password;
email = _email;
country = _country;
realName = _realname;
lastHostname = hostname;
lastPort = port;
hashedPassword.clear();
connectToHost(hostname, port);
setStatus(StatusRegistering);
}
void RemoteClient::doActivateToServer(const QString &_token)
{
doDisconnectFromServer();
token = _token.trimmed();
connectToHost(lastHostname, lastPort);
setStatus(StatusActivating);
}
void RemoteClient::doDisconnectFromServer()
{
timer->stop();
messageInProgress = false;
handshakeStarted = false;
messageLength = 0;
QList<PendingCommand *> pc = pendingCommands.values();
for (const auto &i : pc) {
Response response;
response.set_response_code(Response::RespNotConnected);
response.set_cmd_id(i->getCommandContainer().cmd_id());
i->processResponse(response);
delete i;
}
pendingCommands.clear();
setStatus(StatusDisconnected);
if (websocket->isValid())
websocket->close();
socket->close();
}
void RemoteClient::ping()
{
QMutableMapIterator<int, PendingCommand *> i(pendingCommands);
while (i.hasNext()) {
PendingCommand *pend = i.next().value();
if (pend->tick() > maxTimeout) {
i.remove();
pend->deleteLater();
}
}
int maxTime = timeRunning - lastDataReceived;
emit maxPingTime(maxTime, maxTimeout);
if (maxTime >= maxTimeout) {
disconnectFromServer();
emit serverTimeout();
} else {
sendCommand(prepareSessionCommand(Command_Ping()));
++timeRunning;
}
}
void RemoteClient::connectToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password)
{
emit sigConnectToServer(hostname, port, _userName, _password);
}
void RemoteClient::registerToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password,
const QString &_email,
const QString &_country,
const QString &_realname)
{
emit sigRegisterToServer(hostname, port, _userName, _password, _email, _country, _realname);
}
void RemoteClient::activateToServer(const QString &_token)
{
emit sigActivateToServer(_token.trimmed());
}
void RemoteClient::disconnectFromServer()
{
SettingsCache::instance().servers().setAutoConnect(false);
emit sigDisconnectFromServer();
}
QString RemoteClient::getSrvClientID(const QString &_hostname)
{
QString srvClientID = SettingsCache::instance().getClientID();
QHostInfo hostInfo = QHostInfo::fromName(_hostname);
if (!hostInfo.error()) {
QHostAddress hostAddress = hostInfo.addresses().first();
srvClientID += hostAddress.toString();
} else {
qWarning() << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]";
srvClientID += _hostname;
}
QString uniqueServerClientID =
QCryptographicHash::hash(srvClientID.toUtf8(), QCryptographicHash::Sha1).toHex().right(15);
return uniqueServerClientID;
}
bool RemoteClient::newMissingFeatureFound(const QString &_serversMissingFeatures)
{
bool newMissingFeature = false;
QStringList serversMissingFeaturesList = _serversMissingFeatures.split(",");
for (const QString &feature : serversMissingFeaturesList) {
if (!feature.isEmpty()) {
if (!SettingsCache::instance().getKnownMissingFeatures().contains(feature))
return true;
}
}
return newMissingFeature;
}
void RemoteClient::clearNewClientFeatures()
{
QString newKnownMissingFeatures;
QStringList existingKnownMissingFeatures = SettingsCache::instance().getKnownMissingFeatures().split(",");
for (const QString &existingKnownFeature : existingKnownMissingFeatures) {
if (!existingKnownFeature.isEmpty()) {
if (!clientFeatures.contains(existingKnownFeature))
newKnownMissingFeatures.append("," + existingKnownFeature);
}
}
SettingsCache::instance().setKnownMissingFeatures(newKnownMissingFeatures);
}
void RemoteClient::requestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName)
{
emit sigRequestForgotPasswordToServer(hostname, port, _userName);
}
void RemoteClient::submitForgotPasswordResetToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_token,
const QString &_newpassword)
{
emit sigSubmitForgotPasswordResetToServer(hostname, port, _userName, _token.trimmed(), _newpassword);
}
void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName)
{
doDisconnectFromServer();
userName = _userName;
lastHostname = hostname;
lastPort = port;
connectToHost(lastHostname, lastPort);
setStatus(StatusRequestingForgotPassword);
}
void RemoteClient::requestForgotPasswordResponse(const Response &response)
{
const Response_ForgotPasswordRequest &resp = response.GetExtension(Response_ForgotPasswordRequest::ext);
if (response.response_code() == Response::RespOk) {
if (resp.challenge_email()) {
emit sigPromptForForgotPasswordChallenge();
} else
emit sigPromptForForgotPasswordReset();
} else
emit sigForgotPasswordError();
doDisconnectFromServer();
}
void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_token,
const QString &_newpassword)
{
doDisconnectFromServer();
userName = _userName;
lastHostname = hostname;
lastPort = port;
token = _token.trimmed();
password = _newpassword;
hashedPassword.clear();
connectToHost(lastHostname, lastPort);
setStatus(StatusSubmitForgotPasswordReset);
}
void RemoteClient::submitForgotPasswordResetResponse(const Response &response)
{
if (response.response_code() == Response::RespOk) {
emit sigForgotPasswordSuccess();
} else
emit sigForgotPasswordError();
doDisconnectFromServer();
}
void RemoteClient::submitForgotPasswordChallengeToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_email)
{
emit sigSubmitForgotPasswordChallengeToServer(hostname, port, _userName, _email);
}
void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_email)
{
doDisconnectFromServer();
userName = _userName;
lastHostname = hostname;
lastPort = port;
email = _email;
connectToHost(lastHostname, lastPort);
setStatus(StatusSubmitForgotPasswordChallenge);
}
void RemoteClient::submitForgotPasswordChallengeResponse(const Response &response)
{
if (response.response_code() == Response::RespOk) {
emit sigPromptForForgotPasswordReset();
} else
emit sigForgotPasswordError();
doDisconnectFromServer();
}

View file

@ -0,0 +1,148 @@
#ifndef REMOTECLIENT_H
#define REMOTECLIENT_H
#include "../../client/game_logic/abstract_client.h"
#include "pb/commands.pb.h"
#include <QTcpSocket>
#include <QWebSocket>
class QTimer;
class RemoteClient : public AbstractClient
{
Q_OBJECT
signals:
void maxPingTime(int seconds, int maxSeconds);
void serverTimeout();
void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime, QList<QString> missingFeatures);
void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime);
void activateError();
void socketError(const QString &errorString);
void protocolVersionMismatch(int clientVersion, int serverVersion);
void
sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
void sigRegisterToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password,
const QString &_email,
const QString &_country,
const QString &_realname);
void sigActivateToServer(const QString &_token);
void sigDisconnectFromServer();
void notifyUserAboutUpdate();
void sigRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName);
void sigForgotPasswordSuccess();
void sigForgotPasswordError();
void sigPromptForForgotPasswordReset();
void sigSubmitForgotPasswordResetToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_token,
const QString &_newpassword);
void sigPromptForForgotPasswordChallenge();
void sigSubmitForgotPasswordChallengeToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_email);
private slots:
void slotConnected();
void readData();
void websocketMessageReceived(const QByteArray &message);
void slotSocketError(QAbstractSocket::SocketError error);
void slotWebSocketError(QAbstractSocket::SocketError error);
void ping();
void processServerIdentificationEvent(const Event_ServerIdentification &event);
void processConnectionClosedEvent(const Event_ConnectionClosed &event);
void passwordSaltResponse(const Response &response);
void loginResponse(const Response &response);
void registerResponse(const Response &response);
void activateResponse(const Response &response);
void
doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
void doRegisterToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password,
const QString &_email,
const QString &_country,
const QString &_realname);
void doRequestPasswordSalt();
void doLogin();
void doHashedLogin();
Command_Login generateCommandLogin();
void doDisconnectFromServer();
void doActivateToServer(const QString &_token);
void doRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName);
void requestForgotPasswordResponse(const Response &response);
void doSubmitForgotPasswordResetToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_token,
const QString &_newpassword);
void submitForgotPasswordResetResponse(const Response &response);
void doSubmitForgotPasswordChallengeToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_email);
void submitForgotPasswordChallengeResponse(const Response &response);
private:
int maxTimeout;
int timeRunning, lastDataReceived;
QByteArray inputBuffer;
bool messageInProgress;
bool handshakeStarted;
bool usingWebSocket;
int messageLength;
QTimer *timer;
QTcpSocket *socket;
QWebSocket *websocket;
QString lastHostname;
unsigned int lastPort;
QString hashedPassword;
QString getSrvClientID(const QString &_hostname);
bool newMissingFeatureFound(const QString &_serversMissingFeatures);
void clearNewClientFeatures();
void connectToHost(const QString &hostname, unsigned int port);
protected slots:
void sendCommandContainer(const CommandContainer &cont) override;
public:
explicit RemoteClient(QObject *parent = nullptr);
~RemoteClient() override;
QString peerName() const
{
if (usingWebSocket) {
return websocket->peerName();
} else {
return socket->peerName();
}
}
void
connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
void registerToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_password,
const QString &_email,
const QString &_country,
const QString &_realname);
void activateToServer(const QString &_token);
void disconnectFromServer();
void requestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName);
void submitForgotPasswordResetToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_token,
const QString &_newpassword);
void submitForgotPasswordChallengeToServer(const QString &hostname,
unsigned int port,
const QString &_userName,
const QString &_email);
};
#endif

View file

@ -0,0 +1,353 @@
#include "remote_decklist_tree_widget.h"
#include "../../client/game_logic/abstract_client.h"
#include "../pending_command.h"
#include "pb/command_deck_list.pb.h"
#include "pb/response_deck_list.pb.h"
#include "pb/serverinfo_deckstorage.pb.h"
#include <QFileIconProvider>
#include <QHeaderView>
#include <QSortFilterProxyModel>
RemoteDeckList_TreeModel::DirectoryNode::DirectoryNode(const QString &_name,
RemoteDeckList_TreeModel::DirectoryNode *_parent)
: RemoteDeckList_TreeModel::Node(_name, _parent)
{
}
RemoteDeckList_TreeModel::DirectoryNode::~DirectoryNode()
{
clearTree();
}
void RemoteDeckList_TreeModel::DirectoryNode::clearTree()
{
for (int i = 0; i < size(); ++i)
delete at(i);
clear();
}
QString RemoteDeckList_TreeModel::DirectoryNode::getPath() const
{
if (parent) {
QString parentPath = parent->getPath();
if (parentPath.isEmpty())
return name;
else
return parentPath + "/" + name;
} else
return name;
}
RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeByPath(QStringList path)
{
QString pathItem;
if (parent) {
if (path.isEmpty())
return this;
pathItem = path.takeFirst();
if (pathItem.isEmpty() && name.isEmpty())
return this;
}
for (int i = 0; i < size(); ++i) {
DirectoryNode *node = dynamic_cast<DirectoryNode *>(at(i));
if (!node)
continue;
if (node->getName() == pathItem)
return node->getNodeByPath(path);
}
return 0;
}
RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeById(int id) const
{
for (int i = 0; i < size(); ++i) {
DirectoryNode *node = dynamic_cast<DirectoryNode *>(at(i));
if (node) {
FileNode *result = node->getNodeById(id);
if (result)
return result;
} else {
FileNode *file = dynamic_cast<FileNode *>(at(i));
if (file->getId() == id)
return file;
}
}
return 0;
}
RemoteDeckList_TreeModel::RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent)
: QAbstractItemModel(parent), client(_client)
{
QFileIconProvider fip;
dirIcon = fip.icon(QFileIconProvider::Folder);
fileIcon = fip.icon(QFileIconProvider::File);
root = new DirectoryNode;
refreshTree();
}
RemoteDeckList_TreeModel::~RemoteDeckList_TreeModel()
{
delete root;
}
int RemoteDeckList_TreeModel::rowCount(const QModelIndex &parent) const
{
DirectoryNode *node = getNode<DirectoryNode *>(parent);
if (node)
return node->size();
else
return 0;
}
int RemoteDeckList_TreeModel::columnCount(const QModelIndex & /*parent*/) const
{
return 3;
}
QVariant RemoteDeckList_TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.column() >= 3)
return QVariant();
Node *temp = static_cast<Node *>(index.internalPointer());
FileNode *file = dynamic_cast<FileNode *>(temp);
if (!file) {
DirectoryNode *node = dynamic_cast<DirectoryNode *>(temp);
switch (role) {
case Qt::DisplayRole: {
switch (index.column()) {
case 0:
return node->getName();
default:
return QVariant();
}
}
case Qt::DecorationRole:
return index.column() == 0 ? dirIcon : QVariant();
default:
return QVariant();
}
} else {
switch (role) {
case Qt::DisplayRole: {
switch (index.column()) {
case 0:
return file->getName();
case 1:
return file->getId();
case 2:
return file->getUploadTime();
default:
return QVariant();
}
}
case Qt::DecorationRole:
return index.column() == 0 ? fileIcon : QVariant();
case Qt::TextAlignmentRole:
return index.column() == 1 ? Qt::AlignRight : Qt::AlignLeft;
default:
return QVariant();
}
}
}
QVariant RemoteDeckList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal)
return QVariant();
switch (role) {
case Qt::TextAlignmentRole:
return section == 1 ? Qt::AlignRight : Qt::AlignLeft;
case Qt::DisplayRole: {
switch (section) {
case 0:
return tr("Name");
case 1:
return tr("ID");
case 2:
return tr("Upload time");
default:
return QVariant();
}
}
default:
return QVariant();
}
}
QModelIndex RemoteDeckList_TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
DirectoryNode *parentNode = getNode<DirectoryNode *>(parent);
if (row >= parentNode->size())
return QModelIndex();
return createIndex(row, column, parentNode->at(row));
}
QModelIndex RemoteDeckList_TreeModel::parent(const QModelIndex &ind) const
{
if (!ind.isValid())
return QModelIndex();
return nodeToIndex(static_cast<Node *>(ind.internalPointer())->getParent());
}
Qt::ItemFlags RemoteDeckList_TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {
return Qt::NoItemFlags;
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QModelIndex RemoteDeckList_TreeModel::nodeToIndex(Node *node) const
{
if (node == nullptr || node == root)
return QModelIndex();
return createIndex(node->getParent()->indexOf(node), 0, node);
}
void RemoteDeckList_TreeModel::addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, DirectoryNode *parent)
{
const ServerInfo_DeckStorage_File &fileInfo = file.file();
QDateTime time;
time.setSecsSinceEpoch(fileInfo.creation_time());
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
parent->append(new FileNode(QString::fromStdString(file.name()), file.id(), time, parent));
endInsertRows();
}
void RemoteDeckList_TreeModel::addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, DirectoryNode *parent)
{
DirectoryNode *newItem = addNamedFolderToTree(QString::fromStdString(folder.name()), parent);
const ServerInfo_DeckStorage_Folder &folderInfo = folder.folder();
const int folderItemsSize = folderInfo.items_size();
for (int i = 0; i < folderItemsSize; ++i) {
const ServerInfo_DeckStorage_TreeItem &subItem = folderInfo.items(i);
if (subItem.has_folder())
addFolderToTree(subItem, newItem);
else
addFileToTree(subItem, newItem);
}
}
RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::addNamedFolderToTree(const QString &name,
DirectoryNode *parent)
{
DirectoryNode *newItem = new DirectoryNode(name, parent);
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
parent->append(newItem);
endInsertRows();
return newItem;
}
void RemoteDeckList_TreeModel::removeNode(RemoteDeckList_TreeModel::Node *node)
{
int ind = node->getParent()->indexOf(node);
beginRemoveRows(nodeToIndex(node->getParent()), ind, ind);
node->getParent()->removeAt(ind);
endRemoveRows();
delete node;
}
void RemoteDeckList_TreeModel::refreshTree()
{
PendingCommand *pend = client->prepareSessionCommand(Command_DeckList());
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(deckListFinished(const Response &)));
client->sendCommand(pend);
}
void RemoteDeckList_TreeModel::deckListFinished(const Response &r)
{
const Response_DeckList &resp = r.GetExtension(Response_DeckList::ext);
beginResetModel();
root->clearTree();
endResetModel();
ServerInfo_DeckStorage_TreeItem tempRoot;
tempRoot.set_id(0);
tempRoot.mutable_folder()->CopyFrom(resp.root());
addFolderToTree(tempRoot, root);
emit treeRefreshed();
}
RemoteDeckList_TreeWidget::RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent) : QTreeView(parent)
{
treeModel = new RemoteDeckList_TreeModel(_client, this);
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(treeModel);
proxyModel->setDynamicSortFilter(true);
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
setModel(proxyModel);
connect(treeModel, SIGNAL(treeRefreshed()), this, SLOT(expandAll()));
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
setUniformRowHeights(true);
setSortingEnabled(true);
proxyModel->sort(0, Qt::AscendingOrder);
header()->setSortIndicator(0, Qt::AscendingOrder);
}
RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getNode(const QModelIndex &ind) const
{
return treeModel->getNode<RemoteDeckList_TreeModel::Node *>(proxyModel->mapToSource(ind));
}
RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getCurrentItem() const
{
return getNode(selectionModel()->currentIndex());
}
RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeWidget::getNodeByPath(const QString &path) const
{
return treeModel->getRoot()->getNodeByPath(path.split("/"));
}
RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeWidget::getNodeById(int id) const
{
return treeModel->getRoot()->getNodeById(id);
}
void RemoteDeckList_TreeWidget::addFileToTree(const ServerInfo_DeckStorage_TreeItem &file,
RemoteDeckList_TreeModel::DirectoryNode *parent)
{
treeModel->addFileToTree(file, parent);
}
void RemoteDeckList_TreeWidget::addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder,
RemoteDeckList_TreeModel::DirectoryNode *parent)
{
treeModel->addFolderToTree(folder, parent);
}
void RemoteDeckList_TreeWidget::addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent)
{
treeModel->addNamedFolderToTree(name, parent);
}
void RemoteDeckList_TreeWidget::removeNode(RemoteDeckList_TreeModel::Node *node)
{
treeModel->removeNode(node);
}
void RemoteDeckList_TreeWidget::refreshTree()
{
treeModel->refreshTree();
}

View file

@ -0,0 +1,131 @@
#ifndef REMOTEDECKLIST_TREEWIDGET_H
#define REMOTEDECKLIST_TREEWIDGET_H
#include <QAbstractItemModel>
#include <QDateTime>
#include <QTreeView>
class Response;
class AbstractClient;
class QSortFilterProxyModel;
class ServerInfo_DeckStorage_TreeItem;
class RemoteDeckList_TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
class DirectoryNode;
class FileNode;
class Node
{
protected:
DirectoryNode *parent;
QString name;
public:
Node(const QString &_name, DirectoryNode *_parent = nullptr) : parent(_parent), name(_name)
{
}
virtual ~Node(){};
DirectoryNode *getParent() const
{
return parent;
}
QString getName() const
{
return name;
}
};
class DirectoryNode : public Node, public QList<Node *>
{
public:
DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr);
~DirectoryNode();
void clearTree();
QString getPath() const;
DirectoryNode *getNodeByPath(QStringList path);
FileNode *getNodeById(int id) const;
};
class FileNode : public Node
{
private:
int id;
QDateTime uploadTime;
public:
FileNode(const QString &_name, int _id, const QDateTime &_uploadTime, DirectoryNode *_parent = nullptr)
: Node(_name, _parent), id(_id), uploadTime(_uploadTime)
{
}
int getId() const
{
return id;
}
QDateTime getUploadTime() const
{
return uploadTime;
}
};
template <typename T> T getNode(const QModelIndex &index) const
{
if (!index.isValid())
return dynamic_cast<T>(root);
return dynamic_cast<T>(static_cast<Node *>(index.internalPointer()));
}
private:
AbstractClient *client;
DirectoryNode *root;
QIcon fileIcon, dirIcon;
QModelIndex nodeToIndex(Node *node) const;
signals:
void treeRefreshed();
private slots:
void deckListFinished(const Response &r);
public:
RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr);
~RemoteDeckList_TreeModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
DirectoryNode *getRoot() const
{
return root;
}
void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, DirectoryNode *parent);
void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, DirectoryNode *parent);
DirectoryNode *addNamedFolderToTree(const QString &name, DirectoryNode *parent);
void removeNode(Node *node);
void refreshTree();
};
class RemoteDeckList_TreeWidget : public QTreeView
{
private:
RemoteDeckList_TreeModel *treeModel;
QSortFilterProxyModel *proxyModel;
public:
RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr);
RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const;
RemoteDeckList_TreeModel::Node *getCurrentItem() const;
RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const;
RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const;
void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent);
void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder,
RemoteDeckList_TreeModel::DirectoryNode *parent);
void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent);
void removeNode(RemoteDeckList_TreeModel::Node *node);
void refreshTree();
};
#endif

View file

@ -0,0 +1,319 @@
#include "remote_replay_list_tree_widget.h"
#include "../../client/game_logic/abstract_client.h"
#include "../pending_command.h"
#include "pb/command_replay_list.pb.h"
#include "pb/response_replay_list.pb.h"
#include "pb/serverinfo_replay.pb.h"
#include <QFileIconProvider>
#include <QHeaderView>
#include <QSortFilterProxyModel>
const int RemoteReplayList_TreeModel::numberOfColumns = 6;
RemoteReplayList_TreeModel::MatchNode::MatchNode(const ServerInfo_ReplayMatch &_matchInfo)
: RemoteReplayList_TreeModel::Node(QString::fromStdString(_matchInfo.game_name())), matchInfo(_matchInfo)
{
for (int i = 0; i < matchInfo.replay_list_size(); ++i)
append(new ReplayNode(matchInfo.replay_list(i), this));
}
RemoteReplayList_TreeModel::MatchNode::~MatchNode()
{
for (int i = 0; i < size(); ++i)
delete at(i);
}
void RemoteReplayList_TreeModel::MatchNode::updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo)
{
matchInfo.MergeFrom(_matchInfo);
}
RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent)
: QAbstractItemModel(parent), client(_client)
{
QFileIconProvider fip;
dirIcon = fip.icon(QFileIconProvider::Folder);
fileIcon = fip.icon(QFileIconProvider::File);
lockIcon = QPixmap("theme:icons/lock");
refreshTree();
}
RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel()
{
clearTree();
}
int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return replayMatches.size();
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
if (matchNode)
return matchNode->size();
else
return 0;
}
QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.column() >= numberOfColumns)
return QVariant();
ReplayNode *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
if (replayNode) {
const ServerInfo_Replay &replayInfo = replayNode->getReplayInfo();
switch (role) {
case Qt::TextAlignmentRole:
return index.column() == 0 ? Qt::AlignRight : Qt::AlignLeft;
case Qt::DisplayRole: {
switch (index.column()) {
case 0:
return replayInfo.replay_id();
case 1:
return QString::fromStdString(replayInfo.replay_name());
case 5:
return replayInfo.duration();
default:
return QVariant();
}
}
case Qt::DecorationRole:
return index.column() == 0 ? fileIcon : QVariant();
}
} else {
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
const ServerInfo_ReplayMatch &matchInfo = matchNode->getMatchInfo();
switch (role) {
case Qt::TextAlignmentRole:
switch (index.column()) {
case 0:
case 5:
return Qt::AlignRight;
default:
return Qt::AlignLeft;
}
case Qt::DisplayRole: {
switch (index.column()) {
case 0:
return matchInfo.game_id();
case 1:
return QString::fromStdString(matchInfo.game_name());
case 2: {
QStringList playerList;
for (int i = 0; i < matchInfo.player_names_size(); ++i)
playerList.append(QString::fromStdString(matchInfo.player_names(i)));
return playerList.join(", ");
}
case 4:
return QDateTime::fromSecsSinceEpoch(matchInfo.time_started());
case 5:
return matchInfo.length();
default:
return QVariant();
}
}
case Qt::DecorationRole:
switch (index.column()) {
case 0:
return dirIcon;
case 3:
return matchInfo.do_not_hide() ? lockIcon : QVariant();
default:
return QVariant();
}
}
}
return QVariant();
}
QVariant RemoteReplayList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal)
return QVariant();
switch (role) {
case Qt::TextAlignmentRole:
switch (section) {
case 0:
case 5:
return Qt::AlignRight;
default:
return Qt::AlignLeft;
}
case Qt::DisplayRole: {
switch (section) {
case 0:
return tr("ID");
case 1:
return tr("Name");
case 2:
return tr("Players");
case 3:
return tr("Keep");
case 4:
return tr("Time started");
case 5:
return tr("Duration (sec)");
default:
return QVariant();
}
}
default:
return QVariant();
}
}
QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
if (matchNode) {
if (row >= matchNode->size())
return QModelIndex();
return createIndex(row, column, (void *)matchNode->at(row));
} else {
if (row >= replayMatches.size())
return QModelIndex();
return createIndex(row, column, (void *)replayMatches[row]);
}
}
QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const
{
MatchNode const *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(ind.internalPointer()));
if (matchNode)
return QModelIndex();
else {
ReplayNode *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(ind.internalPointer()));
return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode);
}
}
Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
ServerInfo_Replay const *RemoteReplayList_TreeModel::getReplay(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
ReplayNode *node = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
if (!node)
return 0;
return &node->getReplayInfo();
}
ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getReplayMatch(const QModelIndex &index) const
{
if (!index.isValid())
return nullptr;
auto *node = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
if (!node) {
auto *_node = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
if (!_node)
return nullptr;
return &_node->getParent()->getMatchInfo();
}
return &node->getMatchInfo();
}
void RemoteReplayList_TreeModel::clearTree()
{
for (int i = 0; i < replayMatches.size(); ++i)
delete replayMatches[i];
replayMatches.clear();
}
void RemoteReplayList_TreeModel::refreshTree()
{
PendingCommand *pend = client->prepareSessionCommand(Command_ReplayList());
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(replayListFinished(const Response &)));
client->sendCommand(pend);
}
void RemoteReplayList_TreeModel::addMatchInfo(const ServerInfo_ReplayMatch &matchInfo)
{
beginInsertRows(QModelIndex(), replayMatches.size(), replayMatches.size());
replayMatches.append(new MatchNode(matchInfo));
endInsertRows();
emit treeRefreshed();
}
void RemoteReplayList_TreeModel::updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo)
{
for (int i = 0; i < replayMatches.size(); ++i)
if (replayMatches[i]->getMatchInfo().game_id() == gameId) {
replayMatches[i]->updateMatchInfo(matchInfo);
emit dataChanged(createIndex(i, 0, (void *)replayMatches[i]),
createIndex(i, numberOfColumns - 1, (void *)replayMatches[i]));
break;
}
}
void RemoteReplayList_TreeModel::removeMatchInfo(int gameId)
{
for (int i = 0; i < replayMatches.size(); ++i)
if (replayMatches[i]->getMatchInfo().game_id() == gameId) {
beginRemoveRows(QModelIndex(), i, i);
replayMatches.removeAt(i);
endRemoveRows();
break;
}
}
void RemoteReplayList_TreeModel::replayListFinished(const Response &r)
{
const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext);
beginResetModel();
clearTree();
for (int i = 0; i < resp.match_list_size(); ++i)
replayMatches.append(new MatchNode(resp.match_list(i)));
endResetModel();
emit treeRefreshed();
}
RemoteReplayList_TreeWidget::RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent) : QTreeView(parent)
{
treeModel = new RemoteReplayList_TreeModel(_client, this);
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(treeModel);
proxyModel->setDynamicSortFilter(true);
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
setModel(proxyModel);
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
header()->setStretchLastSection(false);
setUniformRowHeights(true);
setSortingEnabled(true);
proxyModel->sort(0, Qt::AscendingOrder);
header()->setSortIndicator(0, Qt::AscendingOrder);
}
ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentReplay() const
{
return treeModel->getReplay(proxyModel->mapToSource(selectionModel()->currentIndex()));
}
ServerInfo_ReplayMatch const *RemoteReplayList_TreeWidget::getCurrentReplayMatch() const
{
return treeModel->getReplayMatch(proxyModel->mapToSource(selectionModel()->currentIndex()));
}

View file

@ -0,0 +1,131 @@
#ifndef REMOTEREPLAYLIST_TREEWIDGET_H
#define REMOTEREPLAYLIST_TREEWIDGET_H
#include "pb/serverinfo_replay.pb.h"
#include "pb/serverinfo_replay_match.pb.h"
#include <QAbstractItemModel>
#include <QDateTime>
#include <QTreeView>
class Response;
class AbstractClient;
class QSortFilterProxyModel;
class RemoteReplayList_TreeModel : public QAbstractItemModel
{
Q_OBJECT
private:
class MatchNode;
class ReplayNode;
class Node
{
protected:
QString name;
public:
Node(const QString &_name) : name(_name)
{
}
virtual ~Node(){};
QString getName() const
{
return name;
}
};
class MatchNode : public Node, public QList<ReplayNode *>
{
private:
ServerInfo_ReplayMatch matchInfo;
public:
MatchNode(const ServerInfo_ReplayMatch &_matchInfo);
~MatchNode();
void clearTree();
const ServerInfo_ReplayMatch &getMatchInfo()
{
return matchInfo;
}
void updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo);
};
class ReplayNode : public Node
{
private:
MatchNode *parent;
ServerInfo_Replay replayInfo;
public:
ReplayNode(const ServerInfo_Replay &_replayInfo, MatchNode *_parent)
: Node(QString::fromStdString(_replayInfo.replay_name())), parent(_parent), replayInfo(_replayInfo)
{
}
MatchNode *getParent() const
{
return parent;
}
const ServerInfo_Replay &getReplayInfo()
{
return replayInfo;
}
};
AbstractClient *client;
QList<MatchNode *> replayMatches;
QIcon dirIcon, fileIcon, lockIcon;
void clearTree();
static const int numberOfColumns;
signals:
void treeRefreshed();
private slots:
void replayListFinished(const Response &r);
public:
RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr);
~RemoteReplayList_TreeModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
{
return numberOfColumns;
}
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
void refreshTree();
ServerInfo_Replay const *getReplay(const QModelIndex &index) const;
ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const;
void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo);
void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo);
void removeMatchInfo(int gameId);
};
class RemoteReplayList_TreeWidget : public QTreeView
{
private:
RemoteReplayList_TreeModel *treeModel;
QSortFilterProxyModel *proxyModel;
ServerInfo_Replay const *getNode(const QModelIndex &ind) const;
public:
RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr);
ServerInfo_Replay const *getCurrentReplay() const;
ServerInfo_ReplayMatch const *getCurrentReplayMatch() const;
void refreshTree();
void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo)
{
treeModel->addMatchInfo(matchInfo);
}
void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo)
{
treeModel->updateMatchInfo(gameId, matchInfo);
}
void removeMatchInfo(int gameId)
{
treeModel->removeMatchInfo(gameId);
}
};
#endif