network/room_member: Add moderation functions

To allow for passing moderation errors around without impacting the State, this commit also separates the previous State enum into two enums: State, and Error. The State enum now only contains generic states like disconnected or connected, and the Error enum describes the specific error happened.

citra_qt/multiplayer/{state, message} is changed accordingly.
This commit is contained in:
zhupengfei 2018-11-24 16:13:46 +08:00
parent 38f86cce94
commit 7acd2664dd
No known key found for this signature in database
GPG key ID: DD129E108BD09378
6 changed files with 251 additions and 56 deletions

View file

@ -36,11 +36,16 @@ const ConnectionError WRONG_PASSWORD(QT_TR_NOOP("Incorrect password."));
const ConnectionError GENERIC_ERROR(
QT_TR_NOOP("An unknown error occured. If this error continues to occur, please open an issue"));
const ConnectionError LOST_CONNECTION(QT_TR_NOOP("Connection to room lost. Try to reconnect."));
const ConnectionError HOST_KICKED(QT_TR_NOOP("You have been kicked by the room host."));
const ConnectionError MAC_COLLISION(
QT_TR_NOOP("MAC address is already in use. Please choose another."));
const ConnectionError CONSOLE_ID_COLLISION(QT_TR_NOOP(
"Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation "
"> Configure > System to regenerate your Console ID."));
const ConnectionError PERMISSION_DENIED(
QT_TR_NOOP("You do not have enough permission to perform this action."));
const ConnectionError NO_SUCH_USER(QT_TR_NOOP(
"The user you are trying to kick/ban could not be found.\nThey may have left the room."));
static bool WarnMessage(const std::string& title, const std::string& text) {
return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),

View file

@ -36,8 +36,11 @@ extern const ConnectionError WRONG_VERSION;
extern const ConnectionError WRONG_PASSWORD;
extern const ConnectionError GENERIC_ERROR;
extern const ConnectionError LOST_CONNECTION;
extern const ConnectionError HOST_KICKED;
extern const ConnectionError MAC_COLLISION;
extern const ConnectionError CONSOLE_ID_COLLISION;
extern const ConnectionError PERMISSION_DENIED;
extern const ConnectionError NO_SUCH_USER;
/**
* Shows a standard QMessageBox with a error message

View file

@ -27,9 +27,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
[this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); });
connect(this, &MultiplayerState::NetworkStateChanged, this,
&MultiplayerState::OnNetworkStateChanged);
error_callback_handle = member->BindOnError(
[this](const Network::RoomMember::Error& error) { emit NetworkError(error); });
connect(this, &MultiplayerState::NetworkError, this, &MultiplayerState::OnNetworkError);
}
qRegisterMetaType<Network::RoomMember::State>();
qRegisterMetaType<Network::RoomMember::Error>();
qRegisterMetaType<Common::WebResult>();
announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>();
announce_multiplayer_session->BindErrorCallback(
@ -52,6 +56,12 @@ MultiplayerState::~MultiplayerState() {
member->Unbind(state_callback_handle);
}
}
if (error_callback_handle) {
if (auto member = Network::GetRoomMember().lock()) {
member->Unbind(error_callback_handle);
}
}
}
void MultiplayerState::Close() {
@ -88,41 +98,8 @@ void MultiplayerState::retranslateUi() {
void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
bool is_connected = false;
switch (state) {
case Network::RoomMember::State::LostConnection:
NetworkMessage::ShowError(NetworkMessage::LOST_CONNECTION);
break;
case Network::RoomMember::State::CouldNotConnect:
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
break;
case Network::RoomMember::State::NameCollision:
NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID_SERVER);
break;
case Network::RoomMember::State::MacCollision:
NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION);
break;
case Network::RoomMember::State::ConsoleIdCollision:
NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION);
break;
case Network::RoomMember::State::RoomIsFull:
NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL);
break;
case Network::RoomMember::State::WrongPassword:
NetworkMessage::ShowError(NetworkMessage::WRONG_PASSWORD);
break;
case Network::RoomMember::State::WrongVersion:
NetworkMessage::ShowError(NetworkMessage::WRONG_VERSION);
break;
case Network::RoomMember::State::Error:
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
break;
case Network::RoomMember::State::Joined:
is_connected = true;
if (state == Network::RoomMember::State::Joined) {
OnOpenNetworkRoom();
break;
}
if (is_connected) {
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
status_text->setText(tr("Connected"));
leave_room->setEnabled(true);
@ -137,6 +114,51 @@ void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& s
current_state = state;
}
void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) {
LOG_DEBUG(Frontend, "Network Error: {}", Network::GetErrorStr(error));
switch (error) {
case Network::RoomMember::Error::LostConnection:
NetworkMessage::ShowError(NetworkMessage::LOST_CONNECTION);
break;
case Network::RoomMember::Error::HostKicked:
NetworkMessage::ShowError(NetworkMessage::HOST_KICKED);
break;
case Network::RoomMember::Error::CouldNotConnect:
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
break;
case Network::RoomMember::Error::NameCollision:
NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID_SERVER);
break;
case Network::RoomMember::Error::MacCollision:
NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION);
break;
case Network::RoomMember::Error::ConsoleIdCollision:
NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION);
break;
case Network::RoomMember::Error::RoomIsFull:
NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL);
break;
case Network::RoomMember::Error::WrongPassword:
NetworkMessage::ShowError(NetworkMessage::WRONG_PASSWORD);
break;
case Network::RoomMember::Error::WrongVersion:
NetworkMessage::ShowError(NetworkMessage::WRONG_VERSION);
break;
case Network::RoomMember::Error::HostBanned:
NetworkMessage::ShowError(NetworkMessage::HOST_BANNED);
break;
case Network::RoomMember::Error::UnknownError:
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
break;
case Network::RoomMember::Error::PermissionDenied:
NetworkMessage::ShowError(NetworkMessage::PERMISSION_DENIED);
break;
case Network::RoomMember::Error::NoSuchUser:
NetworkMessage::ShowError(NetworkMessage::NO_SUCH_USER);
break;
}
}
void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) {
announce_multiplayer_session->Stop();
QMessageBox::warning(

View file

@ -40,6 +40,7 @@ public:
public slots:
void OnNetworkStateChanged(const Network::RoomMember::State& state);
void OnNetworkError(const Network::RoomMember::Error& error);
void OnViewLobby();
void OnCreateRoom();
bool OnCloseRoom();
@ -50,6 +51,7 @@ public slots:
signals:
void NetworkStateChanged(const Network::RoomMember::State&);
void NetworkError(const Network::RoomMember::Error&);
void AnnounceFailed(const Common::WebResult&);
private:
@ -65,6 +67,7 @@ private:
std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
};
Q_DECLARE_METATYPE(Common::WebResult);

View file

@ -31,6 +31,7 @@ public:
std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
void SetState(const State new_state);
void SetError(const Error new_error);
bool IsConnected() const;
std::string nickname; ///< The nickname of this member.
@ -61,6 +62,8 @@ public:
CallbackSet<StatusMessageEntry> callback_set_status_messages;
CallbackSet<RoomInformation> callback_set_room_information;
CallbackSet<State> callback_set_state;
CallbackSet<Error> callback_set_error;
CallbackSet<Room::BanList> callback_set_ban_list;
};
Callbacks callbacks; ///< All CallbackSets to all events
@ -117,6 +120,12 @@ public:
*/
void HandleStatusMessagePacket(const ENetEvent* event);
/**
* Extracts a ban list request response from a received ENet packet.
* @param event The ENet event that was received.
*/
void HandleModBanListResponsePacket(const ENetEvent* event);
/**
* Disconnects the RoomMember from the Room
*/
@ -137,6 +146,10 @@ void RoomMember::RoomMemberImpl::SetState(const State new_state) {
}
}
void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
Invoke<Error>(new_error);
}
bool RoomMember::RoomMemberImpl::IsConnected() const {
return state == State::Joining || state == State::Joined;
}
@ -170,32 +183,59 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
HandleJoinPacket(&event); // Get the MAC Address for the client
SetState(State::Joined);
break;
case IdModBanListResponse:
HandleModBanListResponsePacket(&event);
break;
case IdRoomIsFull:
SetState(State::RoomIsFull);
SetState(State::Idle);
SetError(Error::RoomIsFull);
break;
case IdNameCollision:
SetState(State::NameCollision);
SetState(State::Idle);
SetError(Error::NameCollision);
break;
case IdMacCollision:
SetState(State::MacCollision);
SetState(State::Idle);
SetError(Error::MacCollision);
break;
case IdConsoleIdCollision:
SetState(State::ConsoleIdCollision);
SetState(State::Idle);
SetError(Error::ConsoleIdCollision);
break;
case IdVersionMismatch:
SetState(State::WrongVersion);
SetState(State::Idle);
SetError(Error::WrongVersion);
break;
case IdWrongPassword:
SetState(State::WrongPassword);
SetState(State::Idle);
SetError(Error::WrongPassword);
break;
case IdCloseRoom:
SetState(State::LostConnection);
SetState(State::Idle);
SetError(Error::LostConnection);
break;
case IdHostKicked:
SetState(State::Idle);
SetError(Error::HostKicked);
break;
case IdHostBanned:
SetState(State::Idle);
SetError(Error::HostBanned);
break;
case IdModPermissionDenied:
SetError(Error::PermissionDenied);
break;
case IdModNoSuchUser:
SetError(Error::NoSuchUser);
break;
}
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
SetState(State::LostConnection);
if (state == State::Joined) {
SetState(State::Idle);
SetError(Error::LostConnection);
}
break;
}
}
@ -251,11 +291,13 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
packet >> info.member_slots;
packet >> info.port;
packet >> info.preferred_game;
packet >> info.host_username;
room_information.name = info.name;
room_information.description = info.description;
room_information.member_slots = info.member_slots;
room_information.port = info.port;
room_information.preferred_game = info.preferred_game;
room_information.host_username = info.host_username;
u32 num_members;
packet >> num_members;
@ -344,6 +386,19 @@ void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* even
Invoke<StatusMessageEntry>(status_message_entry);
}
void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
Packet packet;
packet.Append(event->packet->data, event->packet->dataLength);
// Ignore the first byte, which is the message id.
packet.IgnoreBytes(sizeof(u8));
Room::BanList ban_list = {};
packet >> ban_list.first;
packet >> ban_list.second;
Invoke<Room::BanList>(ban_list);
}
void RoomMember::RoomMemberImpl::Disconnect() {
member_information.clear();
room_information.member_slots = 0;
@ -383,6 +438,12 @@ RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_state;
}
template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_error;
}
template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
@ -400,6 +461,12 @@ RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_status_messages;
}
template <>
RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_ban_list;
}
template <typename T>
void RoomMember::RoomMemberImpl::Invoke(const T& data) {
std::lock_guard<std::mutex> lock(callback_mutex);
@ -481,7 +548,8 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has
enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
if (!room_member_impl->server) {
room_member_impl->SetState(State::Error);
room_member_impl->SetState(State::Idle);
room_member_impl->SetError(Error::UnknownError);
return;
}
@ -494,7 +562,8 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has
SendGameInfo(room_member_impl->current_game_info);
} else {
enet_peer_disconnect(room_member_impl->server, 0);
room_member_impl->SetState(State::CouldNotConnect);
room_member_impl->SetState(State::Idle);
room_member_impl->SetError(Error::CouldNotConnect);
}
}
@ -532,11 +601,37 @@ void RoomMember::SendGameInfo(const GameInfo& game_info) {
room_member_impl->Send(std::move(packet));
}
void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
"type is not a moderation request");
if (!IsConnected())
return;
Packet packet;
packet << static_cast<u8>(type);
packet << nickname;
room_member_impl->Send(std::move(packet));
}
void RoomMember::RequestBanList() {
if (!IsConnected())
return;
Packet packet;
packet << static_cast<u8>(IdModGetBanList);
room_member_impl->Send(std::move(packet));
}
RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
std::function<void(const RoomMember::State&)> callback) {
return room_member_impl->Bind(callback);
}
RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
std::function<void(const RoomMember::Error&)> callback) {
return room_member_impl->Bind(callback);
}
RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
std::function<void(const WifiPacket&)> callback) {
return room_member_impl->Bind(callback);
@ -557,6 +652,11 @@ RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageRe
return room_member_impl->Bind(callback);
}
RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
std::function<void(const Room::BanList&)> callback) {
return room_member_impl->Bind(callback);
}
template <typename T>
void RoomMember::Unbind(CallbackHandle<T> handle) {
std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex);
@ -574,8 +674,10 @@ void RoomMember::Leave() {
template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
template void RoomMember::Unbind(CallbackHandle<Room::BanList>);
} // namespace Network

View file

@ -57,20 +57,30 @@ class RoomMember final {
public:
enum class State : u8 {
Uninitialized, ///< Not initialized
Idle, ///< Default state
Error, ///< Some error [permissions to network device missing or something]
Idle, ///< Default state (i.e. not connected)
Joining, ///< The client is attempting to join a room.
Joined, ///< The client is connected to the room and is ready to send/receive packets.
};
enum class Error : u8 {
// Reasons why connection was closed
LostConnection, ///< Connection closed
HostKicked, ///< Kicked by the host
// Reasons why connection was rejected
UnknownError, ///< Some error [permissions to network device missing or something]
NameCollision, ///< Somebody is already using this name
MacCollision, ///< Somebody is already using that mac-address
ConsoleIdCollision, ///< Somebody in the room has the same Console ID
WrongVersion, ///< The room version is not the same as for this RoomMember
WrongPassword, ///< The password doesn't match the one from the Room
CouldNotConnect, ///< The room is not responding to a connection attempt
RoomIsFull ///< Room is already at the maximum number of players
RoomIsFull, ///< Room is already at the maximum number of players
HostBanned, ///< The user is banned by the host
// Reasons why moderation request failed
PermissionDenied, ///< The user does not have mod permissions
NoSuchUser, ///< The nickname the user attempts to kick/ban does not exist
};
struct MemberInformation {
@ -161,6 +171,19 @@ public:
*/
void SendGameInfo(const GameInfo& game_info);
/**
* Sends a moderation request to the room.
* @param type Moderation request type.
* @param nickname The subject of the request. (i.e. the user you want to kick/ban)
*/
void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);
/**
* Attempts to retrieve ban list from the room.
* If success, the ban list callback would be called. Otherwise an error would be emitted.
*/
void RequestBanList();
/**
* Binds a function to an event that will be triggered every time the State of the member
* changed. The function wil be called every time the event is triggered. The callback function
@ -170,6 +193,15 @@ public:
*/
CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
/**
* Binds a function to an event that will be triggered every time an error happened. The
* function wil be called every time the event is triggered. The callback function must not bind
* or unbind a function. Doing so will cause a deadlock
* @param callback The function to call
* @return A handle used for removing the function from the registered list
*/
CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
/**
* Binds a function to an event that will be triggered every time a WifiPacket is received.
* The function wil be called everytime the event is triggered.
@ -210,6 +242,16 @@ public:
CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
std::function<void(const StatusMessageEntry&)> callback);
/**
* Binds a function to an event that will be triggered every time a requested ban list
* received. The function will be called every time the event is triggered. The callback
* function must not bind or unbind a function. Doing so will cause a deadlock
* @param callback The function to call
* @return A handle used for removing the function from the registered list
*/
CallbackHandle<Room::BanList> BindOnBanListReceived(
std::function<void(const Room::BanList&)> callback);
/**
* Leaves the current room.
*/
@ -224,24 +266,42 @@ static const char* GetStateStr(const RoomMember::State& s) {
switch (s) {
case RoomMember::State::Idle:
return "Idle";
case RoomMember::State::Error:
return "Error";
case RoomMember::State::Joining:
return "Joining";
case RoomMember::State::Joined:
return "Joined";
case RoomMember::State::LostConnection:
}
return "Unknown";
}
static const char* GetErrorStr(const RoomMember::Error& e) {
switch (e) {
case RoomMember::Error::LostConnection:
return "LostConnection";
case RoomMember::State::NameCollision:
case RoomMember::Error::HostKicked:
return "HostKicked";
case RoomMember::Error::UnknownError:
return "UnknownError";
case RoomMember::Error::NameCollision:
return "NameCollision";
case RoomMember::State::MacCollision:
return "MacCollision";
case RoomMember::State::WrongVersion:
case RoomMember::Error::MacCollision:
return "MaxCollision";
case RoomMember::Error::ConsoleIdCollision:
return "ConsoleIdCollision";
case RoomMember::Error::WrongVersion:
return "WrongVersion";
case RoomMember::State::WrongPassword:
case RoomMember::Error::WrongPassword:
return "WrongPassword";
case RoomMember::State::CouldNotConnect:
case RoomMember::Error::CouldNotConnect:
return "CouldNotConnect";
case RoomMember::Error::RoomIsFull:
return "RoomIsFull";
case RoomMember::Error::HostBanned:
return "HostBanned";
case RoomMember::Error::PermissionDenied:
return "PermissionDenied";
case RoomMember::Error::NoSuchUser:
return "NoSuchUser";
}
return "Unknown";
}