diff --git a/cockatrice/src/remotereplaylist_treewidget.cpp b/cockatrice/src/remotereplaylist_treewidget.cpp index 379b7261..d978fe33 100644 --- a/cockatrice/src/remotereplaylist_treewidget.cpp +++ b/cockatrice/src/remotereplaylist_treewidget.cpp @@ -9,10 +9,24 @@ #include "pb/response_replay_list.pb.h" #include "pb/serverinfo_replay.pb.h" +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))); +} + +RemoteReplayList_TreeModel::MatchNode::~MatchNode() +{ + for (int i = 0; i < size(); ++i) + delete at(i); +} + RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent) : QAbstractItemModel(parent), client(_client) { QFileIconProvider fip; + dirIcon = fip.icon(QFileIconProvider::Folder); fileIcon = fip.icon(QFileIconProvider::File); refreshTree(); @@ -20,47 +34,74 @@ RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() { + clearTree(); } int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : replays.size(); + if (!parent.isValid()) + return replayMatches.size(); + + MatchNode *matchNode = dynamic_cast(static_cast(parent.internalPointer())); + if (matchNode) + return matchNode->size(); + else + return 0; } int RemoteReplayList_TreeModel::columnCount(const QModelIndex &/*parent*/) const { - return 6; + return 5; } QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - if (index.column() > 5) + if (index.column() > 4) 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(", "); + ReplayNode *replayNode = dynamic_cast(static_cast(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 4: return replayInfo.duration(); + default: return QVariant(); } - 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(); + } + } else { + MatchNode *matchNode = dynamic_cast(static_cast(index.internalPointer())); + const ServerInfo_ReplayMatch &matchInfo = matchNode->getMatchInfo(); + switch (role) { + case Qt::TextAlignmentRole: + return index.column() == 0 ? Qt::AlignRight : 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 3: return QDateTime::fromTime_t(matchInfo.time_started()); + case 4: return matchInfo.length(); + default: return QVariant(); + } + } + case Qt::DecorationRole: + return index.column() == 0 ? dirIcon : QVariant(); } - case Qt::DecorationRole: - return index.column() == 0 ? fileIcon : QVariant(); } return QVariant(); } @@ -74,12 +115,11 @@ QVariant RemoteReplayList_TreeModel::headerData(int section, Qt::Orientation ori 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)"); + case 0: return tr("ID"); + case 1: return tr("Name"); + case 2: return tr("Players"); + case 3: return tr("Time started"); + case 4: return tr("Duration (sec)"); default: return QVariant(); } } @@ -91,13 +131,28 @@ QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelI { if (!hasIndex(row, column, parent)) return QModelIndex(); - - return createIndex(row, column, (void *) &(replays[row])); + + MatchNode *matchNode = dynamic_cast(static_cast(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 { - return QModelIndex(); + MatchNode const *matchNode = dynamic_cast(static_cast(ind.internalPointer())); + if (matchNode) + return QModelIndex(); + else { + ReplayNode *replayNode = dynamic_cast(static_cast(ind.internalPointer())); + return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode); + } } Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const @@ -108,12 +163,33 @@ Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } -ServerInfo_Replay const* RemoteReplayList_TreeModel::getNode(const QModelIndex &index) const +ServerInfo_Replay const* RemoteReplayList_TreeModel::getReplay(const QModelIndex &index) const { if (!index.isValid()) return 0; - return &(replays[index.row()]); + ReplayNode *node = dynamic_cast(static_cast(index.internalPointer())); + if (!node) + return 0; + return &node->getReplayInfo(); +} + +ServerInfo_ReplayMatch const* RemoteReplayList_TreeModel::getReplayMatch(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + MatchNode *node = dynamic_cast(static_cast(index.internalPointer())); + if (!node) + return 0; + return &node->getMatchInfo(); +} + +void RemoteReplayList_TreeModel::clearTree() +{ + for (int i = 0; i < replayMatches.size(); ++i) + delete replayMatches[i]; + replayMatches.clear(); } void RemoteReplayList_TreeModel::refreshTree() @@ -129,10 +205,10 @@ void RemoteReplayList_TreeModel::replayListFinished(const Response &r) const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext); beginResetModel(); - replays.clear(); + clearTree(); - for (int i = 0; i < resp.replay_list_size(); ++i) - replays.append(resp.replay_list(i)); + for (int i = 0; i < resp.match_list_size(); ++i) + replayMatches.append(new MatchNode(resp.match_list(i))); endResetModel(); emit treeRefreshed(); @@ -156,12 +232,7 @@ RemoteReplayList_TreeWidget::RemoteReplayList_TreeWidget(AbstractClient *_client header()->setSortIndicator(0, Qt::AscendingOrder); } -ServerInfo_Replay const *RemoteReplayList_TreeWidget::getNode(const QModelIndex &ind) const +ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentReplay() const { - return treeModel->getNode(proxyModel->mapToSource(ind)); -} - -ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentItem() const -{ - return getNode(selectionModel()->currentIndex()); + return treeModel->getReplay(proxyModel->mapToSource(selectionModel()->currentIndex())); } diff --git a/cockatrice/src/remotereplaylist_treewidget.h b/cockatrice/src/remotereplaylist_treewidget.h index 5cbb6736..9c1567ea 100644 --- a/cockatrice/src/remotereplaylist_treewidget.h +++ b/cockatrice/src/remotereplaylist_treewidget.h @@ -5,6 +5,7 @@ #include #include #include "pb/serverinfo_replay.pb.h" +#include "pb/serverinfo_replay_match.pb.h" class Response; class AbstractClient; @@ -13,10 +14,42 @@ class QSortFilterProxyModel; class RemoteReplayList_TreeModel : public QAbstractItemModel { Q_OBJECT private: - AbstractClient *client; - QList replays; + 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 { + private: + ServerInfo_ReplayMatch matchInfo; + public: + MatchNode(const ServerInfo_ReplayMatch &_matchInfo); + ~MatchNode(); + void clearTree(); + const ServerInfo_ReplayMatch &getMatchInfo() { return matchInfo; } + }; + class ReplayNode : public Node { + private: + MatchNode *parent; + ServerInfo_Replay replayInfo; + public: + ReplayNode(const ServerInfo_Replay &_replayInfo, MatchNode *_parent = 0) + : Node(QString::fromStdString(_replayInfo.replay_name())), parent(_parent), replayInfo(_replayInfo) { } + MatchNode *getParent() const { return parent; } + const ServerInfo_Replay &getReplayInfo() { return replayInfo; } + }; - QIcon fileIcon; + AbstractClient *client; + QList replayMatches; + + QIcon dirIcon, fileIcon; + void clearTree(); signals: void treeRefreshed(); private slots: @@ -32,7 +65,8 @@ public: QModelIndex parent(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const; void refreshTree(); - ServerInfo_Replay const *getNode(const QModelIndex &index) const; + ServerInfo_Replay const* getReplay(const QModelIndex &index) const; + ServerInfo_ReplayMatch const* getReplayMatch(const QModelIndex &index) const; }; class RemoteReplayList_TreeWidget : public QTreeView { @@ -42,7 +76,8 @@ private: ServerInfo_Replay const *getNode(const QModelIndex &ind) const; public: RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = 0); - ServerInfo_Replay const *getCurrentItem() const; + ServerInfo_Replay const *getCurrentReplay() const; + ServerInfo_ReplayMatch const *getCurrentReplayMatch() const; void refreshTree(); }; diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 254f7d5c..bb606ab5 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -198,7 +198,7 @@ void DeckViewContainer::setDeck(DeckList *deck) deckView->setDeck(deck); readyStartButton->setEnabled(true); } -#include + TabGame::TabGame(GameReplay *_replay) : Tab(0), hostId(-1), diff --git a/cockatrice/src/tab_replays.cpp b/cockatrice/src/tab_replays.cpp index 4c9d258d..bc430a10 100644 --- a/cockatrice/src/tab_replays.cpp +++ b/cockatrice/src/tab_replays.cpp @@ -123,12 +123,12 @@ void TabReplays::actOpenLocalReplay() void TabReplays::actOpenRemoteReplay() { - ServerInfo_Replay const *curRight = serverDirView->getCurrentItem(); + ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); if (!curRight) return; Command_ReplayDownload cmd; - cmd.set_game_id(curRight->game_id()); + cmd.set_replay_id(curRight->replay_id()); PendingCommand *pend = client->prepareSessionCommand(cmd); connect(pend, SIGNAL(finished(const Response &)), this, SLOT(openRemoteReplayFinished(const Response &))); @@ -156,13 +156,13 @@ void TabReplays::actDownload() filePath = localDirModel->filePath(curLeft); } - ServerInfo_Replay const *curRight = serverDirView->getCurrentItem(); + ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); if (!curRight) return; - filePath += QString("/game_%1.cor").arg(curRight->game_id()); + filePath += QString("/replay_%1.cor").arg(curRight->replay_id()); Command_ReplayDownload cmd; - cmd.set_game_id(curRight->game_id()); + cmd.set_replay_id(curRight->replay_id()); PendingCommand *pend = client->prepareSessionCommand(cmd); pend->setExtraData(filePath); diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt index 95628b02..f60ca819 100644 --- a/common/pb/CMakeLists.txt +++ b/common/pb/CMakeLists.txt @@ -126,6 +126,7 @@ SET(PROTO_FILES serverinfo_playerproperties.proto serverinfo_player.proto serverinfo_replay.proto + serverinfo_replay_match.proto serverinfo_room.proto serverinfo_user.proto serverinfo_zone.proto diff --git a/common/pb/command_replay_download.proto b/common/pb/command_replay_download.proto index 324f2f1e..ff7e2b34 100644 --- a/common/pb/command_replay_download.proto +++ b/common/pb/command_replay_download.proto @@ -4,5 +4,5 @@ message Command_ReplayDownload { extend SessionCommand { optional Command_ReplayDownload ext = 1101; } - optional sint32 game_id = 1 [default = -1]; + optional sint32 replay_id = 1 [default = -1]; } diff --git a/common/pb/game_replay.proto b/common/pb/game_replay.proto index 10bb233d..e3438e66 100644 --- a/common/pb/game_replay.proto +++ b/common/pb/game_replay.proto @@ -4,4 +4,5 @@ import "game_event_container.proto"; message GameReplay { optional ServerInfo_Game game_info = 1; repeated GameEventContainer event_list = 2; + optional uint32 duration_seconds = 3; } diff --git a/common/pb/response_replay_list.proto b/common/pb/response_replay_list.proto index 6a4c29f6..93ffb8bb 100644 --- a/common/pb/response_replay_list.proto +++ b/common/pb/response_replay_list.proto @@ -1,9 +1,9 @@ import "response.proto"; -import "serverinfo_replay.proto"; +import "serverinfo_replay_match.proto"; message Response_ReplayList { extend Response { optional Response_ReplayList ext = 1100; } - repeated ServerInfo_Replay replay_list = 1; + repeated ServerInfo_ReplayMatch match_list = 1; } diff --git a/common/pb/serverinfo_replay.proto b/common/pb/serverinfo_replay.proto index 1201e46c..18f68317 100644 --- a/common/pb/serverinfo_replay.proto +++ b/common/pb/serverinfo_replay.proto @@ -1,9 +1,5 @@ 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; + optional sint32 replay_id = 1 [default = -1]; + optional string replay_name = 2; + optional uint32 duration = 3; } diff --git a/common/server.h b/common/server.h index 843cf86a..b696190a 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 QSet &allPlayersEver, const QSet &allSpectatorsEver, const GameReplay &replay) { } + virtual void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replays) { } protected: void prepareDestroy(); QList clients; diff --git a/common/server_game.cpp b/common/server_game.cpp index 9924e4ae..99887d78 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -45,14 +45,15 @@ #include Server_Game::Server_Game(Server_ProtocolHandler *_creator, int _gameId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, Server_Room *_room) - : 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) + : 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), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), startTime(QDateTime::currentDateTime()), gameMutex(QMutex::Recursive) { - replay = new GameReplay; + currentReplay = new GameReplay; connect(this, SIGNAL(sigStartGameIfReady()), this, SLOT(doStartGameIfReady()), Qt::QueuedConnection); addPlayer(_creator, false, false); - replay->mutable_game_info()->CopyFrom(getInfo()); + + currentReplay->mutable_game_info()->CopyFrom(getInfo()); if (room->getServer()->getGameShouldPing()) { pingClock = new QTimer(this); @@ -80,8 +81,12 @@ Server_Game::~Server_Game() gameMutex.unlock(); room->roomMutex.unlock(); - room->getServer()->storeGameInformation(secondsElapsed, allPlayersEver, allSpectatorsEver, *replay); - delete replay; + currentReplay->set_duration_seconds(secondsElapsed - startTimeOfThisGame); + replayList.append(currentReplay); + room->getServer()->storeGameInformation(secondsElapsed, allPlayersEver, allSpectatorsEver, replayList); + + for (int i = 0; i < replayList.size(); ++i) + delete replayList[i]; qDebug() << "Server_Game destructor: gameId=" << gameId; } @@ -176,9 +181,9 @@ void Server_Game::sendGameStateToPlayers() omniscientEvent.add_player_list()->CopyFrom(omniscientGameStateIterator.next()); GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1); - replayCont->set_seconds_elapsed(secondsElapsed); + replayCont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame); replayCont->clear_game_id(); - replay->add_event_list()->CopyFrom(*replayCont); + currentReplay->add_event_list()->CopyFrom(*replayCont); delete replayCont; // If spectators are not omniscient, we need an additional getGameState call, otherwise we can use the data we used for the replay. @@ -237,6 +242,27 @@ void Server_Game::doStartGameIfReady() player->setReadyStart(false); } + if (firstGameStarted) { + currentReplay->set_duration_seconds(secondsElapsed - startTimeOfThisGame); + replayList.append(currentReplay); + currentReplay = new GameReplay; + currentReplay->mutable_game_info()->CopyFrom(getInfo()); + + Event_GameStateChanged omniscientEvent; + QListIterator omniscientGameStateIterator(getGameState(0, true, true)); + while (omniscientGameStateIterator.hasNext()) + omniscientEvent.add_player_list()->CopyFrom(omniscientGameStateIterator.next()); + + GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1); + replayCont->set_seconds_elapsed(0); + replayCont->clear_game_id(); + currentReplay->add_event_list()->CopyFrom(*replayCont); + delete replayCont; + + startTimeOfThisGame = secondsElapsed; + } else + firstGameStarted = true; + sendGameStateToPlayers(); activePlayer = -1; @@ -624,9 +650,9 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont, GameEventStor p->sendGameEvent(*cont); } if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) { - cont->set_seconds_elapsed(secondsElapsed); + cont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame); cont->clear_game_id(); - replay->add_event_list()->CopyFrom(*cont); + currentReplay->add_event_list()->CopyFrom(*cont); } delete cont; diff --git a/common/server_game.h b/common/server_game.h index e7efd1ba..5e3e3803 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -59,10 +59,12 @@ private: bool spectatorsCanTalk; bool spectatorsSeeEverything; int inactivityCounter; - int secondsElapsed; + int startTimeOfThisGame, secondsElapsed; + bool firstGameStarted; QDateTime startTime; QTimer *pingClock; - GameReplay *replay; + QList replayList; + GameReplay *currentReplay; signals: void sigStartGameIfReady(); private slots: diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index b0c07f6f..3ec03785 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -538,42 +538,21 @@ void Servatrice::statusUpdate() execSqlQuery(query); } -void Servatrice::storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const GameReplay &replay) +void Servatrice::storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replayList) { - Server_Room *room = rooms.value(replay.game_info().room_id()); + const ServerInfo_Game &gameInfo = replayList.first()->game_info(); + + Server_Room *room = rooms.value(gameInfo.room_id()); const QStringList &allGameTypes = room->getGameTypes(); QStringList gameTypes; - for (int i = replay.game_info().game_types_size() - 1; i >= 0; --i) - gameTypes.append(allGameTypes[replay.game_info().game_types(i)]); - - QByteArray replayBlob; - const unsigned int size = replay.ByteSize(); - replayBlob.resize(size); - replay.SerializeToArray(replayBlob.data(), size); - - QMutexLocker locker(&dbMutex); - if (!checkSql()) - return; - - QSqlQuery query1; - 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(":player_count", replay.game_info().max_players()); - query1.bindValue(":seconds", secondsElapsed); - query1.bindValue(":replay", replayBlob); - if (!execSqlQuery(query1)) - return; + for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) + gameTypes.append(allGameTypes[gameInfo.game_types(i)]); QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; QSetIterator playerIterator(allPlayersEver); while (playerIterator.hasNext()) { - gameIds1.append(replay.game_info().game_id()); + gameIds1.append(gameInfo.game_id()); playerNames.append(playerIterator.next()); } QSet allUsersInGame = allPlayersEver + allSpectatorsEver; @@ -582,17 +561,53 @@ void Servatrice::storeGameInformation(int secondsElapsed, const QSet &a int id = getUserIdInDB(allUsersIterator.next()); if (id == -1) continue; - gameIds2.append(replay.game_info().game_id()); + gameIds2.append(gameInfo.game_id()); userIds.append(id); - replayNames.append(QString::fromStdString(replay.game_info().description())); + replayNames.append(QString::fromStdString(gameInfo.description())); } + QVariantList replayGameIds, replayDurations, replayBlobs; + for (int i = 0; i < replayList.size(); ++i) { + QByteArray blob; + const unsigned int size = replayList[i]->ByteSize(); + blob.resize(size); + replayList[i]->SerializeToArray(blob.data(), size); + + replayGameIds.append(gameInfo.game_id()); + replayDurations.append(replayList[i]->duration_seconds()); + replayBlobs.append(blob); + } + + QMutexLocker locker(&dbMutex); + if (!checkSql()) + return; + + QSqlQuery query1; + query1.prepare("insert into " + dbPrefix + "_games (room_name, id, descr, creator_name, password, game_types, player_count, time_started, time_finished) values (:id_room, :id_game, :descr, :creator_name, :password, :game_types, :player_count, date_sub(now(), interval :seconds second), now())"); + query1.bindValue(":room_name", room->getName()); + query1.bindValue(":id_game", gameInfo.game_id()); + query1.bindValue(":descr", QString::fromStdString(gameInfo.description())); + query1.bindValue(":creator_name", QString::fromStdString(gameInfo.creator_info().name())); + query1.bindValue(":password", gameInfo.with_password() ? 1 : 0); + query1.bindValue(":game_types", gameTypes.isEmpty() ? QString("") : gameTypes.join(", ")); + query1.bindValue(":player_count", gameInfo.max_players()); + query1.bindValue(":seconds", secondsElapsed); + if (!execSqlQuery(query1)) + return; + QSqlQuery query2; query2.prepare("insert into " + dbPrefix + "_games_players (id_game, player_name) values (:id_game, :player_name)"); query2.bindValue(":id_game", gameIds1); query2.bindValue(":player_name", playerNames); query2.execBatch(); + QSqlQuery replayQuery1; + replayQuery1.prepare("insert into " + dbPrefix + "_replays (id_game, duration, replay) values (:id_game, :duration, :replay)"); + replayQuery1.bindValue(":id_game", replayGameIds); + replayQuery1.bindValue(":duration", replayDurations); + replayQuery1.bindValue(":replay", replayBlobs); + replayQuery1.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); diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 8aa84fef..99a22300 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -82,7 +82,7 @@ public: void incTxBytes(quint64 num); void incRxBytes(quint64 num); int getUserIdInDB(const QString &name); - void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const GameReplay &replay); + void storeGameInformation(int secondsElapsed, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replays); protected: int startSession(const QString &userName, const QString &address); void endSession(int sessionId); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index c46fdc87..95a844ad 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -503,23 +503,35 @@ Response::ResponseCode ServerSocketInterface::cmdReplayList(const Command_Replay query1.bindValue(":id_player", userInfo->id()); servatrice->execSqlQuery(query1); while (query1.next()) { - ServerInfo_Replay *replayInfo = re->add_replay_list(); + ServerInfo_ReplayMatch *matchInfo = re->add_match_list(); + const int gameId = query1.value(0).toInt(); - replayInfo->set_game_id(gameId); - replayInfo->set_room_name(query1.value(2).toString().toStdString()); + matchInfo->set_game_id(gameId); + matchInfo->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()); + matchInfo->set_time_started(timeStarted); + matchInfo->set_length(timeFinished - timeStarted); + matchInfo->set_game_name(query1.value(5).toString().toStdString()); + const QString replayName = query1.value(1).toString(); 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()); + matchInfo->add_player_names(query2.value(0).toString().toStdString()); + + QSqlQuery query3; + query3.prepare("select id, duration from " + servatrice->getDbPrefix() + "_replays where id_game = :id_game"); + query3.bindValue(":id_game", gameId); + servatrice->execSqlQuery(query3); + while (query3.next()) { + ServerInfo_Replay *replayInfo = matchInfo->add_replay_list(); + replayInfo->set_replay_id(query3.value(0).toInt()); + replayInfo->set_replay_name(replayName.toStdString()); + replayInfo->set_duration(query3.value(1).toInt()); + } } servatrice->dbMutex.unlock(); @@ -535,8 +547,8 @@ Response::ResponseCode ServerSocketInterface::cmdReplayDownload(const Command_Re 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.prepare("select 1 from " + servatrice->getDbPrefix() + "_replays_access a left join " + servatrice->getDbPrefix() + "_replays b on a.id_game = b.id_game where b.id = :id_replay and a.id_player = :id_player"); + query1.bindValue(":id_replay", cmd.replay_id()); query1.bindValue(":id_player", userInfo->id()); if (!servatrice->execSqlQuery(query1)) return Response::RespInternalError; @@ -544,8 +556,8 @@ Response::ResponseCode ServerSocketInterface::cmdReplayDownload(const Command_Re return Response::RespAccessDenied; QSqlQuery query2; - query2.prepare("select replay from " + servatrice->getDbPrefix() + "_games where id = :id_game"); - query2.bindValue(":id_game", cmd.game_id()); + query2.prepare("select replay from " + servatrice->getDbPrefix() + "_replays where id = :id_replay"); + query2.bindValue(":id_replay", cmd.replay_id()); if (!servatrice->execSqlQuery(query2)) return Response::RespInternalError; if (!query2.next())