diff --git a/common/server.cpp b/common/server.cpp index f351f190..49e36d8b 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -129,6 +129,24 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString return authState; } +void Server::addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId) +{ + QWriteLocker locker(&persistentPlayersLock); + persistentPlayers.insert(userName, PlayerReference(roomId, gameId, playerId)); +} + +void Server::removePersistentPlayer(const QString &userName, int roomId, int gameId, int playerId) +{ + QWriteLocker locker(&persistentPlayersLock); + persistentPlayers.remove(userName, PlayerReference(roomId, gameId, playerId)); +} + +QList Server::getPersistentPlayerReferences(const QString &userName) const +{ + QReadLocker locker(&persistentPlayersLock); + return persistentPlayers.values(userName); +} + void Server::addClient(Server_ProtocolHandler *client) { QWriteLocker locker(&clientsLock); @@ -181,24 +199,56 @@ void Server::externalUserJoined(const ServerInfo_User &userInfo) if (clients[i]->getAcceptsUserListChanges()) clients[i]->sendProtocolItem(*se); delete se; + clientsLock.unlock(); + + ResponseContainer rc(-1); + newUser->joinPersistentGames(rc); + newUser->sendResponseContainer(rc, Response::RespNothing); } void Server::externalUserLeft(const QString &userName) { // This function is always called from the main thread via signal/slot. - QWriteLocker locker(&clientsLock); + clientsLock.lockForWrite(); Server_AbstractUserInterface *user = externalUsers.take(userName); externalUsersBySessionId.remove(user->getUserInfo()->session_id()); + clientsLock.unlock(); + + QMap > userGames(user->getGames()); + QMapIterator > userGamesIterator(userGames); + roomsLock.lockForRead(); + while (userGamesIterator.hasNext()) { + userGamesIterator.next(); + Server_Room *room = rooms.value(userGamesIterator.value().first); + if (!room) + continue; + + QMutexLocker roomGamesLocker(&room->gamesMutex); + Server_Game *game = room->getGames().value(userGamesIterator.key()); + if (!game) + continue; + + QMutexLocker gameLocker(&game->gameMutex); + Server_Player *player = game->getPlayers().value(userGamesIterator.value().second); + if (!player) + continue; + + player->disconnectClient(); + } + roomsLock.unlock(); + delete user; Event_UserLeft event; event.set_name(userName.toStdString()); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + clientsLock.lockForRead(); for (int i = 0; i < clients.size(); ++i) if (clients[i]->getAcceptsUserListChanges()) clients[i]->sendProtocolItem(*se); + clientsLock.unlock(); delete se; } diff --git a/common/server.h b/common/server.h index 6ef9d654..a314898d 100644 --- a/common/server.h +++ b/common/server.h @@ -4,9 +4,11 @@ #include #include #include +#include #include #include #include "pb/serverinfo_user.pb.h" +#include "server_player_reference.h" class Server_Game; class Server_Room; @@ -77,6 +79,13 @@ public: void addExternalUser(const ServerInfo_User &userInfo); void removeExternalUser(const QString &userName); const QMap &getExternalUsers() const { return externalUsers; } + + void addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId); + void removePersistentPlayer(const QString &userName, int roomId, int gameId, int playerId); + QList getPersistentPlayerReferences(const QString &userName) const; +private: + QMultiMap persistentPlayers; + mutable QReadWriteLock persistentPlayersLock; protected slots: void externalUserJoined(const ServerInfo_User &userInfo); void externalUserLeft(const QString &userName); diff --git a/common/server_abstractuserinterface.cpp b/common/server_abstractuserinterface.cpp index ebd7d983..b4f59d75 100644 --- a/common/server_abstractuserinterface.cpp +++ b/common/server_abstractuserinterface.cpp @@ -1,7 +1,15 @@ #include #include +#include #include "server_abstractuserinterface.h" +#include "server_game.h" #include "server_response_containers.h" +#include "server_player_reference.h" +#include "server.h" +#include "server_room.h" +#include "server_game.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_game_state_changed.pb.h" #include void Server_AbstractUserInterface::sendProtocolItemByType(ServerMessage::MessageType type, const ::google::protobuf::Message &item) @@ -27,15 +35,61 @@ void Server_AbstractUserInterface::sendResponseContainer(const ResponseContainer for (int i = 0; i < preResponseQueue.size(); ++i) sendProtocolItemByType(preResponseQueue[i].first, *preResponseQueue[i].second); - Response response; - response.set_cmd_id(responseContainer.getCmdId()); - response.set_response_code(responseCode); - ::google::protobuf::Message *responseExtension = responseContainer.getResponseExtension(); - if (responseExtension) - response.GetReflection()->MutableMessage(&response, responseExtension->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*responseExtension); - sendProtocolItem(response); + if (responseCode != Response::RespNothing) { + Response response; + response.set_cmd_id(responseContainer.getCmdId()); + response.set_response_code(responseCode); + ::google::protobuf::Message *responseExtension = responseContainer.getResponseExtension(); + if (responseExtension) + response.GetReflection()->MutableMessage(&response, responseExtension->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*responseExtension); + sendProtocolItem(response); + } const QList > &postResponseQueue = responseContainer.getPostResponseQueue(); for (int i = 0; i < postResponseQueue.size(); ++i) sendProtocolItemByType(postResponseQueue[i].first, *postResponseQueue[i].second); } + +void Server_AbstractUserInterface::playerRemovedFromGame(Server_Game *game) +{ + qDebug() << "Server_AbstractUserInterface::playerRemovedFromGame(): gameId =" << game->getGameId(); + + QMutexLocker locker(&gameListMutex); + games.remove(game->getGameId()); +} + +void Server_AbstractUserInterface::playerAddedToGame(int gameId, int roomId, int playerId) +{ + qDebug() << "Server_AbstractUserInterface::playerAddedToGame(): gameId =" << gameId; + + QMutexLocker locker(&gameListMutex); + games.insert(gameId, QPair(roomId, playerId)); +} + +void Server_AbstractUserInterface::joinPersistentGames(ResponseContainer &rc) +{ + QList gamesToJoin = server->getPersistentPlayerReferences(QString::fromStdString(userInfo->name())); + + server->roomsLock.lockForRead(); + for (int i = 0; i < gamesToJoin.size(); ++i) { + const PlayerReference &pr = gamesToJoin.at(i); + + Server_Room *room = server->getRooms().value(pr.getRoomId()); + if (!room) + continue; + QMutexLocker roomGamesLocker(&room->gamesMutex); + + Server_Game *game = room->getGames().value(pr.getGameId()); + if (!game) + continue; + QMutexLocker gameLocker(&game->gameMutex); + + Server_Player *player = game->getPlayers().value(pr.getPlayerId()); + + player->setUserInterface(this); + playerAddedToGame(game->getGameId(), room->getId(), player->getPlayerId()); + + game->createGameJoinedEvent(player, rc, true); + } + server->roomsLock.unlock(); +} diff --git a/common/server_abstractuserinterface.h b/common/server_abstractuserinterface.h index b73b247b..485162e0 100644 --- a/common/server_abstractuserinterface.h +++ b/common/server_abstractuserinterface.h @@ -1,6 +1,9 @@ #ifndef SERVER_ABSTRACTUSERINTERFACE #define SERVER_ABSTRACTUSERINTERFACE +#include +#include +#include #include "serverinfo_user_container.h" #include "pb/server_message.pb.h" #include "pb/response.pb.h" @@ -14,6 +17,9 @@ class Server; class Server_Game; class Server_AbstractUserInterface : public ServerInfo_User_Container { +private: + mutable QMutex gameListMutex; + QMap > games; // gameId -> (roomId, playerId) protected: Server *server; public: @@ -23,8 +29,11 @@ public: virtual int getLastCommandTime() const = 0; - virtual void playerRemovedFromGame(Server_Game *game) = 0; - virtual void playerAddedToGame(int gameId, int roomId, int playerId) = 0; + void playerRemovedFromGame(Server_Game *game); + void playerAddedToGame(int gameId, int roomId, int playerId); + void joinPersistentGames(ResponseContainer &rc); + + QMap > getGames() const { QMutexLocker locker(&gameListMutex); return games; } virtual void sendProtocolItem(const Response &item) = 0; virtual void sendProtocolItem(const SessionEvent &item) = 0; diff --git a/common/server_game.cpp b/common/server_game.cpp index 587356e7..c1302977 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -48,6 +48,7 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, 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), + nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)), gameStarted(false), @@ -371,58 +372,38 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, Respons { QMutexLocker locker(&gameMutex); - const QList &keyList = players.keys(); - int playerId = keyList.isEmpty() ? 0 : (keyList.last() + 1); - - Server_Player *newPlayer = new Server_Player(this, playerId, userInterface->copyUserInfo(true), spectator, userInterface); + Server_Player *newPlayer = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true), spectator, userInterface); newPlayer->moveToThread(thread()); Event_Join joinEvent; joinEvent.mutable_player_properties()->CopyFrom(newPlayer->getProperties(true)); sendGameEventContainer(prepareGameEvent(joinEvent, -1)); + const QString playerName = QString::fromStdString(newPlayer->getUserInfo()->name()); if (spectator) - allSpectatorsEver.insert(QString::fromStdString(newPlayer->getUserInfo()->name())); + allSpectatorsEver.insert(playerName); else - allPlayersEver.insert(QString::fromStdString(newPlayer->getUserInfo()->name())); - players.insert(playerId, newPlayer); + allPlayersEver.insert(playerName); + players.insert(newPlayer->getPlayerId(), newPlayer); if (newPlayer->getUserInfo()->name() == creatorInfo->name()) { - hostId = playerId; - sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), playerId)); + hostId = newPlayer->getPlayerId(); + sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); } if (broadcastUpdate) emit gameInfoChanged(getInfo()); + if ((newPlayer->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !spectator) + room->getServer()->addPersistentPlayer(playerName, room->getId(), gameId, newPlayer->getPlayerId()); + userInterface->playerAddedToGame(gameId, room->getId(), newPlayer->getPlayerId()); - Event_GameJoined event1; - event1.set_room_id(room->getId()); - event1.set_game_id(gameId); - event1.set_game_description(description.toStdString()); - event1.set_host_id(hostId); - event1.set_player_id(newPlayer->getPlayerId()); - event1.set_spectator(newPlayer->getSpectator()); - event1.set_spectators_can_talk(spectatorsCanTalk); - event1.set_spectators_see_everything(spectatorsSeeEverything); - event1.set_resuming(false); - rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, Server_AbstractUserInterface::prepareSessionEvent(event1)); - - Event_GameStateChanged event2; - QListIterator gameStateIterator(getGameState(newPlayer, false, true)); - while (gameStateIterator.hasNext()) - event2.add_player_list()->CopyFrom(gameStateIterator.next()); - event2.set_seconds_elapsed(secondsElapsed); - event2.set_game_started(gameStarted); - event2.set_active_player_id(activePlayer); - event2.set_active_phase(activePhase); - rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1)); + createGameJoinedEvent(newPlayer, rc, false); } void Server_Game::removePlayer(Server_Player *player) { - QMutexLocker locker(&gameMutex); - + room->getServer()->removePersistentPlayer(QString::fromStdString(player->getUserInfo()->name()), room->getId(), gameId, player->getPlayerId()); players.remove(player->getPlayerId()); GameEventStorage ges; @@ -688,6 +669,31 @@ QList Server_Game::getGameState(Server_Player *playerWhosAski return result; } +void Server_Game::createGameJoinedEvent(Server_Player *player, ResponseContainer &rc, bool resuming) +{ + Event_GameJoined event1; + event1.set_room_id(room->getId()); + event1.set_game_id(gameId); + event1.set_game_description(description.toStdString()); + event1.set_host_id(hostId); + event1.set_player_id(player->getPlayerId()); + event1.set_spectator(player->getSpectator()); + event1.set_spectators_can_talk(spectatorsCanTalk); + event1.set_spectators_see_everything(spectatorsSeeEverything); + event1.set_resuming(resuming); + rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, Server_AbstractUserInterface::prepareSessionEvent(event1)); + + Event_GameStateChanged event2; + QListIterator gameStateIterator(getGameState(player, player->getSpectator() && spectatorsSeeEverything, true)); + while (gameStateIterator.hasNext()) + event2.add_player_list()->CopyFrom(gameStateIterator.next()); + event2.set_seconds_elapsed(secondsElapsed); + event2.set_game_started(gameStarted); + event2.set_active_player_id(activePlayer); + event2.set_active_phase(activePhase); + rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1)); +} + void Server_Game::sendGameEventContainer(GameEventContainer *cont, GameEventStorageItem::EventRecipients recipients, int privatePlayerId) { QMutexLocker locker(&gameMutex); diff --git a/common/server_game.h b/common/server_game.h index 1c27bada..3a8d4ee6 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -42,6 +42,7 @@ class Server_Game : public QObject { Q_OBJECT private: Server_Room *room; + int nextPlayerId; int hostId; ServerInfo_User *creatorInfo; QMap players; @@ -108,6 +109,7 @@ public: void nextTurn(); int getSecondsElapsed() const { return secondsElapsed; } + void createGameJoinedEvent(Server_Player *player, ResponseContainer &rc, bool resuming); void sendGameStateToPlayers(); QList getGameState(Server_Player *playerWhosAsking, bool omniscient = false, bool withUserInfo = false) const; diff --git a/common/server_player.cpp b/common/server_player.cpp index e707732b..e61f4902 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -1578,3 +1578,11 @@ void Server_Player::setUserInterface(Server_AbstractUserInterface *_userInterfac ges.enqueueGameEvent(event, playerId); ges.sendToGame(game); } + +void Server_Player::disconnectClient() +{ + if (!(userInfo->user_level() & ServerInfo_User::IsRegistered) || spectator) + game->removePlayer(this); + else + setUserInterface(0); +} diff --git a/common/server_player.h b/common/server_player.h index b5959f1c..927e69c1 100644 --- a/common/server_player.h +++ b/common/server_player.h @@ -83,6 +83,7 @@ public: void prepareDestroy(); Server_AbstractUserInterface *getUserInterface() const { return userInterface; } void setUserInterface(Server_AbstractUserInterface *_userInterface); + void disconnectClient(); void setPlayerId(int _id) { playerId = _id; } bool getReadyStart() const { return readyStart; } diff --git a/common/server_player_reference.h b/common/server_player_reference.h new file mode 100644 index 00000000..fc784a1a --- /dev/null +++ b/common/server_player_reference.h @@ -0,0 +1,17 @@ +#ifndef SERVER_PLAYER_REFERENCE_H +#define SERVER_PLAYER_REFERENCE_H + +class PlayerReference { +private: + int roomId; + int gameId; + int playerId; +public: + PlayerReference(int _roomId, int _gameId, int _playerId) : roomId(_roomId), gameId(_gameId), playerId(_playerId) { } + int getRoomId() const { return roomId; } + int getGameId() const { return gameId; } + int getPlayerId() const { return playerId; } + bool operator==(const PlayerReference &other) { return ((roomId == other.roomId) && (gameId == other.gameId) && (playerId == other.playerId)); } +}; + +#endif diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index b254a868..1aa2c27b 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -49,15 +49,11 @@ void Server_ProtocolHandler::prepareDestroy() { qDebug("Server_ProtocolHandler::prepareDestroy"); - server->removeClient(this); - QMapIterator roomIterator(rooms); while (roomIterator.hasNext()) roomIterator.next().value()->removeClient(this); - gameListMutex.lock(); - QMap > tempGames(games); - gameListMutex.unlock(); + QMap > tempGames(getGames()); server->roomsLock.lockForRead(); QMapIterator > gameIterator(tempGames); @@ -81,35 +77,18 @@ void Server_ProtocolHandler::prepareDestroy() continue; } - if ((authState == UnknownUser) || p->getSpectator()) - g->removePlayer(p); - else - p->setUserInterface(0); + p->disconnectClient(); g->gameMutex.unlock(); r->gamesMutex.unlock(); } server->roomsLock.unlock(); + server->removeClient(this); + deleteLater(); } -void Server_ProtocolHandler::playerRemovedFromGame(Server_Game *game) -{ - qDebug() << "Server_ProtocolHandler::playerRemovedFromGame(): gameId =" << game->getGameId(); - - QMutexLocker locker(&gameListMutex); - games.remove(game->getGameId()); -} - -void Server_ProtocolHandler::playerAddedToGame(int gameId, int roomId, int playerId) -{ - qDebug() << "Server_ProtocolHandler::playerAddedToGame(): gameId =" << gameId; - - QMutexLocker locker(&gameListMutex); - games.insert(gameId, QPair(roomId, playerId)); -} - void Server_ProtocolHandler::sendProtocolItem(const Response &item) { ServerMessage msg; @@ -217,13 +196,10 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const qDebug() << QString::fromStdString(cont.DebugString()); - gameListMutex.lock(); - if (!games.contains(cont.game_id())) { - gameListMutex.unlock(); + QMap > gameMap = getGames(); + if (!gameMap.contains(cont.game_id())) return Response::RespNotInRoom; - } - const QPair roomIdAndPlayerId = games.value(cont.game_id()); - gameListMutex.unlock(); + const QPair roomIdAndPlayerId = gameMap.value(cont.game_id()); QReadLocker roomsLocker(&server->roomsLock); Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); @@ -394,53 +370,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); } - server->roomsLock.lockForRead(); - - QMapIterator roomIterator(server->getRooms()); - while (roomIterator.hasNext()) { - Server_Room *room = roomIterator.next().value(); - room->gamesMutex.lock(); - QMapIterator gameIterator(room->getGames()); - while (gameIterator.hasNext()) { - Server_Game *game = gameIterator.next().value(); - QMutexLocker gameLocker(&game->gameMutex); - const QList &gamePlayers = game->getPlayers().values(); - for (int j = 0; j < gamePlayers.size(); ++j) - if (gamePlayers[j]->getUserInfo()->name() == userInfo->name()) { - gamePlayers[j]->setUserInterface(this); - - gameListMutex.lock(); - games.insert(game->getGameId(), QPair(room->getId(), gamePlayers[j]->getPlayerId())); - gameListMutex.unlock(); - - Event_GameJoined event1; - event1.set_room_id(room->getId()); - event1.set_game_id(game->getGameId()); - event1.set_game_description(game->getDescription().toStdString()); - event1.set_host_id(game->getHostId()); - event1.set_player_id(gamePlayers[j]->getPlayerId()); - event1.set_spectator(gamePlayers[j]->getSpectator()); - event1.set_spectators_can_talk(game->getSpectatorsCanTalk()); - event1.set_spectators_see_everything(game->getSpectatorsSeeEverything()); - event1.set_resuming(true); - rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event1)); - - Event_GameStateChanged event2; - QListIterator gameStateIterator(game->getGameState(gamePlayers[j], gamePlayers[j]->getSpectator() && game->getSpectatorsSeeEverything(), true)); - while (gameStateIterator.hasNext()) - event2.add_player_list()->CopyFrom(gameStateIterator.next()); - event2.set_seconds_elapsed(game->getSecondsElapsed()); - event2.set_game_started(game->getGameStarted()); - event2.set_active_player_id(game->getActivePlayer()); - event2.set_active_phase(game->getActivePhase()); - rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, game->prepareGameEvent(event2, -1)); - - break; - } - } - room->gamesMutex.unlock(); - } - server->roomsLock.unlock(); + joinPersistentGames(rc); rc.setResponseExtension(re); return Response::RespOk; diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index ed3a42e4..d17e06a4 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -57,14 +57,12 @@ class Command_ShutdownServer; class Server_ProtocolHandler : public QObject, public Server_AbstractUserInterface { Q_OBJECT protected: - QMap > games; // gameId -> (roomId, playerId) QMap rooms; AuthenticationResult authState; bool acceptsUserListChanges; bool acceptsRoomListChanges; private: - QMutex gameListMutex; QList messageSizeOverTime, messageCountOverTime; int timeRunning, lastDataReceived; @@ -113,8 +111,6 @@ public slots: public: Server_ProtocolHandler(Server *_server, QObject *parent = 0); ~Server_ProtocolHandler(); - void playerRemovedFromGame(Server_Game *game); - void playerAddedToGame(int gameId, int roomId, int playerId); bool getAcceptsUserListChanges() const { return acceptsUserListChanges; } bool getAcceptsRoomListChanges() const { return acceptsRoomListChanges; } diff --git a/common/server_remoteuserinterface.h b/common/server_remoteuserinterface.h index 7e09d210..d85b54a3 100644 --- a/common/server_remoteuserinterface.h +++ b/common/server_remoteuserinterface.h @@ -9,9 +9,6 @@ public: int getLastCommandTime() const { return 0; } - void playerRemovedFromGame(Server_Game * /*game*/) { } - void playerAddedToGame(int /*gameId*/, int /*roomId*/, int /*playerId*/) { } - void sendProtocolItem(const Response &item); void sendProtocolItem(const SessionEvent &item); void sendProtocolItem(const GameEventContainer &item); diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index 771c49ae..136d99c2 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -49,14 +49,6 @@ IslInterface::~IslInterface() // As these signals are connected with Qt::QueuedConnection implicitly, // we don't need to worry about them modifying the lists while we're iterating. - server->clientsLock.lockForRead(); - QMapIterator extUsers(server->getExternalUsers()); - while (extUsers.hasNext()) { - extUsers.next(); - if (extUsers.value()->getUserInfo()->server_id() == serverId) - emit externalUserLeft(extUsers.key()); - } - server->clientsLock.unlock(); server->roomsLock.lockForRead(); QMapIterator roomIterator(server->getRooms()); @@ -72,6 +64,15 @@ IslInterface::~IslInterface() room->usersLock.unlock(); } server->roomsLock.unlock(); + + server->clientsLock.lockForRead(); + QMapIterator extUsers(server->getExternalUsers()); + while (extUsers.hasNext()) { + extUsers.next(); + if (extUsers.value()->getUserInfo()->server_id() == serverId) + emit externalUserLeft(extUsers.key()); + } + server->clientsLock.unlock(); } void IslInterface::initServer()