mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-11 16:44:48 -07:00
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:
parent
d1e0f9dfc5
commit
fa999880ee
261 changed files with 735 additions and 729 deletions
729
cockatrice/src/server/remote/remote_client.cpp
Normal file
729
cockatrice/src/server/remote/remote_client.cpp
Normal 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();
|
||||
}
|
||||
148
cockatrice/src/server/remote/remote_client.h
Normal file
148
cockatrice/src/server/remote/remote_client.h
Normal 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
|
||||
353
cockatrice/src/server/remote/remote_decklist_tree_widget.cpp
Normal file
353
cockatrice/src/server/remote/remote_decklist_tree_widget.cpp
Normal 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();
|
||||
}
|
||||
131
cockatrice/src/server/remote/remote_decklist_tree_widget.h
Normal file
131
cockatrice/src/server/remote/remote_decklist_tree_widget.h
Normal 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
|
||||
319
cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp
Normal file
319
cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp
Normal 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()));
|
||||
}
|
||||
131
cockatrice/src/server/remote/remote_replay_list_tree_widget.h
Normal file
131
cockatrice/src/server/remote/remote_replay_list_tree_widget.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue