From c6c6a970c6b770a7eb4d7c5b0971dd2274cddb01 Mon Sep 17 00:00:00 2001 From: Max-Wilhelm Bruker Date: Sat, 25 Feb 2012 19:33:44 +0100 Subject: [PATCH] interface & client tab for replay transfer --- cockatrice/CMakeLists.txt | 4 + cockatrice/src/localserver.cpp | 4 +- cockatrice/src/localserver.h | 2 +- cockatrice/src/localserverinterface.h | 2 + cockatrice/src/playertarget.cpp | 4 +- cockatrice/src/remotedecklist_treewidget.cpp | 2 +- .../src/remotereplaylist_treewidget.cpp | 167 ++++++++++++++++ cockatrice/src/remotereplaylist_treewidget.h | 49 +++++ cockatrice/src/tab_deck_storage.h | 1 - cockatrice/src/tab_game.cpp | 8 + cockatrice/src/tab_game.h | 2 +- cockatrice/src/tab_replays.cpp | 185 ++++++++++++++++++ cockatrice/src/tab_replays.h | 44 +++++ cockatrice/src/tab_supervisor.cpp | 18 +- cockatrice/src/tab_supervisor.h | 4 + common/pb/CMakeLists.txt | 6 + common/pb/command_deck_list.proto | 7 + common/pb/command_replay_download.proto | 8 + common/pb/command_replay_list.proto | 7 + common/pb/response.proto | 3 + common/pb/response_replay_download.proto | 9 + common/pb/response_replay_list.proto | 9 + common/pb/serverinfo_replay.proto | 9 + common/pb/serverinfo_user.proto | 1 + common/pb/session_commands.proto | 8 +- common/server.cpp | 2 +- common/server.h | 4 +- common/server_game.cpp | 9 +- common/server_game.h | 2 +- common/server_protocolhandler.cpp | 9 +- common/server_protocolhandler.h | 4 + common/server_room.h | 1 + servatrice/src/servatrice.cpp | 70 ++++--- servatrice/src/servatrice.h | 6 +- servatrice/src/serversocketinterface.cpp | 82 +++++++- servatrice/src/serversocketinterface.h | 2 + 36 files changed, 702 insertions(+), 52 deletions(-) create mode 100644 cockatrice/src/remotereplaylist_treewidget.cpp create mode 100644 cockatrice/src/remotereplaylist_treewidget.h create mode 100644 cockatrice/src/tab_replays.cpp create mode 100644 cockatrice/src/tab_replays.h create mode 100644 common/pb/command_deck_list.proto create mode 100644 common/pb/command_replay_download.proto create mode 100644 common/pb/command_replay_list.proto create mode 100644 common/pb/response_replay_download.proto create mode 100644 common/pb/response_replay_list.proto create mode 100644 common/pb/serverinfo_replay.proto diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index ec272dc5..d6f46fc9 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -54,6 +54,7 @@ SET(cockatrice_SOURCES src/tab_message.cpp src/tab_game.cpp src/tab_deck_storage.cpp + src/tab_replays.cpp src/tab_supervisor.cpp src/tab_admin.cpp src/tab_userlists.cpp @@ -61,6 +62,7 @@ SET(cockatrice_SOURCES src/userlist.cpp src/userinfobox.cpp src/remotedecklist_treewidget.cpp + src/remotereplaylist_treewidget.cpp src/deckview.cpp src/playerlistwidget.cpp src/pixmapgenerator.cpp @@ -122,6 +124,7 @@ SET(cockatrice_HEADERS src/tab_message.h src/tab_game.h src/tab_deck_storage.h + src/tab_replays.h src/tab_supervisor.h src/tab_admin.h src/tab_userlists.h @@ -129,6 +132,7 @@ SET(cockatrice_HEADERS src/userlist.h src/userinfobox.h src/remotedecklist_treewidget.h + src/remotereplaylist_treewidget.h src/deckview.h src/playerlistwidget.h src/settingscache.h diff --git a/cockatrice/src/localserver.cpp b/cockatrice/src/localserver.cpp index 16216b87..c4d67227 100644 --- a/cockatrice/src/localserver.cpp +++ b/cockatrice/src/localserver.cpp @@ -20,9 +20,9 @@ LocalServerInterface *LocalServer::newConnection() return lsi; } -ServerInfo_User LocalServer::getUserData(const QString &name) +ServerInfo_User LocalServer::getUserData(const QString &name, bool /*withId*/) { ServerInfo_User result; result.set_name(name.toStdString()); return result; -} \ No newline at end of file +} diff --git a/cockatrice/src/localserver.h b/cockatrice/src/localserver.h index 8935c12c..c4fa3d65 100644 --- a/cockatrice/src/localserver.h +++ b/cockatrice/src/localserver.h @@ -14,7 +14,7 @@ public: LocalServerInterface *newConnection(); protected: - ServerInfo_User getUserData(const QString &name); + ServerInfo_User getUserData(const QString &name, bool withId = false); }; #endif \ No newline at end of file diff --git a/cockatrice/src/localserverinterface.h b/cockatrice/src/localserverinterface.h index a1b08ea3..c44a32a6 100644 --- a/cockatrice/src/localserverinterface.h +++ b/cockatrice/src/localserverinterface.h @@ -18,6 +18,8 @@ private: Response::ResponseCode cmdDeckDel(const Command_DeckDel & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } Response::ResponseCode cmdDeckUpload(const Command_DeckUpload & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } Response::ResponseCode cmdDeckDownload(const Command_DeckDownload & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } + Response::ResponseCode cmdReplayList(const Command_ReplayList & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } + Response::ResponseCode cmdReplayDownload(const Command_ReplayDownload & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } Response::ResponseCode cmdBanFromServer(const Command_BanFromServer & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage & /*cmd*/, ResponseContainer & /*rc*/) { return Response::RespFunctionNotAllowed; } diff --git a/cockatrice/src/playertarget.cpp b/cockatrice/src/playertarget.cpp index dde115db..a20c5c85 100644 --- a/cockatrice/src/playertarget.cpp +++ b/cockatrice/src/playertarget.cpp @@ -51,7 +51,7 @@ PlayerTarget::PlayerTarget(Player *_owner, QGraphicsItem *parentItem) { setCacheMode(DeviceCoordinateCache); - const std::string bmp = _owner->getUserInfo()->avatar_bmp(); + const std::string &bmp = _owner->getUserInfo()->avatar_bmp(); if (!fullPixmap.loadFromData((const uchar *) bmp.data(), bmp.size())) fullPixmap = QPixmap(); } @@ -144,4 +144,4 @@ AbstractCounter *PlayerTarget::addCounter(int _counterId, const QString &_name, void PlayerTarget::delCounter() { playerCounter = 0; -} \ No newline at end of file +} diff --git a/cockatrice/src/remotedecklist_treewidget.cpp b/cockatrice/src/remotedecklist_treewidget.cpp index ee3b15be..4cd35dfd 100644 --- a/cockatrice/src/remotedecklist_treewidget.cpp +++ b/cockatrice/src/remotedecklist_treewidget.cpp @@ -5,7 +5,7 @@ #include "abstractclient.h" #include "pending_command.h" -#include "pb/session_commands.pb.h" +#include "pb/command_deck_list.pb.h" #include "pb/response_deck_list.pb.h" #include "pb/serverinfo_deckstorage.pb.h" diff --git a/cockatrice/src/remotereplaylist_treewidget.cpp b/cockatrice/src/remotereplaylist_treewidget.cpp new file mode 100644 index 00000000..379b7261 --- /dev/null +++ b/cockatrice/src/remotereplaylist_treewidget.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include "remotereplaylist_treewidget.h" +#include "abstractclient.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" + +RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent) + : QAbstractItemModel(parent), client(_client) +{ + QFileIconProvider fip; + fileIcon = fip.icon(QFileIconProvider::File); + + refreshTree(); +} + +RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() +{ +} + +int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : replays.size(); +} + +int RemoteReplayList_TreeModel::columnCount(const QModelIndex &/*parent*/) const +{ + return 6; +} + +QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.column() > 5) + return QVariant(); + + ServerInfo_Replay *replayInfo = static_cast(index.internalPointer()); + switch (role) { + case Qt::TextAlignmentRole: + return index.column() == 0 ? Qt::AlignRight : Qt::AlignLeft; + case Qt::DisplayRole: { + switch (index.column()) { + case 0: return replayInfo->game_id(); + case 1: return QString::fromStdString(replayInfo->game_name()); + case 2: return QString::fromStdString(replayInfo->replay_name()); + case 3: { + QStringList playerList; + for (int i = 0; i < replayInfo->player_names_size(); ++i) + playerList.append(QString::fromStdString(replayInfo->player_names(i))); + return playerList.join(", "); + } + case 4: return QDateTime::fromTime_t(replayInfo->time_started()); + case 5: return replayInfo->length(); + default: return QVariant(); + } + } + case Qt::DecorationRole: + return index.column() == 0 ? fileIcon : 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: + return section == 0 ? Qt::AlignRight : Qt::AlignLeft; + case Qt::DisplayRole: { + switch (section) { + case 0: return tr("Game ID"); + case 1: return tr("Game name"); + case 2: return tr("Replay name"); + case 3: return tr("Players"); + 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(); + + return createIndex(row, column, (void *) &(replays[row])); +} + +QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const +{ + return QModelIndex(); +} + +Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +ServerInfo_Replay const* RemoteReplayList_TreeModel::getNode(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + return &(replays[index.row()]); +} + +void RemoteReplayList_TreeModel::refreshTree() +{ + PendingCommand *pend = client->prepareSessionCommand(Command_ReplayList()); + connect(pend, SIGNAL(finished(const Response &)), this, SLOT(replayListFinished(const Response &))); + + client->sendCommand(pend); +} + +void RemoteReplayList_TreeModel::replayListFinished(const Response &r) +{ + const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext); + + beginResetModel(); + replays.clear(); + + for (int i = 0; i < resp.replay_list_size(); ++i) + replays.append(resp.replay_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); + connect(treeModel, SIGNAL(treeRefreshed()), this, SLOT(expandAll())); + + header()->setResizeMode(QHeaderView::ResizeToContents); + setUniformRowHeights(true); + setSortingEnabled(true); + proxyModel->sort(0, Qt::AscendingOrder); + header()->setSortIndicator(0, Qt::AscendingOrder); +} + +ServerInfo_Replay const *RemoteReplayList_TreeWidget::getNode(const QModelIndex &ind) const +{ + return treeModel->getNode(proxyModel->mapToSource(ind)); +} + +ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentItem() const +{ + return getNode(selectionModel()->currentIndex()); +} diff --git a/cockatrice/src/remotereplaylist_treewidget.h b/cockatrice/src/remotereplaylist_treewidget.h new file mode 100644 index 00000000..5cbb6736 --- /dev/null +++ b/cockatrice/src/remotereplaylist_treewidget.h @@ -0,0 +1,49 @@ +#ifndef REMOTEREPLAYLIST_TREEWIDGET_H +#define REMOTEREPLAYLIST_TREEWIDGET_H + +#include +#include +#include +#include "pb/serverinfo_replay.pb.h" + +class Response; +class AbstractClient; +class QSortFilterProxyModel; + +class RemoteReplayList_TreeModel : public QAbstractItemModel { + Q_OBJECT +private: + AbstractClient *client; + QList replays; + + QIcon fileIcon; +signals: + void treeRefreshed(); +private slots: + void replayListFinished(const Response &r); +public: + RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = 0); + ~RemoteReplayList_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; + void refreshTree(); + ServerInfo_Replay const *getNode(const QModelIndex &index) const; +}; + +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 = 0); + ServerInfo_Replay const *getCurrentItem() const; + void refreshTree(); +}; + +#endif diff --git a/cockatrice/src/tab_deck_storage.h b/cockatrice/src/tab_deck_storage.h index 88101981..41b53954 100644 --- a/cockatrice/src/tab_deck_storage.h +++ b/cockatrice/src/tab_deck_storage.h @@ -12,7 +12,6 @@ class QToolBar; class QTreeWidget; class QTreeWidgetItem; class QGroupBox; -class ProtocolResponse; class RemoteDeckList_TreeWidget; class TabDeckStorage : public Tab { diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 5e9ae917..a4c0bf4b 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -957,6 +957,14 @@ CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) co return zone->getCard(cardId, QString()); } +QString TabGame::getTabText() const +{ + if (replay) + return tr("Replay %1: %2").arg(gameId).arg(gameDescription); + else + return tr("Game %1: %2").arg(gameId).arg(gameDescription); +} + Player *TabGame::getActiveLocalPlayer() const { Player *active = players.value(activePlayer, 0); diff --git a/cockatrice/src/tab_game.h b/cockatrice/src/tab_game.h index 26c5c910..8678a316 100644 --- a/cockatrice/src/tab_game.h +++ b/cockatrice/src/tab_game.h @@ -180,7 +180,7 @@ public: CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; bool isHost() const { return hostId == localPlayerId; } int getGameId() const { return gameId; } - QString getTabText() const { return tr("Game %1: %2").arg(gameId).arg(gameDescription); } + QString getTabText() const; bool getSpectator() const { return spectator; } bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } bool getSpectatorsSeeEverything() const { return spectatorsSeeEverything; } diff --git a/cockatrice/src/tab_replays.cpp b/cockatrice/src/tab_replays.cpp new file mode 100644 index 00000000..4c9d258d --- /dev/null +++ b/cockatrice/src/tab_replays.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tab_replays.h" +#include "remotereplaylist_treewidget.h" +#include "abstractclient.h" +#include "tab_game.h" +#include "settingscache.h" + +#include "pending_command.h" +#include "pb/game_replay.pb.h" +#include "pb/response.pb.h" +#include "pb/response_replay_download.pb.h" +#include "pb/command_replay_download.pb.h" + +TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) + : Tab(_tabSupervisor), client(_client) +{ + localDirModel = new QFileSystemModel(this); + localDirModel->setRootPath(settingsCache->getReplaysPath()); + + sortFilter = new QSortFilterProxyModel(this); + sortFilter->setSourceModel(localDirModel); + sortFilter->setDynamicSortFilter(true); + + localDirView = new QTreeView; + localDirView->setModel(sortFilter); + localDirView->setColumnHidden(1, true); + localDirView->setRootIndex(sortFilter->mapFromSource(localDirModel->index(localDirModel->rootPath(), 0))); + localDirView->setSortingEnabled(true); + localDirView->header()->setResizeMode(QHeaderView::ResizeToContents); + sortFilter->sort(0, Qt::AscendingOrder); + localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); + + leftToolBar = new QToolBar; + leftToolBar->setOrientation(Qt::Horizontal); + leftToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *leftToolBarLayout = new QHBoxLayout; + leftToolBarLayout->addStretch(); + leftToolBarLayout->addWidget(leftToolBar); + leftToolBarLayout->addStretch(); + + QVBoxLayout *leftVbox = new QVBoxLayout; + leftVbox->addWidget(localDirView); + leftVbox->addLayout(leftToolBarLayout); + leftGroupBox = new QGroupBox; + leftGroupBox->setLayout(leftVbox); + + rightToolBar = new QToolBar; + rightToolBar->setOrientation(Qt::Horizontal); + rightToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *rightToolBarLayout = new QHBoxLayout; + rightToolBarLayout->addStretch(); + rightToolBarLayout->addWidget(rightToolBar); + rightToolBarLayout->addStretch(); + + serverDirView = new RemoteReplayList_TreeWidget(client); + + QVBoxLayout *rightVbox = new QVBoxLayout; + rightVbox->addWidget(serverDirView); + rightVbox->addLayout(rightToolBarLayout); + rightGroupBox = new QGroupBox; + rightGroupBox->setLayout(rightVbox); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addWidget(leftGroupBox); + hbox->addWidget(rightGroupBox); + + aOpenLocalReplay = new QAction(this); + aOpenLocalReplay->setIcon(QIcon(":/resources/pencil.svg")); + connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay())); + aOpenRemoteReplay = new QAction(this); + aOpenRemoteReplay->setIcon(QIcon(":/resources/pencil.svg")); + connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay())); + aDownload = new QAction(this); + aDownload->setIcon(QIcon(":/resources/arrow_left_green.svg")); + connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload())); + + leftToolBar->addAction(aOpenLocalReplay); + rightToolBar->addAction(aOpenRemoteReplay); + rightToolBar->addAction(aDownload); + + retranslateUi(); + setLayout(hbox); +} + +void TabReplays::retranslateUi() +{ + leftGroupBox->setTitle(tr("Local file system")); + rightGroupBox->setTitle(tr("Server replay storage")); + + aOpenLocalReplay->setText(tr("Watch replay")); + aOpenRemoteReplay->setText(tr("Watch replay")); + aDownload->setText(tr("Download replay")); +} + +void TabReplays::actOpenLocalReplay() +{ + QModelIndex curLeft = sortFilter->mapToSource(localDirView->selectionModel()->currentIndex()); + if (localDirModel->isDir(curLeft)) + return; + QString filePath = localDirModel->filePath(curLeft); + + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly)) + return; + QByteArray data = f.readAll(); + f.close(); + + GameReplay *replay = new GameReplay; + replay->ParseFromArray(data.data(), data.size()); + + emit openReplay(replay); +} + +void TabReplays::actOpenRemoteReplay() +{ + ServerInfo_Replay const *curRight = serverDirView->getCurrentItem(); + if (!curRight) + return; + + Command_ReplayDownload cmd; + cmd.set_game_id(curRight->game_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(const Response &)), this, SLOT(openRemoteReplayFinished(const Response &))); + client->sendCommand(pend); +} + +void TabReplays::openRemoteReplayFinished(const Response &r) +{ + const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); + GameReplay *replay = new GameReplay; + replay->ParseFromString(resp.replay_data()); + + emit openReplay(replay); +} + +void TabReplays::actDownload() +{ + QString filePath; + QModelIndex curLeft = sortFilter->mapToSource(localDirView->selectionModel()->currentIndex()); + if (!curLeft.isValid()) + filePath = localDirModel->rootPath(); + else { + while (!localDirModel->isDir(curLeft)) + curLeft = curLeft.parent(); + filePath = localDirModel->filePath(curLeft); + } + + ServerInfo_Replay const *curRight = serverDirView->getCurrentItem(); + if (!curRight) + return; + filePath += QString("/game_%1.cor").arg(curRight->game_id()); + + Command_ReplayDownload cmd; + cmd.set_game_id(curRight->game_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(filePath); + connect(pend, SIGNAL(finished(const Response &)), this, SLOT(downloadFinished(const Response &))); + client->sendCommand(pend); +} + +void TabReplays::downloadFinished(const Response &r) +{ + const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); + + PendingCommand *pend = static_cast(sender()); + QString filePath = pend->getExtraData().toString(); + + const std::string &data = resp.replay_data(); + QFile f(filePath); + f.open(QIODevice::WriteOnly); + f.write((const char *) data.data(), data.size()); + f.close(); +} diff --git a/cockatrice/src/tab_replays.h b/cockatrice/src/tab_replays.h new file mode 100644 index 00000000..d564e531 --- /dev/null +++ b/cockatrice/src/tab_replays.h @@ -0,0 +1,44 @@ +#ifndef TAB_REPLAYS_H +#define TAB_REPLAYS_H + +#include "tab.h" +#include "pb/response.pb.h" + +class AbstractClient; +class QTreeView; +class QFileSystemModel; +class QSortFilterProxyModel; +class QToolBar; +class QGroupBox; +class RemoteReplayList_TreeWidget; +class GameReplay; + +class TabReplays : public Tab { + Q_OBJECT +private: + AbstractClient *client; + QTreeView *localDirView; + QFileSystemModel *localDirModel; + QSortFilterProxyModel *sortFilter; + QToolBar *leftToolBar, *rightToolBar; + RemoteReplayList_TreeWidget *serverDirView; + QGroupBox *leftGroupBox, *rightGroupBox; + + QAction *aOpenLocalReplay, *aOpenRemoteReplay, *aDownload; +private slots: + void actOpenLocalReplay(); + + void actOpenRemoteReplay(); + void openRemoteReplayFinished(const Response &r); + + void actDownload(); + void downloadFinished(const Response &r); +signals: + void openReplay(GameReplay *replay); +public: + TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client); + void retranslateUi(); + QString getTabText() const { return tr("Game replays"); } +}; + +#endif diff --git a/cockatrice/src/tab_supervisor.cpp b/cockatrice/src/tab_supervisor.cpp index e66603fd..02ab078e 100644 --- a/cockatrice/src/tab_supervisor.cpp +++ b/cockatrice/src/tab_supervisor.cpp @@ -5,6 +5,7 @@ #include "tab_room.h" #include "tab_game.h" #include "tab_deck_storage.h" +#include "tab_replays.h" #include "tab_admin.h" #include "tab_message.h" #include "tab_userlists.h" @@ -138,8 +139,14 @@ void TabSupervisor::start(AbstractClient *_client, const ServerInfo_User &_userI if (userInfo->user_level() & ServerInfo_User::IsRegistered) { tabDeckStorage = new TabDeckStorage(this, client); myAddTab(tabDeckStorage); - } else + + tabReplays = new TabReplays(this, client); + connect(tabReplays, SIGNAL(openReplay(GameReplay *)), this, SLOT(openReplay(GameReplay *))); + myAddTab(tabReplays); + } else { tabDeckStorage = 0; + tabReplays = 0; + } if (userInfo->user_level() & ServerInfo_User::IsModerator) { tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin)); @@ -155,6 +162,7 @@ void TabSupervisor::startLocal(const QList &_clients) { tabUserLists = 0; tabDeckStorage = 0; + tabReplays = 0; tabAdmin = 0; userInfo = new ServerInfo_User; localClients = _clients; @@ -183,10 +191,12 @@ void TabSupervisor::stop() tabUserLists->deleteLater(); tabServer->deleteLater(); tabDeckStorage->deleteLater(); + tabReplays->deleteLater(); } tabUserLists = 0; tabServer = 0; tabDeckStorage = 0; + tabReplays = 0; clear(); QMapIterator roomIterator(roomTabs); @@ -306,6 +316,12 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus return tab; } +void TabSupervisor::openReplay(GameReplay *replay) +{ + TabGame *replayTab = new TabGame(replay); + myAddTab(replayTab); +} + void TabSupervisor::talkLeft(TabMessage *tab) { emit setMenu(0); diff --git a/cockatrice/src/tab_supervisor.h b/cockatrice/src/tab_supervisor.h index 0744a2d2..9a60998e 100644 --- a/cockatrice/src/tab_supervisor.h +++ b/cockatrice/src/tab_supervisor.h @@ -12,6 +12,7 @@ class TabServer; class TabRoom; class TabGame; class TabDeckStorage; +class TabReplays; class TabAdmin; class TabMessage; class TabUserLists; @@ -21,6 +22,7 @@ class Event_GameJoined; class Event_UserMessage; class ServerInfo_Room; class ServerInfo_User; +class GameReplay; class CloseButton : public QAbstractButton { Q_OBJECT @@ -44,6 +46,7 @@ private: TabServer *tabServer; TabUserLists *tabUserLists; TabDeckStorage *tabDeckStorage; + TabReplays *tabReplays; TabAdmin *tabAdmin; QMap roomTabs; QMap gameTabs; @@ -77,6 +80,7 @@ private slots: void addRoomTab(const ServerInfo_Room &info, bool setCurrent); void roomLeft(TabRoom *tab); TabMessage *addMessageTab(const QString &userName, bool focus); + void openReplay(GameReplay *replay); void processUserLeft(const QString &userName); void processUserJoined(const QString &userName); void talkLeft(TabMessage *tab); diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt index 88f26dbf..9001bd8d 100644 --- a/common/pb/CMakeLists.txt +++ b/common/pb/CMakeLists.txt @@ -12,6 +12,7 @@ SET(PROTO_FILES command_deck_del_dir.proto command_deck_del.proto command_deck_download.proto + command_deck_list.proto command_deck_new_dir.proto command_deck_select.proto command_deck_upload.proto @@ -29,6 +30,8 @@ SET(PROTO_FILES command_mulligan.proto command_next_turn.proto command_ready_start.proto + command_replay_list.proto + command_replay_download.proto command_reveal_cards.proto command_roll_die.proto command_set_active_phase.proto @@ -107,6 +110,8 @@ SET(PROTO_FILES response_join_room.proto response_list_users.proto response_login.proto + response_replay_download.proto + response_replay_list.proto response.proto room_commands.proto room_event.proto @@ -120,6 +125,7 @@ SET(PROTO_FILES serverinfo_playerping.proto serverinfo_playerproperties.proto serverinfo_player.proto + serverinfo_replay.proto serverinfo_room.proto serverinfo_user.proto serverinfo_zone.proto diff --git a/common/pb/command_deck_list.proto b/common/pb/command_deck_list.proto new file mode 100644 index 00000000..a1e99208 --- /dev/null +++ b/common/pb/command_deck_list.proto @@ -0,0 +1,7 @@ +import "session_commands.proto"; + +message Command_DeckList { + extend SessionCommand { + optional Command_DeckList ext = 1008; + } +} diff --git a/common/pb/command_replay_download.proto b/common/pb/command_replay_download.proto new file mode 100644 index 00000000..324f2f1e --- /dev/null +++ b/common/pb/command_replay_download.proto @@ -0,0 +1,8 @@ +import "session_commands.proto"; + +message Command_ReplayDownload { + extend SessionCommand { + optional Command_ReplayDownload ext = 1101; + } + optional sint32 game_id = 1 [default = -1]; +} diff --git a/common/pb/command_replay_list.proto b/common/pb/command_replay_list.proto new file mode 100644 index 00000000..2dc0e92e --- /dev/null +++ b/common/pb/command_replay_list.proto @@ -0,0 +1,7 @@ +import "session_commands.proto"; + +message Command_ReplayList { + extend SessionCommand { + optional Command_ReplayList ext = 1100; + } +} diff --git a/common/pb/response.proto b/common/pb/response.proto index d3f680c9..18541963 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -20,6 +20,7 @@ message Response { RespWouldOverwriteOldSession = 17; RespChatFlood = 18; RespUserIsBanned = 19; + RespAccessDenied = 20; } enum ResponseType { JOIN_ROOM = 1000; @@ -31,6 +32,8 @@ message Response { DECK_LIST = 1006; DECK_DOWNLOAD = 1007; DECK_UPLOAD = 1008; + REPLAY_LIST = 1100; + REPLAY_DOWNLOAD = 1101; } required uint64 cmd_id = 1; optional ResponseCode response_code = 2; diff --git a/common/pb/response_replay_download.proto b/common/pb/response_replay_download.proto new file mode 100644 index 00000000..a3df3d65 --- /dev/null +++ b/common/pb/response_replay_download.proto @@ -0,0 +1,9 @@ +import "response.proto"; + +message Response_ReplayDownload { + extend Response { + optional Response_ReplayDownload ext = 1101; + } + optional bytes replay_data = 1; +} + diff --git a/common/pb/response_replay_list.proto b/common/pb/response_replay_list.proto new file mode 100644 index 00000000..6a4c29f6 --- /dev/null +++ b/common/pb/response_replay_list.proto @@ -0,0 +1,9 @@ +import "response.proto"; +import "serverinfo_replay.proto"; + +message Response_ReplayList { + extend Response { + optional Response_ReplayList ext = 1100; + } + repeated ServerInfo_Replay replay_list = 1; +} diff --git a/common/pb/serverinfo_replay.proto b/common/pb/serverinfo_replay.proto new file mode 100644 index 00000000..1201e46c --- /dev/null +++ b/common/pb/serverinfo_replay.proto @@ -0,0 +1,9 @@ +message ServerInfo_Replay { + optional sint32 game_id = 1 [default = -1]; + optional string room_name = 2; + optional uint32 time_started = 3; + optional uint32 length = 4; + optional string game_name = 5; + optional string replay_name = 6; + repeated string player_names = 7; +} diff --git a/common/pb/serverinfo_user.proto b/common/pb/serverinfo_user.proto index 509756f5..b24f7e55 100644 --- a/common/pb/serverinfo_user.proto +++ b/common/pb/serverinfo_user.proto @@ -18,4 +18,5 @@ message ServerInfo_User { optional Gender gender = 5 [default = GenderUnknown]; optional string country = 6; optional bytes avatar_bmp = 7; + optional sint32 id = 8 [default = -1]; } diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index 77447972..dc9a61cc 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -16,6 +16,8 @@ message SessionCommand { DECK_UPLOAD = 1013; LIST_ROOMS = 1014; JOIN_ROOM = 1015; + REPLAY_LIST = 1100; + REPLAY_DOWNLOAD = 1101; } extensions 100 to max; } @@ -78,12 +80,6 @@ message Command_RemoveFromList { optional string user_name = 2; } -message Command_DeckList { - extend SessionCommand { - optional Command_DeckList ext = 1008; - } -} - message Command_ListRooms { extend SessionCommand { optional Command_ListRooms ext = 1014; diff --git a/common/server.cpp b/common/server.cpp index 558725da..886fd9df 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -58,7 +58,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString if ((authState == NotLoggedIn) || (authState == UserIsBanned)) return authState; - ServerInfo_User data = getUserData(name); + ServerInfo_User data = getUserData(name, true); data.set_address(session->getAddress().toStdString()); name = QString::fromStdString(data.name()); // Compensate for case indifference diff --git a/common/server.h b/common/server.h index 7f0a75c3..843cf86a 100644 --- a/common/server.h +++ b/common/server.h @@ -48,7 +48,7 @@ public: virtual bool isInBuddyList(const QString &whoseList, const QString &who) { return false; } virtual bool isInIgnoreList(const QString &whoseList, const QString &who) { return false; } - virtual void storeGameInformation(int secondsElapsed, const QStringList &allPlayersEver, const GameReplay &replay) { } + virtual void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const GameReplay &replay) { } protected: void prepareDestroy(); QList clients; @@ -59,7 +59,7 @@ protected: virtual void endSession(int sessionId) { } virtual bool userExists(const QString &user) { return false; } virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reason) { return UnknownUser; } - virtual ServerInfo_User getUserData(const QString &name) = 0; + virtual ServerInfo_User getUserData(const QString &name, bool withId = false) = 0; int getUsersCount() const; int getGamesCount() const; int nextGameId; diff --git a/common/server_game.cpp b/common/server_game.cpp index 99180d1a..9924e4ae 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -48,11 +48,11 @@ Server_Game::Server_Game(Server_ProtocolHandler *_creator, int _gameId, const QS : QObject(), room(_room), hostId(0), creatorInfo(new ServerInfo_User(_creator->copyUserInfo(false))), gameStarted(false), gameId(_gameId), description(_description), password(_password), maxPlayers(_maxPlayers), gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies), onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed), spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk), spectatorsSeeEverything(_spectatorsSeeEverything), inactivityCounter(0), secondsElapsed(0), startTime(QDateTime::currentDateTime()), gameMutex(QMutex::Recursive) { replay = new GameReplay; - replay->mutable_game_info()->CopyFrom(getInfo()); connect(this, SIGNAL(sigStartGameIfReady()), this, SLOT(doStartGameIfReady()), Qt::QueuedConnection); addPlayer(_creator, false, false); + replay->mutable_game_info()->CopyFrom(getInfo()); if (room->getServer()->getGameShouldPing()) { pingClock = new QTimer(this); @@ -80,7 +80,7 @@ Server_Game::~Server_Game() gameMutex.unlock(); room->roomMutex.unlock(); - room->getServer()->storeGameInformation(secondsElapsed, allPlayersEver.toList(), *replay); + room->getServer()->storeGameInformation(secondsElapsed, allPlayersEver, allSpectatorsEver, *replay); delete replay; qDebug() << "Server_Game destructor: gameId=" << gameId; @@ -324,7 +324,10 @@ Server_Player *Server_Game::addPlayer(Server_ProtocolHandler *handler, bool spec joinEvent.mutable_player_properties()->CopyFrom(newPlayer->getProperties(true)); sendGameEventContainer(prepareGameEvent(joinEvent, -1)); - allPlayersEver.insert(QString::fromStdString(newPlayer->getUserInfo()->name())); + if (spectator) + allSpectatorsEver.insert(QString::fromStdString(newPlayer->getUserInfo()->name())); + else + allPlayersEver.insert(QString::fromStdString(newPlayer->getUserInfo()->name())); players.insert(playerId, newPlayer); if (newPlayer->getUserInfo()->name() == creatorInfo->name()) { hostId = playerId; diff --git a/common/server_game.h b/common/server_game.h index 7348c457..e7efd1ba 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -45,7 +45,7 @@ private: int hostId; ServerInfo_User *creatorInfo; QMap players; - QSet allPlayersEver; + QSet allPlayersEver, allSpectatorsEver; bool gameStarted; int gameId; QString description; diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 3bd383ea..3091c403 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -42,11 +42,14 @@ #include "pb/command_shuffle.pb.h" #include "pb/command_stop_dump_zone.pb.h" #include "pb/command_undo_draw.pb.h" +#include "pb/command_deck_list.pb.h" #include "pb/command_deck_upload.pb.h" #include "pb/command_deck_download.pb.h" #include "pb/command_deck_new_dir.pb.h" #include "pb/command_deck_del_dir.pb.h" #include "pb/command_deck_del.pb.h" +#include "pb/command_replay_list.pb.h" +#include "pb/command_replay_download.pb.h" #include "pb/response.pb.h" #include "pb/response_login.pb.h" #include "pb/response_list_users.pb.h" @@ -137,8 +140,10 @@ ServerInfo_User Server_ProtocolHandler::copyUserInfo(bool complete, bool moderat ServerInfo_User result; if (userInfo) { result.CopyFrom(*userInfo); - if (!moderatorInfo) + if (!moderatorInfo) { result.clear_address(); + result.clear_id(); + } if (!complete) result.clear_avatar_bmp(); } @@ -234,6 +239,8 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co case SessionCommand::DECK_DEL: resp = cmdDeckDel(sc.GetExtension(Command_DeckDel::ext), rc); break; case SessionCommand::DECK_UPLOAD: resp = cmdDeckUpload(sc.GetExtension(Command_DeckUpload::ext), rc); break; case SessionCommand::DECK_DOWNLOAD: resp = cmdDeckDownload(sc.GetExtension(Command_DeckDownload::ext), rc); break; + case SessionCommand::REPLAY_LIST: resp = cmdReplayList(sc.GetExtension(Command_ReplayList::ext), rc); break; + case SessionCommand::REPLAY_DOWNLOAD: resp = cmdReplayDownload(sc.GetExtension(Command_ReplayDownload::ext), rc); break; case SessionCommand::GET_GAMES_OF_USER: resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc); break; case SessionCommand::GET_USER_INFO: resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc); break; case SessionCommand::LIST_ROOMS: resp = cmdListRooms(sc.GetExtension(Command_ListRooms::ext), rc); break; diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index f11a5187..9e35790c 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -39,6 +39,8 @@ class Command_DeckDelDir; class Command_DeckDel; class Command_DeckDownload; class Command_DeckUpload; +class Command_ReplayList; +class Command_ReplayDownload; class Command_ListRooms; class Command_JoinRoom; class Command_LeaveRoom; @@ -119,6 +121,8 @@ private: virtual Response::ResponseCode cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer &rc) = 0; virtual Response::ResponseCode cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc) = 0; virtual Response::ResponseCode cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc) = 0; + virtual Response::ResponseCode cmdReplayList(const Command_ReplayList &cmd, ResponseContainer &rc) = 0; + virtual Response::ResponseCode cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc) = 0; Response::ResponseCode cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc); Response::ResponseCode cmdListRooms(const Command_ListRooms &cmd, ResponseContainer &rc); diff --git a/common/server_room.h b/common/server_room.h index b58473fe..04fe4048 100644 --- a/common/server_room.h +++ b/common/server_room.h @@ -37,6 +37,7 @@ public: QString getDescription() const { return description; } bool getAutoJoin() const { return autoJoin; } QString getJoinMessage() const { return joinMessage; } + const QStringList &getGameTypes() const { return gameTypes; } const QMap &getGames() const { return games; } Server *getServer() const; ServerInfo_Room getInfo(bool complete, bool showGameTypes = false, bool updating = false) const; diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 22b2b265..b0c07f6f 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -323,29 +323,31 @@ bool Servatrice::isInIgnoreList(const QString &whoseList, const QString &who) return query.next(); } -ServerInfo_User Servatrice::evalUserQueryResult(const QSqlQuery &query, bool complete) +ServerInfo_User Servatrice::evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId) { ServerInfo_User result; - result.set_name(query.value(0).toString().toStdString()); + if (withId) + result.set_id(query.value(0).toInt()); + result.set_name(query.value(1).toString().toStdString()); - const QString country = query.value(4).toString(); + const QString country = query.value(5).toString(); if (!country.isEmpty()) result.set_country(country.toStdString()); if (complete) { - const QByteArray avatarBmp = query.value(5).toByteArray(); + const QByteArray avatarBmp = query.value(6).toByteArray(); if (avatarBmp.size()) result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); } - const QString genderStr = query.value(3).toString(); + const QString genderStr = query.value(4).toString(); if (genderStr == "m") result.set_gender(ServerInfo_User::Male); else if (genderStr == "f") result.set_gender(ServerInfo_User::Female); - const int is_admin = query.value(1).toInt(); + const int is_admin = query.value(2).toInt(); int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; if (is_admin == 1) userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; @@ -353,14 +355,14 @@ ServerInfo_User Servatrice::evalUserQueryResult(const QSqlQuery &query, bool com userLevel |= ServerInfo_User::IsModerator; result.set_user_level(userLevel); - const QString realName = query.value(2).toString(); + const QString realName = query.value(3).toString(); if (!realName.isEmpty()) result.set_real_name(realName.toStdString()); return result; } -ServerInfo_User Servatrice::getUserData(const QString &name) +ServerInfo_User Servatrice::getUserData(const QString &name, bool withId) { ServerInfo_User result; result.set_name(name.toStdString()); @@ -372,13 +374,13 @@ ServerInfo_User Servatrice::getUserData(const QString &name) return result; QSqlQuery query; - query.prepare("select name, admin, realname, gender, country, avatar_bmp from " + dbPrefix + "_users where name = :name and active = 1"); + query.prepare("select id, name, admin, realname, gender, country, avatar_bmp from " + dbPrefix + "_users where name = :name and active = 1"); query.bindValue(":name", name); if (!execSqlQuery(query)) return result; if (query.next()) - return evalUserQueryResult(query, true); + return evalUserQueryResult(query, true, withId); else return result; } else @@ -447,7 +449,7 @@ QMap Servatrice::getBuddyList(const QString &name) checkSql(); QSqlQuery query; - query.prepare("select a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_buddylist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); + query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_buddylist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); query.bindValue(":name", name); if (!execSqlQuery(query)) return result; @@ -469,7 +471,7 @@ QMap Servatrice::getIgnoreList(const QString &name) checkSql(); QSqlQuery query; - query.prepare("select a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_ignorelist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); + query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_ignorelist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); query.bindValue(":name", name); if (!execSqlQuery(query)) return result; @@ -536,11 +538,14 @@ void Servatrice::statusUpdate() execSqlQuery(query); } -void Servatrice::storeGameInformation(int secondsElapsed, const QStringList &allPlayersEver, const GameReplay &replay) +void Servatrice::storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const GameReplay &replay) { + Server_Room *room = rooms.value(replay.game_info().room_id()); + + const QStringList &allGameTypes = room->getGameTypes(); QStringList gameTypes; for (int i = replay.game_info().game_types_size() - 1; i >= 0; --i) - gameTypes.append(QString::number(replay.game_info().game_types(i))); + gameTypes.append(allGameTypes[replay.game_info().game_types(i)]); QByteArray replayBlob; const unsigned int size = replay.ByteSize(); @@ -552,29 +557,48 @@ void Servatrice::storeGameInformation(int secondsElapsed, const QStringList &all return; QSqlQuery query1; - query1.prepare("insert into " + dbPrefix + "_games (id_room, id, descr, creator_name, password, game_types, player_count, time_started, time_finished, replay) values (:id_room, :id_game, :descr, :creator_name, :password, :game_types, :player_count, date_sub(now(), interval :seconds second), now(), :replay)"); - query1.bindValue(":id_room", replay.game_info().room_id()); + query1.prepare("insert into " + dbPrefix + "_games (room_name, id, descr, creator_name, password, game_types, player_count, time_started, time_finished, replay) values (:id_room, :id_game, :descr, :creator_name, :password, :game_types, :player_count, date_sub(now(), interval :seconds second), now(), :replay)"); + query1.bindValue(":room_name", room->getName()); query1.bindValue(":id_game", replay.game_info().game_id()); query1.bindValue(":descr", QString::fromStdString(replay.game_info().description())); query1.bindValue(":creator_name", QString::fromStdString(replay.game_info().creator_info().name())); query1.bindValue(":password", replay.game_info().with_password() ? 1 : 0); - query1.bindValue(":game_types", gameTypes.isEmpty() ? QString("") : gameTypes.join(",")); + query1.bindValue(":game_types", gameTypes.isEmpty() ? QString("") : gameTypes.join(", ")); query1.bindValue(":player_count", replay.game_info().max_players()); query1.bindValue(":seconds", secondsElapsed); query1.bindValue(":replay", replayBlob); if (!execSqlQuery(query1)) return; + QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; + QSetIterator playerIterator(allPlayersEver); + while (playerIterator.hasNext()) { + gameIds1.append(replay.game_info().game_id()); + playerNames.append(playerIterator.next()); + } + QSet allUsersInGame = allPlayersEver + allSpectatorsEver; + QSetIterator allUsersIterator(allUsersInGame); + while (allUsersIterator.hasNext()) { + int id = getUserIdInDB(allUsersIterator.next()); + if (id == -1) + continue; + gameIds2.append(replay.game_info().game_id()); + userIds.append(id); + replayNames.append(QString::fromStdString(replay.game_info().description())); + } + QSqlQuery query2; query2.prepare("insert into " + dbPrefix + "_games_players (id_game, player_name) values (:id_game, :player_name)"); - QVariantList gameIds, playerNames; - for (int i = allPlayersEver.size() - 1; i >= 0; --i) { - gameIds.append(replay.game_info().game_id()); - playerNames.append(allPlayersEver[i]); - } - query2.bindValue(":id_game", gameIds); + query2.bindValue(":id_game", gameIds1); query2.bindValue(":player_name", playerNames); query2.execBatch(); + + QSqlQuery query3; + query3.prepare("insert into " + dbPrefix + "_replays_access (id_game, id_player, replay_name) values (:id_game, :id_player, :replay_name)"); + query3.bindValue(":id_game", gameIds2); + query3.bindValue(":id_player", userIds); + query3.bindValue(":replay_name", replayNames); + query3.execBatch(); } void Servatrice::scheduleShutdown(const QString &reason, int minutes) diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 08b98612..8aa84fef 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -71,7 +71,7 @@ public: bool getThreaded() const { return threaded; } QString getDbPrefix() const { return dbPrefix; } void updateLoginMessage(); - ServerInfo_User getUserData(const QString &name); + ServerInfo_User getUserData(const QString &name, bool withId = false); int getUsersWithAddress(const QHostAddress &address) const; QList getUsersWithAddressAsList(const QHostAddress &address) const; QMap getBuddyList(const QString &name); @@ -82,7 +82,7 @@ public: void incTxBytes(quint64 num); void incRxBytes(quint64 num); int getUserIdInDB(const QString &name); - void storeGameInformation(int secondsElapsed, const QStringList &allPlayersEver, const GameReplay &replay); + void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const GameReplay &replay); protected: int startSession(const QString &userName, const QString &address); void endSession(int sessionId); @@ -106,7 +106,7 @@ private: quint64 txBytes, rxBytes; int maxGameInactivityTime, maxPlayerInactivityTime; int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser; - ServerInfo_User evalUserQueryResult(const QSqlQuery &query, bool complete); + ServerInfo_User evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId = false); QString shutdownReason; int shutdownMinutes; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index d14e8ca0..c46fdc87 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -34,6 +34,8 @@ #include "pb/command_deck_new_dir.pb.h" #include "pb/command_deck_del_dir.pb.h" #include "pb/command_deck_del.pb.h" +#include "pb/command_replay_list.pb.h" +#include "pb/command_replay_download.pb.h" #include "pb/event_connection_closed.pb.h" #include "pb/event_server_message.pb.h" #include "pb/event_server_identification.pb.h" @@ -42,6 +44,9 @@ #include "pb/response_deck_list.pb.h" #include "pb/response_deck_download.pb.h" #include "pb/response_deck_upload.pb.h" +#include "pb/response_replay_list.pb.h" +#include "pb/response_replay_download.pb.h" +#include "pb/serverinfo_replay.pb.h" #include "pb/serverinfo_user.pb.h" #include "pb/serverinfo_deckstorage.pb.h" @@ -175,7 +180,7 @@ Response::ResponseCode ServerSocketInterface::cmdAddToList(const Command_AddToLi if (servatrice->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) return Response::RespContextError; - int id1 = servatrice->getUserIdInDB(QString::fromStdString(userInfo->name())); + int id1 = userInfo->id(); int id2 = servatrice->getUserIdInDB(user); if (id2 < 0) return Response::RespNameNotFound; @@ -216,7 +221,7 @@ Response::ResponseCode ServerSocketInterface::cmdRemoveFromList(const Command_Re if (!servatrice->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) return Response::RespContextError; - int id1 = servatrice->getUserIdInDB(QString::fromStdString(userInfo->name())); + int id1 = userInfo->id(); int id2 = servatrice->getUserIdInDB(user); if (id2 < 0) return Response::RespNameNotFound; @@ -485,6 +490,77 @@ Response::ResponseCode ServerSocketInterface::cmdDeckDownload(const Command_Deck return Response::RespOk; } +Response::ResponseCode ServerSocketInterface::cmdReplayList(const Command_ReplayList & /*cmd*/, ResponseContainer &rc) +{ + if (authState != PasswordRight) + return Response::RespFunctionNotAllowed; + + Response_ReplayList *re = new Response_ReplayList; + + servatrice->dbMutex.lock(); + QSqlQuery query1; + query1.prepare("select a.id_game, a.replay_name, b.room_name, b.time_started, b.time_finished, b.descr from cockatrice_replays_access a left join cockatrice_games b on b.id = a.id_game where a.id_player = :id_player"); + query1.bindValue(":id_player", userInfo->id()); + servatrice->execSqlQuery(query1); + while (query1.next()) { + ServerInfo_Replay *replayInfo = re->add_replay_list(); + const int gameId = query1.value(0).toInt(); + replayInfo->set_game_id(gameId); + replayInfo->set_room_name(query1.value(2).toString().toStdString()); + const int timeStarted = query1.value(3).toDateTime().toTime_t(); + const int timeFinished = query1.value(4).toDateTime().toTime_t(); + replayInfo->set_time_started(timeStarted); + replayInfo->set_length(timeFinished - timeStarted); + replayInfo->set_game_name(query1.value(5).toString().toStdString()); + replayInfo->set_replay_name(query1.value(1).toString().toStdString()); + + QSqlQuery query2; + query2.prepare("select player_name from cockatrice_games_players where id_game = :id_game"); + query2.bindValue(":id_game", gameId); + servatrice->execSqlQuery(query2); + while (query2.next()) + replayInfo->add_player_names(query2.value(0).toString().toStdString()); + } + servatrice->dbMutex.unlock(); + + rc.setResponseExtension(re); + return Response::RespOk; +} + +Response::ResponseCode ServerSocketInterface::cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc) +{ + if (authState != PasswordRight) + return Response::RespFunctionNotAllowed; + + QMutexLocker dbLocker(&servatrice->dbMutex); + + QSqlQuery query1; + query1.prepare("select 1 from " + servatrice->getDbPrefix() + "_replays_access where id_game = :id_game and id_player = :id_player"); + query1.bindValue(":id_game", cmd.game_id()); + query1.bindValue(":id_player", userInfo->id()); + if (!servatrice->execSqlQuery(query1)) + return Response::RespInternalError; + if (!query1.next()) + return Response::RespAccessDenied; + + QSqlQuery query2; + query2.prepare("select replay from " + servatrice->getDbPrefix() + "_games where id = :id_game"); + query2.bindValue(":id_game", cmd.game_id()); + if (!servatrice->execSqlQuery(query2)) + return Response::RespInternalError; + if (!query2.next()) + return Response::RespNameNotFound; + + QByteArray data = query2.value(0).toByteArray(); + + Response_ReplayDownload *re = new Response_ReplayDownload; + re->set_replay_data(data.data(), data.size()); + rc.setResponseExtension(re); + + return Response::RespOk; +} + + // MODERATOR FUNCTIONS. // May be called by admins and moderators. Permission is checked by the calling function. @@ -499,7 +575,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban query.prepare("insert into " + servatrice->getDbPrefix() + "_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason, :visible_reason)"); query.bindValue(":user_name", userName); query.bindValue(":ip_address", address); - query.bindValue(":id_admin", servatrice->getUserIdInDB(QString::fromStdString(userInfo->name()))); + query.bindValue(":id_admin", userInfo->id()); query.bindValue(":minutes", minutes); query.bindValue(":reason", QString::fromStdString(cmd.reason()) + "\n"); query.bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason()) + "\n"); diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 170475be..ef0dfd48 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -61,6 +61,8 @@ private: Response::ResponseCode cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc); DeckList *getDeckFromDatabase(int deckId); Response::ResponseCode cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc); + Response::ResponseCode cmdReplayList(const Command_ReplayList &cmd, ResponseContainer &rc); + Response::ResponseCode cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc); Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc); Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer &rc); Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc);