From a68530cbbfeb17e01fd684b1ef41b960bc173f66 Mon Sep 17 00:00:00 2001 From: Kevin Smith <git@kismith.co.uk> Date: Sat, 22 May 2010 20:45:53 +0000 Subject: Implement XEP-0045 joining, and appropriate error handling. Resolves: #211 diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index a8a6747..dbf03c9 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -45,7 +45,6 @@ MUCController::MUCController ( ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc, presenceOracle, avatarManager, useDelayForLatency, uiEventStream), muc_(new MUC(stanzaChannel, presenceSender, muc)), nick_(nick) { - loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); parting_ = false; events_ = uiEventStream; @@ -53,11 +52,16 @@ MUCController::MUCController ( chatWindow_->setRosterModel(roster_); chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); + muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); - loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); - loginCheckTimer_->start(); + muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); + if (timerFactory) { + loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); + loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); + loginCheckTimer_->start(); + } muc_->joinAs(nick); chatWindow_->convertToMUC(); @@ -72,21 +76,51 @@ MUCController::~MUCController() { delete muc_; chatWindow_->setRosterModel(NULL); delete roster_; - loginCheckTimer_->stop(); + if (loginCheckTimer_) { + loginCheckTimer_->stop(); + } } void MUCController::handleJoinTimeoutTick() { - loginCheckTimer_->stop(); + receivedActivity(); chatWindow_->addSystemMessage("Room " + toJID_.toString() + " is not responding. This operation may never complete"); } -void MUCController::handleJoinComplete(MUC::JoinResult result) { - loginCheckTimer_->stop(); - if (result == MUC::JoinFailed) { - chatWindow_->addErrorMessage("Unable to join this room"); - } +void MUCController::receivedActivity() { + if (loginCheckTimer_) { + loginCheckTimer_->stop(); + } +} + +void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { + receivedActivity(); + String errorMessage = "Unable to join this room"; + String rejoinNick; + if (error) { + switch (error->getCondition()) { + case ErrorPayload::Conflict: rejoinNick = nick_ + "_"; errorMessage += " as " + nick_ + ", retrying as " + rejoinNick; break; + case ErrorPayload::JIDMalformed: errorMessage += ", no nickname specified";break; + case ErrorPayload::NotAuthorized: errorMessage += ", a password needed";break; + case ErrorPayload::RegistrationRequired: errorMessage += ", only members may join"; break; + case ErrorPayload::Forbidden: errorMessage += ", you are banned from the room"; break; + case ErrorPayload::ServiceUnavailable: errorMessage += ", the room is full";break; + case ErrorPayload::ItemNotFound: errorMessage += ", the room does not exist";break; + + default: break; + } + } + errorMessage += "."; + chatWindow_->addErrorMessage(errorMessage); + if (!rejoinNick.isEmpty()) { + muc_->joinAs(rejoinNick); + } +} + +void MUCController::handleJoinComplete(const String& nick) { + receivedActivity(); joined_ = true; - String joinMessage = "You have joined room " + toJID_.toString() + " as " + nick_; + String joinMessage = "You have joined room " + toJID_.toString() + " as " + nick; + nick_ = nick; chatWindow_->addSystemMessage(joinMessage); } @@ -105,18 +139,76 @@ void MUCController::handleWindowClosed() { } void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { - JID jid(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick())); - roster_->addContact(jid, occupant.getNick(), "Occupants"); + receivedActivity(); + JID jid(nickToJID(occupant.getNick())); + roster_->addContact(jid, occupant.getNick(), roleToGroupName(occupant.getRole())); + if (joined_) { + String joinString = occupant.getNick() + " has joined the room"; + MUCOccupant::Role role = occupant.getRole(); + if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) { + joinString += " as a " + roleToFriendlyName(role); + + } + joinString += "."; + chatWindow_->addSystemMessage(joinString); + } if (avatarManager_ != NULL) { handleAvatarChanged(jid, "dummy"); } } -void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType, const String& /*reason*/) { +String MUCController::roleToFriendlyName(MUCOccupant::Role role) { + switch (role) { + case MUCOccupant::Moderator: return "moderator"; + case MUCOccupant::Participant: return "participant"; + case MUCOccupant::Visitor: return "visitor"; + case MUCOccupant::NoRole: return ""; + } + return ""; +} + +JID MUCController::nickToJID(const String& nick) { + return JID(toJID_.getNode(), toJID_.getDomain(), nick); +} + +void MUCController::preHandleIncomingMessage(boost::shared_ptr<Message>) { + /*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/ + receivedActivity(); + joined_ = true; +} + +void MUCController::handleOccupantRoleChanged(const String& nick, const MUCOccupant::Role& newRole, const MUCOccupant::Role& oldRole) { + receivedActivity(); + JID jid(nickToJID(nick)); + roster_->removeContactFromGroup(jid, roleToGroupName(oldRole)); + roster_->addContact(jid, nick, roleToGroupName(newRole)); + chatWindow_->addSystemMessage(nick + " is now a " + roleToFriendlyName(newRole)); +} + +String MUCController::roleToGroupName(MUCOccupant::Role role) { + String result; + switch (role) { + case MUCOccupant::Moderator: result = "Moderators"; break; + case MUCOccupant::Participant: result = "Participants"; break; + case MUCOccupant::Visitor: result = "Visitors"; break; + case MUCOccupant::NoRole: result = "Occupants"; break; + default: assert(false); + } + return result; +} + +void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType, const String& reason) { + String partMessage = occupant.getNick() + " has left the room"; + if (!reason.isEmpty()) { + partMessage += " (" + reason + ")"; + } + partMessage += "."; + chatWindow_->addSystemMessage(partMessage); roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick())); } void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) { + receivedActivity(); roster_->applyOnItems(SetPresence(presence, JID::WithResource)); } diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 9e79835..247a942 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -4,8 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_MUCController_H -#define SWIFTEN_MUCController_H +#pragma once #include <boost/shared_ptr.hpp> #include <boost/signals.hpp> @@ -47,9 +46,15 @@ namespace Swift { void handleOccupantJoined(const MUCOccupant& occupant); void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const String& reason); void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence); - void handleJoinComplete(MUC::JoinResult result); + void handleOccupantRoleChanged(const String& nick, const MUCOccupant::Role& newRole, const MUCOccupant::Role& oldRole); + void handleJoinComplete(const String& nick); + void handleJoinFailed(boost::shared_ptr<ErrorPayload> error); void handleJoinTimeoutTick(); - + String roleToGroupName(MUCOccupant::Role role); + JID nickToJID(const String& nick); + String roleToFriendlyName(MUCOccupant::Role role); + void receivedActivity(); + void preHandleIncomingMessage(boost::shared_ptr<Message>); private: MUC* muc_; UIEventStream* events_; @@ -61,5 +66,4 @@ namespace Swift { boost::shared_ptr<Timer> loginCheckTimer_; }; } -#endif diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 99b2f90..2204366 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -64,7 +64,7 @@ public: uiEventStream_ = new UIEventStream(); chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createWindow).With(uiEventStream_).Return(NULL); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, true); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL); avatarManager_ = new MockAvatarManager(); manager_->setAvatarManager(avatarManager_); }; diff --git a/Swiften/Elements/MUCPayload.h b/Swiften/Elements/MUCPayload.h index 5ce682e..4b48b45 100644 --- a/Swiften/Elements/MUCPayload.h +++ b/Swiften/Elements/MUCPayload.h @@ -1,17 +1,19 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once +#include <boost/optional.hpp> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" #include "Swiften/Elements/Payload.h" namespace Swift { class MUCPayload : public Payload { - public: - MUCPayload() { - } + }; } diff --git a/Swiften/Elements/MUCUserPayload.h b/Swiften/Elements/MUCUserPayload.h new file mode 100644 index 0000000..3032b9f --- /dev/null +++ b/Swiften/Elements/MUCUserPayload.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/MUC/MUCOccupant.h" + +namespace Swift { + class MUCUserPayload : public Payload { + public: + struct Item { + Item() : affiliation(MUCOccupant::NoAffiliation), role(MUCOccupant::NoRole) {} + boost::optional<JID> realJID; + boost::optional<String> nick; + MUCOccupant::Affiliation affiliation; + MUCOccupant::Role role; + }; + + struct StatusCode { + StatusCode() {} + int code; + }; + + // struct Password { + + // } + + // struct History { + + // } + + // struct Invite { + + // } + + MUCUserPayload() { + } + + void addItem(Item item) {items_.push_back(item);} + + void addStatusCode(StatusCode code) {statusCodes_.push_back(code);} + + const std::vector<Item> getItems() const {return items_;} + + const std::vector<StatusCode> getStatusCodes() const {return statusCodes_;} + + private: + std::vector<Item> items_; + std::vector<StatusCode> statusCodes_; + }; +} diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp index e52eae4..60b81a0 100644 --- a/Swiften/MUC/MUC.cpp +++ b/Swiften/MUC/MUC.cpp @@ -1,17 +1,20 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include "Swiften/MUC/MUC.h" +#include <iostream> + #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include "Swiften/Presence/PresenceSender.h" #include "Swiften/Client/StanzaChannel.h" #include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/MUCUserPayload.h" #include "Swiften/Elements/MUCPayload.h" namespace Swift { @@ -25,9 +28,9 @@ MUC::MUC(StanzaChannel* stanzaChannel, PresenceSender* presenceSender, const JID //FIXME: discover reserved nickname void MUC::joinAs(const String &nick) { - //FIXME: password - //FIXME: history request - firstPresenceSeen = false; + //TODO: password + //TODO: history request + joinComplete_ = false; ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick); @@ -45,53 +48,73 @@ void MUC::handleIncomingPresence(boost::shared_ptr<Presence> presence) { if (!isFromMUC(presence->getFrom())) { return; } - - if (!firstPresenceSeen) { + boost::shared_ptr<MUCUserPayload> mucPayload; + foreach (boost::shared_ptr<MUCUserPayload> payload, presence->getPayloads<MUCUserPayload>()) { + if (payload->getItems().size() > 0 || payload->getStatusCodes().size() > 0) { + mucPayload = payload; + } + } + + if (!joinComplete_) { if (presence->getType() == Presence::Error) { - onJoinComplete(JoinFailed); - //FIXME: parse error element - //Wrong password - //Members-only - //Banned - //Nickname-conflict - //Max-users - //Locked-room - + String reason; + onJoinFailed(presence->getPayload<ErrorPayload>()); return; } - firstPresenceSeen = true; - onJoinComplete(JoinSucceeded); - presenceSender->addDirectedPresenceReceiver(ownMUCJID); } String nick = presence->getFrom().getResource(); if (nick.isEmpty()) { return; } - //FIXME: occupant affiliation, role. - //FIXME: if status code='110', This is me, stop talking. - //FIXME: what's status 210? + MUCOccupant::Role role(MUCOccupant::NoRole); + MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation); + if (mucPayload && mucPayload->getItems().size() > 0) { + role = mucPayload->getItems()[0].role; + affiliation = mucPayload->getItems()[0].affiliation; + } + //100 is non-anonymous - //FIXME: 100 may also be specified in a <message/> - //Once I've got my nick (110), everything new is a join + //TODO: 100 may also be specified in a <message/> //170 is room logging to http - //FIXME: full JIDs - //FIXME: Nick changes + //TODO: Nick changes if (presence->getType() == Presence::Unavailable) { std::map<String,MUCOccupant>::iterator i = occupants.find(nick); if (i != occupants.end()) { - //FIXME: part type + //TODO: part type onOccupantLeft(i->second, Part, ""); occupants.erase(i); } } else if (presence->getType() == Presence::Available) { - std::pair<std::map<String,MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, MUCOccupant(nick))); + std::map<String, MUCOccupant>::iterator it = occupants.find(nick); + if (it != occupants.end()) { + MUCOccupant oldOccupant = it->second; + if (oldOccupant.getRole() != role) { + onOccupantRoleChanged(nick, role, oldOccupant.getRole()); + } + if (oldOccupant.getAffiliation() != affiliation) { + onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation()); + } + } + std::pair<std::map<String, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, MUCOccupant(nick, role, affiliation))); if (result.second) { onOccupantJoined(result.first->second); } onOccupantPresenceChange(presence); } + if (mucPayload && !joinComplete_) { + foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) { + if (status.code == 110) { + /* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */ + joinComplete_ = true; + ownMUCJID = presence->getFrom(); + onJoinComplete(getOwnNick()); + presenceSender->addDirectedPresenceReceiver(ownMUCJID); + } + } + } + } //FIXME: Recognise Topic changes diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h index e306f11..fc691f1 100644 --- a/Swiften/MUC/MUC.h +++ b/Swiften/MUC/MUC.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -25,10 +25,7 @@ namespace Swift { class MUC { public: enum JoinResult { JoinSucceeded, JoinFailed }; - enum LeavingType { Part }; - enum Roles {Moderator, Participant, Visitor, NoRole}; - enum Affiliations {Owner, Admin, Member, Outcast, NoAffiliation}; - + enum LeavingType { Part }; public: MUC(StanzaChannel* stanzaChannel, PresenceSender* presenceSender, const JID &muc); @@ -41,8 +38,11 @@ namespace Swift { void handleIncomingMessage(boost::shared_ptr<Message> message); public: - boost::signal<void (JoinResult)> onJoinComplete; + boost::signal<void (const String& /*nick*/)> onJoinComplete; + boost::signal<void (boost::shared_ptr<ErrorPayload>)> onJoinFailed; boost::signal<void (boost::shared_ptr<Presence>)> onOccupantPresenceChange; + boost::signal<void (const String&, const MUCOccupant::Role& /*new*/, const MUCOccupant::Role& /*old*/)> onOccupantRoleChanged; + boost::signal<void (const String&, const MUCOccupant::Affiliation& /*new*/, const MUCOccupant::Affiliation& /*old*/)> onOccupantAffiliationChanged; boost::signal<void (const MUCOccupant&)> onOccupantJoined; boost::signal<void (const MUCOccupant&, LeavingType, const String& /*reason*/)> onOccupantLeft; /* boost::signal<void (const MUCInfo&)> onInfoResult; */ @@ -66,7 +66,7 @@ namespace Swift { StanzaChannel* stanzaChannel; PresenceSender* presenceSender; std::map<String, MUCOccupant> occupants; - bool firstPresenceSeen; + bool joinComplete_; boost::bsignals::scoped_connection scopedConnection_; }; } diff --git a/Swiften/MUC/MUCOccupant.cpp b/Swiften/MUC/MUCOccupant.cpp index 0bd9787..3e907ab 100644 --- a/Swiften/MUC/MUCOccupant.cpp +++ b/Swiften/MUC/MUCOccupant.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -8,7 +8,7 @@ namespace Swift { -MUCOccupant::MUCOccupant(const String &nick) : nick_(nick) { +MUCOccupant::MUCOccupant(const String &nick, Role role, Affiliation affiliation) : nick_(nick), role_(role), affiliation_(affiliation) { } MUCOccupant::~MUCOccupant() { @@ -18,4 +18,25 @@ String MUCOccupant::getNick() const { return nick_; } +MUCOccupant::Role MUCOccupant::getRole() const { + return role_; +} + +MUCOccupant::Affiliation MUCOccupant::getAffiliation() const { + return affiliation_; +} + +void MUCOccupant::setRealJID(const JID& realJID) { + realJID_ = realJID; +} + +void MUCOccupant::setNick(const String& nick) { + nick_ = nick; +} + + +boost::optional<JID> MUCOccupant::getRealJID() const { + return realJID_; +} + } diff --git a/Swiften/MUC/MUCOccupant.h b/Swiften/MUC/MUCOccupant.h index 939d634..c9551de 100644 --- a/Swiften/MUC/MUCOccupant.h +++ b/Swiften/MUC/MUCOccupant.h @@ -1,27 +1,39 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_MUCOccupant_H -#define SWIFTEN_MUCOccupant_H +#pragma once + +#include <boost/optional.hpp> #include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" namespace Swift { class Client; class MUCOccupant { public: - MUCOccupant(const String &nick); + enum Role {Moderator, Participant, Visitor, NoRole}; + enum Affiliation {Owner, Admin, Member, Outcast, NoAffiliation}; + + MUCOccupant(const String &nick, Role role, Affiliation affiliation); ~MUCOccupant(); String getNick() const; + Role getRole() const; + Affiliation getAffiliation() const; + boost::optional<JID> getRealJID() const; + void setRealJID(const JID& jid); + void setNick(const String& nick); private: String nick_; + Role role_; + Affiliation affiliation_; + boost::optional<JID> realJID_; }; } -#endif diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index 8c570cb..1a45ed2 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -28,6 +28,7 @@ #include "Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h" #include "Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h" #include "Swiften/Parser/PayloadParsers/DelayParserFactory.h" +#include "Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h" using namespace boost; @@ -53,6 +54,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this))); factories_.push_back(shared_ptr<PayloadParserFactory>(new ChatStateParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new DelayParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new MUCUserPayloadParserFactory())); foreach(shared_ptr<PayloadParserFactory> factory, factories_) { addFactory(factory.get()); } diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp new file mode 100644 index 0000000..5bb0e68 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h" + +#include <boost/lexical_cast.hpp> + +#include "Swiften/MUC/MUCOccupant.h" + +#include <cassert> +#include <iostream> + +namespace Swift { + +MUCUserPayloadParser::MUCUserPayloadParser() : level(TopLevel) { +} + +void MUCUserPayloadParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level == ItemLevel) { + if (element == "item") { + MUCUserPayload::Item item; + String affiliation = attributes.getAttribute("affiliation"); + String role = attributes.getAttribute("role"); + String nick = attributes.getAttribute("nick"); + String jid = attributes.getAttribute("jid"); + item.affiliation = parseAffiliation(affiliation); + item.role = parseRole(role); + if (!jid.isEmpty()) { + item.realJID = JID(jid); + } + if (!nick.isEmpty()) { + item.nick = nick; + } + getPayloadInternal()->addItem(item); + } else if (element == "status") { + MUCUserPayload::StatusCode status; + try { + status.code = boost::lexical_cast<int>(attributes.getAttribute("code").getUTF8Data()); + getPayloadInternal()->addStatusCode(status); + } catch (boost::bad_lexical_cast&) { + } + } + } + ++level; +} + +MUCOccupant::Role MUCUserPayloadParser::parseRole(const String& roleString) const { + if (roleString == "moderator") { + return MUCOccupant::Moderator; + } + if (roleString == "participant") { + return MUCOccupant::Participant; + } + if (roleString == "visitor") { + return MUCOccupant::Visitor; + } + if (roleString == "none") { + return MUCOccupant::NoRole; + } + return MUCOccupant::NoRole; +} + +MUCOccupant::Affiliation MUCUserPayloadParser::parseAffiliation(const String& affiliationString) const { + if (affiliationString == "owner") { + return MUCOccupant::Owner; + } + if (affiliationString == "admin") { + return MUCOccupant::Admin; + } + if (affiliationString == "member") { + return MUCOccupant::Member; + } + if (affiliationString == "outcast") { + return MUCOccupant::Outcast; + } + if (affiliationString == "none") { + return MUCOccupant::NoAffiliation; + } + return MUCOccupant::NoAffiliation; +} + + +void MUCUserPayloadParser::handleEndElement(const String& /*element*/, const String&) { + --level; +} + +void MUCUserPayloadParser::handleCharacterData(const String& /*data*/) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h new file mode 100644 index 0000000..01c7de1 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/MUCUserPayload.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class MUCUserPayloadParser : public GenericPayloadParser<MUCUserPayload> { + public: + MUCUserPayloadParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + MUCOccupant::Role parseRole(const String& itemString) const; + MUCOccupant::Affiliation parseAffiliation(const String& statusString) const; + private: + enum Level { + TopLevel = 0, + ItemLevel = 1 + }; + int level; + }; +} diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h b/Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h new file mode 100644 index 0000000..3946ece --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h" + +namespace Swift { + class MUCUserPayloadParserFactory : public GenericPayloadParserFactory<MUCUserPayloadParser> { + public: + MUCUserPayloadParserFactory() : GenericPayloadParserFactory<MUCUserPayloadParser>("x", "http://jabber.org/protocol/muc#user") {} + }; +} diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 77f50be..430d827 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -37,6 +37,7 @@ sources = [ "PayloadParsers/VCardParser.cpp", "PayloadParsers/VCardUpdateParser.cpp", "PayloadParsers/DelayParser.cpp", + "PayloadParsers/MUCUserPayloadParser.cpp", "PlatformXMLParserFactory.cpp", "PresenceParser.cpp", "SerializingParser.cpp", diff --git a/Swiften/SConscript b/Swiften/SConscript index 4d25dc5..f8bcc8c 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -74,6 +74,7 @@ if env["SCONS_STAGE"] == "build" : "Serializer/PayloadSerializers/ErrorSerializer.cpp", "Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp", "Serializer/PayloadSerializers/MUCPayloadSerializer.cpp", + "Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp", "Serializer/PayloadSerializers/ResourceBindSerializer.cpp", "Serializer/PayloadSerializers/RosterSerializer.cpp", "Serializer/PayloadSerializers/SecurityLabelSerializer.cpp", diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index caf1d9c..e407876 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -13,6 +13,7 @@ #include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" #include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" #include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h" #include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" #include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h" #include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h" diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp index 9630014..36aa47f 100644 --- a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp @@ -1,19 +1,21 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + namespace Swift { MUCPayloadSerializer::MUCPayloadSerializer() : GenericPayloadSerializer<MUCPayload>() { } -String MUCPayloadSerializer::serializePayload(boost::shared_ptr<MUCPayload>) const { - String result("<x xmlns='http://jabber.org/protocol/muc'/>"); - return result; +String MUCPayloadSerializer::serializePayload(boost::shared_ptr<MUCPayload> payload) const { + XMLElement mucElement("x", "http://jabber.org/protocol/muc"); + return mucElement.serialize(); } } diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h index b015980..cd7f107 100644 --- a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h +++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h @@ -1,11 +1,10 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_MUCPayloadSerializer_H -#define SWIFTEN_MUCPayloadSerializer_H +#pragma once #include "Swiften/Serializer/GenericPayloadSerializer.h" #include "Swiften/Elements/MUCPayload.h" @@ -14,9 +13,7 @@ namespace Swift { class MUCPayloadSerializer : public GenericPayloadSerializer<MUCPayload> { public: MUCPayloadSerializer(); - virtual String serializePayload(boost::shared_ptr<MUCPayload> version) const; }; } -#endif diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp new file mode 100644 index 0000000..f4732ea --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h" + +#include <sstream> + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + + +namespace Swift { + +MUCUserPayloadSerializer::MUCUserPayloadSerializer() : GenericPayloadSerializer<MUCUserPayload>() { +} + +String MUCUserPayloadSerializer::serializePayload(boost::shared_ptr<MUCUserPayload> payload) const { + XMLElement mucElement("x", "http://jabber.org/protocol/muc"); + foreach (const MUCUserPayload::StatusCode statusCode, payload->getStatusCodes()) { + boost::shared_ptr<XMLElement> statusElement(new XMLElement("status")); + std::ostringstream code; + code << statusCode.code; + statusElement->setAttribute("code", code.str()); + mucElement.addNode(statusElement); + } + foreach (const MUCUserPayload::Item item, payload->getItems()) { + boost::shared_ptr<XMLElement> itemElement(new XMLElement("item")); + itemElement->setAttribute("affiliation", affiliationToString(item.affiliation)); + itemElement->setAttribute("role", roleToString(item.role)); + if (item.realJID) { + itemElement->setAttribute("jid", item.realJID.get()); + } + if (item.nick) { + itemElement->setAttribute("nick", item.nick.get()); + } + mucElement.addNode(itemElement); + } + return mucElement.serialize(); +} + +String MUCUserPayloadSerializer::affiliationToString(MUCOccupant::Affiliation affiliation) const { + String result; + switch (affiliation) { + case MUCOccupant::Owner: result = "owner"; break; + case MUCOccupant::Admin: result = "admin"; break; + case MUCOccupant::Member: result = "member"; break; + case MUCOccupant::Outcast: result = "outcast"; break; + case MUCOccupant::NoAffiliation: result = "none"; break; + default: assert(false); + } + return result; +} + +String MUCUserPayloadSerializer::roleToString(MUCOccupant::Role role) const { + String result; + switch (role) { + case MUCOccupant::Moderator: result = "moderator"; break; + case MUCOccupant::NoRole: result = "none"; break; + case MUCOccupant::Participant: result = "participant"; break; + case MUCOccupant::Visitor: result = "visitor"; break; + default: assert(false); + } + return result; + +} + + +} diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h new file mode 100644 index 0000000..bad21c9 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/MUCUserPayload.h" + +namespace Swift { + class MUCUserPayloadSerializer : public GenericPayloadSerializer<MUCUserPayload> { + public: + MUCUserPayloadSerializer(); + String affiliationToString(MUCOccupant::Affiliation affiliation) const; + String roleToString(MUCOccupant::Role role) const; + + virtual String serializePayload(boost::shared_ptr<MUCUserPayload> version) const; + }; +} + -- cgit v0.10.2-6-g49f6