diff options
-rw-r--r-- | Swift/Controllers/Chat/MUCController.cpp | 6 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp | 72 | ||||
-rw-r--r-- | Swift/Controllers/Roster/ContactRosterItem.cpp | 40 | ||||
-rw-r--r-- | Swift/Controllers/Roster/ContactRosterItem.h | 11 | ||||
-rw-r--r-- | Swift/Controllers/Roster/ItemOperations/SetMUC.h | 39 | ||||
-rw-r--r-- | Swift/Controllers/UnitTest/MockChatWindow.h | 4 | ||||
-rw-r--r-- | Swift/QtUI/Roster/RosterTooltip.cpp | 9 | ||||
-rw-r--r-- | Swiften/MUC/MUC.cpp | 420 | ||||
-rw-r--r-- | Swiften/MUC/MUC.h | 95 | ||||
-rw-r--r-- | Swiften/MUC/MUCImpl.cpp | 434 | ||||
-rw-r--r-- | Swiften/MUC/MUCImpl.h | 117 | ||||
-rw-r--r-- | Swiften/MUC/MUCManager.cpp | 3 | ||||
-rw-r--r-- | Swiften/MUC/UnitTest/MUCTest.cpp | 4 | ||||
-rw-r--r-- | Swiften/MUC/UnitTest/MockMUC.cpp | 51 | ||||
-rw-r--r-- | Swiften/MUC/UnitTest/MockMUC.h | 95 | ||||
-rw-r--r-- | Swiften/SConscript | 2 |
16 files changed, 907 insertions, 495 deletions
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index afcb782..14d1767 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -32,6 +32,7 @@ #include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> #include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h> #include <Swift/Controllers/Roster/Roster.h> #include <Swift/Controllers/Roster/RosterVCardProvider.h> #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> @@ -385,6 +386,7 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { appendToJoinParts(joinParts_, event); std::string groupName(roleToGroupName(occupant.getRole())); roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole())); if (joined_) { std::string joinString; @@ -537,6 +539,7 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC std::string group(roleToGroupName(occupant.getRole())); roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); if (nick == nick_) { setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); @@ -548,6 +551,9 @@ void MUCController::handleOccupantAffiliationChanged(const std::string& nick, co if (nick == nick_) { setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole()); } + JID jid(nickToJID(nick)); + MUCOccupant occupant = muc_->getOccupant(nick); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation)); } std::string MUCController::roleToGroupName(MUCOccupant::Role role) { diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index 291fb22..3652e86 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -9,6 +9,7 @@ #include <boost/algorithm/string.hpp> #include <hippomocks.h> +#include "Swiften/Base/foreach.h" #include "Swift/Controllers/XMPPEvents/EventController.h" #include "Swiften/Presence/DirectedPresenceSender.h" #include "Swiften/Presence/StanzaChannelPresenceSender.h" @@ -20,6 +21,7 @@ #include "Swiften/Roster/XMPPRoster.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UnitTest/MockChatWindow.h" +#include "Swiften/MUC/UnitTest/MockMUC.h" #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" #include "Swiften/Presence/PresenceOracle.h" @@ -33,6 +35,8 @@ #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/Chat/UserSearchController.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swiften/Crypto/CryptoProvider.h> using namespace Swift; @@ -48,6 +52,7 @@ class MUCControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testMessageWithEmptyLabelItem); CPPUNIT_TEST(testMessageWithLabelItem); CPPUNIT_TEST(testCorrectMessageWithLabelItem); + CPPUNIT_TEST(testRoleAffiliationStates); CPPUNIT_TEST_SUITE_END(); public: @@ -74,7 +79,7 @@ public: entityCapsProvider_ = new DummyEntityCapsProvider(); settings_ = new DummySettingsProvider(); highlightManager_ = new HighlightManager(settings_); - muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_); + muc_ = boost::make_shared<MockMUC>(mucJID_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>()); vcardStorage_ = new VCardMemoryStorage(crypto_.get()); @@ -337,10 +342,73 @@ public: CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); } + JID jidFromOccupant(const MUCOccupant& occupant) { + return JID(mucJID_.toString()+"/"+occupant.getNick()); + } + + void testRoleAffiliationStates() { + + typedef std::map<std::string, MUCOccupant> occupant_map; + occupant_map occupants; + occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner))); + + /* populate the MUC with fake users */ + typedef const std::pair<std::string,MUCOccupant> occupantIterator; + foreach(occupantIterator &occupant, occupants) { + muc_->insertOccupant(occupant.second); + } + + std::vector<MUCOccupant> alterations; + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner)); + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast)); + + foreach(const MUCOccupant& alteration, alterations) { + /* perform an alteration to a user's role and affiliation */ + occupant_map::iterator occupant = occupants.find(alteration.getNick()); + CPPUNIT_ASSERT(occupant != occupants.end()); + const JID jid = jidFromOccupant(occupant->second); + /* change the affiliation, leave the role in place */ + muc_->changeAffiliation(jid, alteration.getAffiliation()); + occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + /* change the role, leave the affiliation in place */ + muc_->changeOccupantRole(jid, alteration.getRole()); + occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + } + } + + void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) { + /* verify that the roster is in sync */ + GroupRosterItem* group = window_->getRosterModel()->getRoot(); + foreach(RosterItem* rosterItem, group->getChildren()) { + GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem); + CPPUNIT_ASSERT(child); + foreach(RosterItem* childItem, child->getChildren()) { + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); + CPPUNIT_ASSERT(item); + std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); + CPPUNIT_ASSERT(occupant != occupants.end()); + CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); + CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); + } + } + } + private: JID self_; JID mucJID_; - MUC::ref muc_; + MockMUC::ref muc_; std::string nick_; DummyStanzaChannel* stanzaChannel_; DummyIQChannel* iqChannel_; diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 622b6ae..fde4c97 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -11,13 +11,15 @@ #include <Swiften/Base/foreach.h> #include <Swiften/Base/DateTime.h> #include <Swiften/Elements/Idle.h> - +#include <Swift/Controllers/Intl.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> namespace Swift { -ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID), blockState_(BlockingNotSupported) { +ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) +: RosterItem(name, parent), jid_(jid), displayJID_(displayJID), mucRole_(MUCOccupant::NoRole), mucAffiliation_(MUCOccupant::NoAffiliation), blockState_(BlockingNotSupported) +{ } ContactRosterItem::~ContactRosterItem() { @@ -137,6 +139,40 @@ void ContactRosterItem::removeGroup(const std::string& group) { groups_.erase(std::remove(groups_.begin(), groups_.end(), group), groups_.end()); } +MUCOccupant::Role ContactRosterItem::getMUCRole() const +{ + return mucRole_; +} + +void ContactRosterItem::setMUCRole(const MUCOccupant::Role& role) +{ + mucRole_ = role; +} + +MUCOccupant::Affiliation ContactRosterItem::getMUCAffiliation() const +{ + return mucAffiliation_; +} + +void ContactRosterItem::setMUCAffiliation(const MUCOccupant::Affiliation& affiliation) +{ + mucAffiliation_ = affiliation; +} + +std::string ContactRosterItem::getMUCAffiliationText() const +{ + std::string affiliationString; + switch (mucAffiliation_) { + case MUCOccupant::Owner: affiliationString = QT_TRANSLATE_NOOP("", "Owner"); break; + case MUCOccupant::Admin: affiliationString = QT_TRANSLATE_NOOP("", "Admin"); break; + case MUCOccupant::Member: affiliationString = QT_TRANSLATE_NOOP("", "Member"); break; + case MUCOccupant::Outcast: affiliationString = QT_TRANSLATE_NOOP("", "Outcast"); break; + case MUCOccupant::NoAffiliation: affiliationString = ""; break; + } + + return affiliationString; +} + void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) { features_ = features; onDataChanged(); diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 6de7909..ab10c66 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -18,6 +18,7 @@ #include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/Presence.h> #include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/MUCOccupant.h> #include <Swiften/Elements/VCard.h> #include <Swiften/JID/JID.h> @@ -61,7 +62,13 @@ class ContactRosterItem : public RosterItem { /** Only used so a contact can know about the groups it's in*/ void addGroup(const std::string& group); void removeGroup(const std::string& group); - + + MUCOccupant::Role getMUCRole() const; + void setMUCRole(const MUCOccupant::Role& role); + MUCOccupant::Affiliation getMUCAffiliation() const; + void setMUCAffiliation(const MUCOccupant::Affiliation& affiliation); + std::string getMUCAffiliationText() const; + void setSupportedFeatures(const std::set<Feature>& features); bool supportsFeature(Feature feature) const; @@ -82,6 +89,8 @@ class ContactRosterItem : public RosterItem { boost::shared_ptr<Presence> offlinePresence_; boost::shared_ptr<Presence> shownPresence_; std::vector<std::string> groups_; + MUCOccupant::Role mucRole_; + MUCOccupant::Affiliation mucAffiliation_; std::set<Feature> features_; BlockState blockState_; VCard::ref vcard_; diff --git a/Swift/Controllers/Roster/ItemOperations/SetMUC.h b/Swift/Controllers/Roster/ItemOperations/SetMUC.h new file mode 100644 index 0000000..de40e04 --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetMUC.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetMUC : public RosterItemOperation { + public: + SetMUC(const JID& jid, const MUCOccupant::Role& role, const MUCOccupant::Affiliation& affiliation) + : RosterItemOperation(true, jid), jid_(jid), mucRole_(role), mucAffiliation_(affiliation) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, JID::WithResource)) { + contact->setMUCRole(mucRole_); + contact->setMUCAffiliation(mucAffiliation_); + } + } + + private: + JID jid_; + bool mucParticipant_; + MUCOccupant::Role mucRole_; + MUCOccupant::Affiliation mucAffiliation_; +}; + +} diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 43779c5..59ed0f1 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -49,7 +49,8 @@ namespace Swift { virtual void setSecurityLabelsError() {} virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;} virtual void setInputEnabled(bool /*enabled*/) {} - virtual void setRosterModel(Roster* /*roster*/) {} + virtual void setRosterModel(Roster* roster) { roster_ = roster; } + Roster* getRosterModel() { return roster_; } virtual void setTabComplete(TabComplete*) {} void setAckState(const std::string& /*id*/, AckState /*state*/) {} @@ -86,6 +87,7 @@ namespace Swift { std::vector<SecurityLabelsCatalog::Item> labels_; bool labelsEnabled_; SecurityLabelsCatalog::Item label_; + Roster* roster_; }; } diff --git a/Swift/QtUI/Roster/RosterTooltip.cpp b/Swift/QtUI/Roster/RosterTooltip.cpp index edf9c99..045a955 100644 --- a/Swift/QtUI/Roster/RosterTooltip.cpp +++ b/Swift/QtUI/Roster/RosterTooltip.cpp @@ -39,6 +39,7 @@ QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaled "%6" "%7" "%8" + "%9" "</td>" "</tr>" "</table>"); @@ -55,6 +56,7 @@ QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaled "%6" "%7" "%8" + "%9" "</td>" "</tr>" "</table>"); @@ -97,7 +99,12 @@ QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaled lastSeen = htmlEscape(lastSeen) + "<br/>"; } - return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), idleString, lastSeen, vCardSummary); + QString mucOccupant= P2QSTRING(contact->getMUCAffiliationText()); + if (!mucOccupant.isEmpty()) { + mucOccupant = htmlEscape(mucOccupant) + "<br/>"; + } + + return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), mucOccupant, idleString, lastSeen, vCardSummary); } QString RosterTooltip::buildVCardSummary(VCard::ref vcard) { diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp index f85cf8d..c7ba470 100644 --- a/Swiften/MUC/MUC.cpp +++ b/Swiften/MUC/MUC.cpp @@ -1,430 +1,14 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swiften/MUC/MUC.h> -#include <boost/bind.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/smart_ptr/make_shared.hpp> - -#include <Swiften/Base/foreach.h> -#include <Swiften/Presence/DirectedPresenceSender.h> -#include <Swiften/Client/StanzaChannel.h> -#include <Swiften/Queries/IQRouter.h> -#include <Swiften/Elements/Form.h> -#include <Swiften/Elements/Message.h> -#include <Swiften/Elements/IQ.h> -#include <Swiften/Elements/MUCUserPayload.h> -#include <Swiften/Elements/MUCAdminPayload.h> -#include <Swiften/Elements/MUCPayload.h> -#include <Swiften/Elements/MUCDestroyPayload.h> -#include <Swiften/Elements/MUCInvitationPayload.h> -#include <Swiften/MUC/MUCRegistry.h> -#include <Swiften/Queries/GenericRequest.h> - namespace Swift { -typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; - -MUC::MUC(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false), isUnlocked_(false) { - scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUC::handleIncomingPresence, this, _1)); -} - -//FIXME: discover reserved nickname - -/** - * Join the MUC with default context. - */ -void MUC::joinAs(const std::string &nick) { - joinSince_ = boost::posix_time::not_a_date_time; - internalJoin(nick); -} - -/** - * Set the password used for entering the room. - */ -void MUC::setPassword(const boost::optional<std::string>& newPassword) { - password = newPassword; -} - -/** - * Join the MUC with context since date. - */ -void MUC::joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) { - joinSince_ = since; - internalJoin(nick); -} - -std::map<std::string, MUCOccupant> MUC::getOccupants() const { - return occupants; -} - -void MUC::internalJoin(const std::string &nick) { - //TODO: history request - joinComplete_ = false; - joinSucceeded_ = false; - - mucRegistry->addMUC(getJID()); - - ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick); - - Presence::ref joinPresence = boost::make_shared<Presence>(*presenceSender->getLastSentUndirectedPresence()); - assert(joinPresence->getType() == Presence::Available); - joinPresence->setTo(ownMUCJID); - MUCPayload::ref mucPayload = boost::make_shared<MUCPayload>(); - if (joinSince_ != boost::posix_time::not_a_date_time) { - mucPayload->setSince(joinSince_); - } - if (password) { - mucPayload->setPassword(*password); - } - joinPresence->addPayload(mucPayload); - - presenceSender->sendPresence(joinPresence); -} - -void MUC::part() { - presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); - mucRegistry->removeMUC(getJID()); -} - -void MUC::handleUserLeft(LeavingType type) { - std::map<std::string,MUCOccupant>::iterator i = occupants.find(ownMUCJID.getResource()); - if (i != occupants.end()) { - MUCOccupant me = i->second; - occupants.erase(i); - onOccupantLeft(me, type, ""); - } - occupants.clear(); - joinComplete_ = false; - joinSucceeded_ = false; - isUnlocked_ = false; - presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); -} - -void MUC::handleIncomingPresence(Presence::ref presence) { - if (!isFromMUC(presence->getFrom())) { - return; - } - - MUCUserPayload::ref mucPayload; - foreach (MUCUserPayload::ref payload, presence->getPayloads<MUCUserPayload>()) { - if (!payload->getItems().empty() || !payload->getStatusCodes().empty()) { - mucPayload = payload; - } - } - - // On the first incoming presence, check if our join has succeeded - // (i.e. we start getting non-error presence from the MUC) or not - if (!joinSucceeded_) { - if (presence->getType() == Presence::Error) { - std::string reason; - onJoinFailed(presence->getPayload<ErrorPayload>()); - return; - } - else { - joinSucceeded_ = true; - presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); - } - } - - std::string nick = presence->getFrom().getResource(); - if (nick.empty()) { - return; - } - MUCOccupant::Role role(MUCOccupant::NoRole); - MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation); - boost::optional<JID> realJID; - if (mucPayload && mucPayload->getItems().size() > 0) { - role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole; - affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation; - realJID = mucPayload->getItems()[0].realJID; - } - - //100 is non-anonymous - //TODO: 100 may also be specified in a <message/> - //170 is room logging to http - //TODO: Nick changes - if (presence->getType() == Presence::Unavailable) { - LeavingType type = LeavePart; - if (mucPayload) { - if (boost::dynamic_pointer_cast<MUCDestroyPayload>(mucPayload->getPayload())) { - type = LeaveDestroy; - } - else foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) { - if (status.code == 307) { - type = LeaveKick; - } - else if (status.code == 301) { - type = LeaveBan; - } - else if (status.code == 321) { - type = LeaveNotMember; - } - } - } - - if (presence->getFrom() == ownMUCJID) { - handleUserLeft(type); - return; - } - else { - std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick); - if (i != occupants.end()) { - //TODO: part type - MUCOccupant occupant = i->second; - occupants.erase(i); - onOccupantLeft(occupant, type, ""); - } - } - } - else if (presence->getType() == Presence::Available) { - std::map<std::string, MUCOccupant>::iterator it = occupants.find(nick); - MUCOccupant occupant(nick, role, affiliation); - bool isJoin = true; - if (realJID) { - occupant.setRealJID(realJID.get()); - } - if (it != occupants.end()) { - isJoin = false; - MUCOccupant oldOccupant = it->second; - if (oldOccupant.getRole() != role) { - onOccupantRoleChanged(nick, occupant, oldOccupant.getRole()); - } - if (oldOccupant.getAffiliation() != affiliation) { - onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation()); - } - occupants.erase(it); - } - std::pair<std::map<std::string, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, occupant)); - if (isJoin) { - onOccupantJoined(result.first->second); - } - onOccupantPresenceChange(presence); - } - if (mucPayload && !joinComplete_) { - bool isLocked = false; - 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; - if (ownMUCJID != presence->getFrom()) { - presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); - ownMUCJID = presence->getFrom(); - presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); - } - onJoinComplete(getOwnNick()); - } - if (status.code == 201) { - isLocked = true; - /* Room is created and locked */ - /* Currently deal with this by making an instant room */ - if (ownMUCJID != presence->getFrom()) { - presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); - ownMUCJID = presence->getFrom(); - presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); - } - if (createAsReservedIfNew) { - unlocking = true; - requestConfigurationForm(); - } - else { - MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); - presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); - mucPayload->setPayload(boost::make_shared<Form>(Form::SubmitType)); - GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); - request->onResponse.connect(boost::bind(&MUC::handleCreationConfigResponse, this, _1, _2)); - request->send(); - } - } - } - if (!isLocked && !isUnlocked_ && (presence->getFrom() == ownMUCJID)) { - isUnlocked_ = true; - onUnlocked(); - } - } -} - -void MUC::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPayload::ref error) { - unlocking = false; - if (error) { - presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); - onJoinFailed(error); - } else { - onJoinComplete(getOwnNick()); /* Previously, this wasn't needed here, as the presence duplication bug caused an emit elsewhere. */ - isUnlocked_ = true; - onUnlocked(); - } -} - -bool MUC::hasOccupant(const std::string& nick) { - return occupants.find(nick) != occupants.end(); -} - -const MUCOccupant& MUC::getOccupant(const std::string& nick) { - return occupants.find(nick)->second; -} - -void MUC::kickOccupant(const JID& jid) { - changeOccupantRole(jid, MUCOccupant::NoRole); -} - -/** - * Call with the room JID, not the real JID. - */ -void MUC::changeOccupantRole(const JID& jid, MUCOccupant::Role role) { - MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); - MUCItem item; - item.role = role; - item.nick = jid.getResource(); - mucPayload->addItem(item); - GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); - request->onResponse.connect(boost::bind(&MUC::handleOccupantRoleChangeResponse, this, _1, _2, jid, role)); - request->send(); - -} - -void MUC::handleOccupantRoleChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Role role) { - if (error) { - onRoleChangeFailed(error, jid, role); - } -} - -void MUC::requestAffiliationList(MUCOccupant::Affiliation affiliation) { - MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); - MUCItem item; - item.affiliation = affiliation; - mucPayload->addItem(item); - GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Get, getJID(), mucPayload, iqRouter_); - request->onResponse.connect(boost::bind(&MUC::handleAffiliationListResponse, this, _1, _2, affiliation)); - request->send(); -} - -/** - * Must be called with the real JID, not the room JID. - */ -void MUC::changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) { - MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); - MUCItem item; - item.affiliation = affiliation; - item.realJID = jid.toBare(); - mucPayload->addItem(item); - GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); - request->onResponse.connect(boost::bind(&MUC::handleAffiliationChangeResponse, this, _1, _2, jid, affiliation)); - request->send(); -} - -void MUC::handleAffiliationListResponse(MUCAdminPayload::ref payload, ErrorPayload::ref error, MUCOccupant::Affiliation affiliation) { - if (error) { - onAffiliationListFailed(error); - } - else { - std::vector<JID> jids; - foreach (MUCItem item, payload->getItems()) { - if (item.realJID) { - jids.push_back(*item.realJID); - } - } - onAffiliationListReceived(affiliation, jids); - } -} - -void MUC::handleAffiliationChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Affiliation affiliation) { - if (error) { - onAffiliationChangeFailed(error, jid, affiliation); - } -} - -void MUC::changeSubject(const std::string& subject) { - Message::ref message = boost::make_shared<Message>(); - message->setSubject(subject); - message->setType(Message::Groupchat); - message->setTo(ownMUCJID.toBare()); - stanzaChannel->sendMessage(message); -} - -void MUC::requestConfigurationForm() { - MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); - GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Get, getJID(), mucPayload, iqRouter_); - request->onResponse.connect(boost::bind(&MUC::handleConfigurationFormReceived, this, _1, _2)); - request->send(); +MUC::~MUC() { } -void MUC::cancelConfigureRoom() { - MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); - mucPayload->setPayload(boost::make_shared<Form>(Form::CancelType)); - GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); - request->send(); -} - -void MUC::handleConfigurationFormReceived(MUCOwnerPayload::ref payload, ErrorPayload::ref error) { - Form::ref form; - if (payload) { - form = payload->getForm(); - } - if (error || !form) { - onConfigurationFailed(error); - } else { - onConfigurationFormReceived(form); - } -} - -void MUC::handleConfigurationResultReceived(MUCOwnerPayload::ref /*payload*/, ErrorPayload::ref error) { - if (error) { - onConfigurationFailed(error); - } -} - -void MUC::configureRoom(Form::ref form) { - MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); - mucPayload->setPayload(form); - GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); - if (unlocking) { - request->onResponse.connect(boost::bind(&MUC::handleCreationConfigResponse, this, _1, _2)); - } - else { - request->onResponse.connect(boost::bind(&MUC::handleConfigurationResultReceived, this, _1, _2)); - } - request->send(); -} - -void MUC::destroyRoom() { - MUCOwnerPayload::ref mucPayload = boost::make_shared<MUCOwnerPayload>(); - MUCDestroyPayload::ref mucDestroyPayload = boost::make_shared<MUCDestroyPayload>(); - mucPayload->setPayload(mucDestroyPayload); - GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); - request->onResponse.connect(boost::bind(&MUC::handleConfigurationResultReceived, this, _1, _2)); - request->send(); -} - -void MUC::invitePerson(const JID& person, const std::string& reason, bool isImpromptu, bool isReuseChat) { - Message::ref message = boost::make_shared<Message>(); - message->setTo(person); - message->setType(Message::Normal); - MUCInvitationPayload::ref invite = boost::make_shared<MUCInvitationPayload>(); - invite->setReason(reason); - invite->setJID(ownMUCJID.toBare()); - invite->setIsImpromptu(isImpromptu); - invite->setIsContinuation(isReuseChat); - message->addPayload(invite); - stanzaChannel->sendMessage(message); -} - -//TODO: Invites(direct/mediated) - -//TODO: requesting membership - -//TODO: get member list - -//TODO: request voice - -//TODO: moderator use cases - -//TODO: Admin use cases - -//TODO: Owner use cases - } diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h index 6a0ab75..0dcccd9 100644 --- a/Swiften/MUC/MUC.h +++ b/Swiften/MUC/MUC.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -36,50 +36,46 @@ namespace Swift { enum LeavingType { LeavePart, LeaveKick, LeaveBan, LeaveDestroy, LeaveNotMember, Disconnect }; public: - MUC(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry); + virtual ~MUC(); /** * Returns the (bare) JID of the MUC. */ - JID getJID() const { - return ownMUCJID.toBare(); - } + virtual JID getJID() const = 0; /** * Returns if the room is unlocked and other people can join the room. * @return True if joinable by others; false otherwise. */ - bool isUnlocked() const { - return isUnlocked_; - } + virtual bool isUnlocked() const = 0; - void joinAs(const std::string &nick); - void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since); - /*void queryRoomInfo(); */ - /*void queryRoomItems(); */ - std::string getCurrentNick(); - std::map<std::string, MUCOccupant> getOccupants() const; - void part(); - void handleIncomingMessage(Message::ref message); + virtual void joinAs(const std::string &nick) = 0; + virtual void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) = 0; + /*virtual void queryRoomInfo(); */ + /*virtual void queryRoomItems(); */ + /*virtual std::string getCurrentNick() = 0; */ + virtual std::map<std::string, MUCOccupant> getOccupants() const = 0; + virtual void part() = 0; + /*virtual void handleIncomingMessage(Message::ref message) = 0; */ /** Expose public so it can be called when e.g. user goes offline */ - void handleUserLeft(LeavingType); + virtual void handleUserLeft(LeavingType) = 0; /** Get occupant information*/ - const MUCOccupant& getOccupant(const std::string& nick); - bool hasOccupant(const std::string& nick); - void kickOccupant(const JID& jid); - void changeOccupantRole(const JID& jid, MUCOccupant::Role role); - void requestAffiliationList(MUCOccupant::Affiliation); - void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation); - void changeSubject(const std::string& subject); - void requestConfigurationForm(); - void configureRoom(Form::ref); - void cancelConfigureRoom(); - void destroyRoom(); + virtual const MUCOccupant& getOccupant(const std::string& nick) = 0; + virtual bool hasOccupant(const std::string& nick) = 0; + virtual void kickOccupant(const JID& jid) = 0; + virtual void changeOccupantRole(const JID& jid, MUCOccupant::Role role) = 0; + virtual void requestAffiliationList(MUCOccupant::Affiliation) = 0; + virtual void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) = 0; + virtual void changeSubject(const std::string& subject) = 0; + virtual void requestConfigurationForm() = 0; + virtual void configureRoom(Form::ref) = 0; + virtual void cancelConfigureRoom() = 0; + virtual void destroyRoom() = 0; /** Send an invite for the person to join the MUC */ - void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false); - void setCreateAsReservedIfNew() {createAsReservedIfNew = true;} - void setPassword(const boost::optional<std::string>& password); - + virtual void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false) = 0; + virtual void setCreateAsReservedIfNew() = 0; + virtual void setPassword(const boost::optional<std::string>& password) = 0; + public: boost::signal<void (const std::string& /*nick*/)> onJoinComplete; boost::signal<void (ErrorPayload::ref)> onJoinFailed; @@ -97,41 +93,6 @@ namespace Swift { boost::signal<void ()> onUnlocked; /* boost::signal<void (const MUCInfo&)> onInfoResult; */ /* boost::signal<void (const blah&)> onItemsResult; */ - - - private: - bool isFromMUC(const JID& j) const { - return ownMUCJID.equals(j, JID::WithoutResource); - } - - const std::string& getOwnNick() const { - return ownMUCJID.getResource(); - } - - private: - void handleIncomingPresence(Presence::ref presence); - void internalJoin(const std::string& nick); - void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref); - void handleOccupantRoleChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Role); - void handleAffiliationChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Affiliation); - void handleAffiliationListResponse(MUCAdminPayload::ref, ErrorPayload::ref, MUCOccupant::Affiliation); - void handleConfigurationFormReceived(MUCOwnerPayload::ref, ErrorPayload::ref); - void handleConfigurationResultReceived(MUCOwnerPayload::ref, ErrorPayload::ref); - private: - JID ownMUCJID; - StanzaChannel* stanzaChannel; - IQRouter* iqRouter_; - DirectedPresenceSender* presenceSender; - MUCRegistry* mucRegistry; - std::map<std::string, MUCOccupant> occupants; - bool joinSucceeded_; - bool joinComplete_; - boost::bsignals::scoped_connection scopedConnection_; - boost::posix_time::ptime joinSince_; - bool createAsReservedIfNew; - bool unlocking; - bool isUnlocked_; - boost::optional<std::string> password; }; } diff --git a/Swiften/MUC/MUCImpl.cpp b/Swiften/MUC/MUCImpl.cpp new file mode 100644 index 0000000..99789c0 --- /dev/null +++ b/Swiften/MUC/MUCImpl.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2010-2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/MUC/MUCImpl.h> + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Presence/DirectedPresenceSender.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Elements/Form.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/IQ.h> +#include <Swiften/Elements/MUCUserPayload.h> +#include <Swiften/Elements/MUCAdminPayload.h> +#include <Swiften/Elements/MUCPayload.h> +#include <Swiften/Elements/MUCDestroyPayload.h> +#include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/Queries/GenericRequest.h> + +namespace Swift { + +typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; + +MUCImpl::MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false), isUnlocked_(false) { + scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUCImpl::handleIncomingPresence, this, _1)); +} + +MUCImpl::~MUCImpl() +{ +} + +//FIXME: discover reserved nickname + +/** + * Join the MUC with default context. + */ +void MUCImpl::joinAs(const std::string &nick) { + joinSince_ = boost::posix_time::not_a_date_time; + internalJoin(nick); +} + +/** + * Set the password used for entering the room. + */ +void MUCImpl::setPassword(const boost::optional<std::string>& newPassword) { + password = newPassword; +} + +/** + * Join the MUC with context since date. + */ +void MUCImpl::joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) { + joinSince_ = since; + internalJoin(nick); +} + +std::map<std::string, MUCOccupant> MUCImpl::getOccupants() const { + return occupants; +} + +void MUCImpl::internalJoin(const std::string &nick) { + //TODO: history request + joinComplete_ = false; + joinSucceeded_ = false; + + mucRegistry->addMUC(getJID()); + + ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick); + + Presence::ref joinPresence = boost::make_shared<Presence>(*presenceSender->getLastSentUndirectedPresence()); + assert(joinPresence->getType() == Presence::Available); + joinPresence->setTo(ownMUCJID); + MUCPayload::ref mucPayload = boost::make_shared<MUCPayload>(); + if (joinSince_ != boost::posix_time::not_a_date_time) { + mucPayload->setSince(joinSince_); + } + if (password) { + mucPayload->setPassword(*password); + } + joinPresence->addPayload(mucPayload); + + presenceSender->sendPresence(joinPresence); +} + +void MUCImpl::part() { + presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); + mucRegistry->removeMUC(getJID()); +} + +void MUCImpl::handleUserLeft(LeavingType type) { + std::map<std::string,MUCOccupant>::iterator i = occupants.find(ownMUCJID.getResource()); + if (i != occupants.end()) { + MUCOccupant me = i->second; + occupants.erase(i); + onOccupantLeft(me, type, ""); + } + occupants.clear(); + joinComplete_ = false; + joinSucceeded_ = false; + isUnlocked_ = false; + presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); +} + +void MUCImpl::handleIncomingPresence(Presence::ref presence) { + if (!isFromMUC(presence->getFrom())) { + return; + } + + MUCUserPayload::ref mucPayload; + foreach (MUCUserPayload::ref payload, presence->getPayloads<MUCUserPayload>()) { + if (!payload->getItems().empty() || !payload->getStatusCodes().empty()) { + mucPayload = payload; + } + } + + // On the first incoming presence, check if our join has succeeded + // (i.e. we start getting non-error presence from the MUC) or not + if (!joinSucceeded_) { + if (presence->getType() == Presence::Error) { + std::string reason; + onJoinFailed(presence->getPayload<ErrorPayload>()); + return; + } + else { + joinSucceeded_ = true; + presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); + } + } + + std::string nick = presence->getFrom().getResource(); + if (nick.empty()) { + return; + } + MUCOccupant::Role role(MUCOccupant::NoRole); + MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation); + boost::optional<JID> realJID; + if (mucPayload && mucPayload->getItems().size() > 0) { + role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole; + affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation; + realJID = mucPayload->getItems()[0].realJID; + } + + //100 is non-anonymous + //TODO: 100 may also be specified in a <message/> + //170 is room logging to http + //TODO: Nick changes + if (presence->getType() == Presence::Unavailable) { + LeavingType type = LeavePart; + if (mucPayload) { + if (boost::dynamic_pointer_cast<MUCDestroyPayload>(mucPayload->getPayload())) { + type = LeaveDestroy; + } + else foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) { + if (status.code == 307) { + type = LeaveKick; + } + else if (status.code == 301) { + type = LeaveBan; + } + else if (status.code == 321) { + type = LeaveNotMember; + } + } + } + + if (presence->getFrom() == ownMUCJID) { + handleUserLeft(type); + return; + } + else { + std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick); + if (i != occupants.end()) { + //TODO: part type + MUCOccupant occupant = i->second; + occupants.erase(i); + onOccupantLeft(occupant, type, ""); + } + } + } + else if (presence->getType() == Presence::Available) { + std::map<std::string, MUCOccupant>::iterator it = occupants.find(nick); + MUCOccupant occupant(nick, role, affiliation); + bool isJoin = true; + if (realJID) { + occupant.setRealJID(realJID.get()); + } + if (it != occupants.end()) { + isJoin = false; + MUCOccupant oldOccupant = it->second; + if (oldOccupant.getRole() != role) { + onOccupantRoleChanged(nick, occupant, oldOccupant.getRole()); + } + if (oldOccupant.getAffiliation() != affiliation) { + onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation()); + } + occupants.erase(it); + } + std::pair<std::map<std::string, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, occupant)); + if (isJoin) { + onOccupantJoined(result.first->second); + } + onOccupantPresenceChange(presence); + } + if (mucPayload && !joinComplete_) { + bool isLocked = false; + 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; + if (ownMUCJID != presence->getFrom()) { + presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); + ownMUCJID = presence->getFrom(); + presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); + } + onJoinComplete(getOwnNick()); + } + if (status.code == 201) { + isLocked = true; + /* Room is created and locked */ + /* Currently deal with this by making an instant room */ + if (ownMUCJID != presence->getFrom()) { + presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); + ownMUCJID = presence->getFrom(); + presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); + } + if (createAsReservedIfNew) { + unlocking = true; + requestConfigurationForm(); + } + else { + MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); + presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); + mucPayload->setPayload(boost::make_shared<Form>(Form::SubmitType)); + GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUCImpl::handleCreationConfigResponse, this, _1, _2)); + request->send(); + } + } + } + if (!isLocked && !isUnlocked_ && (presence->getFrom() == ownMUCJID)) { + isUnlocked_ = true; + onUnlocked(); + } + } +} + +void MUCImpl::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPayload::ref error) { + unlocking = false; + if (error) { + presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); + onJoinFailed(error); + } else { + onJoinComplete(getOwnNick()); /* Previously, this wasn't needed here, as the presence duplication bug caused an emit elsewhere. */ + isUnlocked_ = true; + onUnlocked(); + } +} + +bool MUCImpl::hasOccupant(const std::string& nick) { + return occupants.find(nick) != occupants.end(); +} + +const MUCOccupant& MUCImpl::getOccupant(const std::string& nick) { + return occupants.find(nick)->second; +} + +void MUCImpl::kickOccupant(const JID& jid) { + changeOccupantRole(jid, MUCOccupant::NoRole); +} + +/** + * Call with the room JID, not the real JID. + */ +void MUCImpl::changeOccupantRole(const JID& jid, MUCOccupant::Role role) { + MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); + MUCItem item; + item.role = role; + item.nick = jid.getResource(); + mucPayload->addItem(item); + GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUCImpl::handleOccupantRoleChangeResponse, this, _1, _2, jid, role)); + request->send(); + +} + +void MUCImpl::handleOccupantRoleChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Role role) { + if (error) { + onRoleChangeFailed(error, jid, role); + } +} + +void MUCImpl::requestAffiliationList(MUCOccupant::Affiliation affiliation) { + MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); + MUCItem item; + item.affiliation = affiliation; + mucPayload->addItem(item); + GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Get, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUCImpl::handleAffiliationListResponse, this, _1, _2, affiliation)); + request->send(); +} + +/** + * Must be called with the real JID, not the room JID. + */ +void MUCImpl::changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) { + MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); + MUCItem item; + item.affiliation = affiliation; + item.realJID = jid.toBare(); + mucPayload->addItem(item); + GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUCImpl::handleAffiliationChangeResponse, this, _1, _2, jid, affiliation)); + request->send(); +} + +void MUCImpl::handleAffiliationListResponse(MUCAdminPayload::ref payload, ErrorPayload::ref error, MUCOccupant::Affiliation affiliation) { + if (error) { + onAffiliationListFailed(error); + } + else { + std::vector<JID> jids; + foreach (MUCItem item, payload->getItems()) { + if (item.realJID) { + jids.push_back(*item.realJID); + } + } + onAffiliationListReceived(affiliation, jids); + } +} + +void MUCImpl::handleAffiliationChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Affiliation affiliation) { + if (error) { + onAffiliationChangeFailed(error, jid, affiliation); + } +} + +void MUCImpl::changeSubject(const std::string& subject) { + Message::ref message = boost::make_shared<Message>(); + message->setSubject(subject); + message->setType(Message::Groupchat); + message->setTo(ownMUCJID.toBare()); + stanzaChannel->sendMessage(message); +} + +void MUCImpl::requestConfigurationForm() { + MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); + GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Get, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationFormReceived, this, _1, _2)); + request->send(); +} + +void MUCImpl::cancelConfigureRoom() { + MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); + mucPayload->setPayload(boost::make_shared<Form>(Form::CancelType)); + GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + request->send(); +} + +void MUCImpl::handleConfigurationFormReceived(MUCOwnerPayload::ref payload, ErrorPayload::ref error) { + Form::ref form; + if (payload) { + form = payload->getForm(); + } + if (error || !form) { + onConfigurationFailed(error); + } else { + onConfigurationFormReceived(form); + } +} + +void MUCImpl::handleConfigurationResultReceived(MUCOwnerPayload::ref /*payload*/, ErrorPayload::ref error) { + if (error) { + onConfigurationFailed(error); + } +} + +void MUCImpl::configureRoom(Form::ref form) { + MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); + mucPayload->setPayload(form); + GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + if (unlocking) { + request->onResponse.connect(boost::bind(&MUCImpl::handleCreationConfigResponse, this, _1, _2)); + } + else { + request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationResultReceived, this, _1, _2)); + } + request->send(); +} + +void MUCImpl::destroyRoom() { + MUCOwnerPayload::ref mucPayload = boost::make_shared<MUCOwnerPayload>(); + MUCDestroyPayload::ref mucDestroyPayload = boost::make_shared<MUCDestroyPayload>(); + mucPayload->setPayload(mucDestroyPayload); + GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationResultReceived, this, _1, _2)); + request->send(); +} + +void MUCImpl::invitePerson(const JID& person, const std::string& reason, bool isImpromptu, bool isReuseChat) { + Message::ref message = boost::make_shared<Message>(); + message->setTo(person); + message->setType(Message::Normal); + MUCInvitationPayload::ref invite = boost::make_shared<MUCInvitationPayload>(); + invite->setReason(reason); + invite->setJID(ownMUCJID.toBare()); + invite->setIsImpromptu(isImpromptu); + invite->setIsContinuation(isReuseChat); + message->addPayload(invite); + stanzaChannel->sendMessage(message); +} + +//TODO: Invites(direct/mediated) + +//TODO: requesting membership + +//TODO: get member list + +//TODO: request voice + +//TODO: moderator use cases + +//TODO: Admin use cases + +//TODO: Owner use cases + +} diff --git a/Swiften/MUC/MUCImpl.h b/Swiften/MUC/MUCImpl.h new file mode 100644 index 0000000..846ddcf --- /dev/null +++ b/Swiften/MUC/MUCImpl.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010-2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/MUC/MUC.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Base/API.h> +#include <string> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/Elements/MUCOwnerPayload.h> +#include <Swiften/Elements/MUCAdminPayload.h> +#include <Swiften/Elements/Form.h> + +#include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <boost/signals/connection.hpp> + +#include <map> + +namespace Swift { + class StanzaChannel; + class IQRouter; + class DirectedPresenceSender; + + class SWIFTEN_API MUCImpl : public MUC { + public: + typedef boost::shared_ptr<MUCImpl> ref; + + public: + MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry); + virtual ~MUCImpl(); + + /** + * Returns the (bare) JID of the MUC. + */ + virtual JID getJID() const { + return ownMUCJID.toBare(); + } + + /** + * Returns if the room is unlocked and other people can join the room. + * @return True if joinable by others; false otherwise. + */ + virtual bool isUnlocked() const { + return isUnlocked_; + } + + virtual void joinAs(const std::string &nick); + virtual void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since); + /*virtual void queryRoomInfo(); */ + /*virtual void queryRoomItems(); */ + /*virtual std::string getCurrentNick(); */ + virtual std::map<std::string, MUCOccupant> getOccupants() const; + virtual void part(); + /*virtual void handleIncomingMessage(Message::ref message); */ + /** Expose public so it can be called when e.g. user goes offline */ + virtual void handleUserLeft(LeavingType); + /** Get occupant information*/ + virtual const MUCOccupant& getOccupant(const std::string& nick); + virtual bool hasOccupant(const std::string& nick); + virtual void kickOccupant(const JID& jid); + virtual void changeOccupantRole(const JID& jid, MUCOccupant::Role role); + virtual void requestAffiliationList(MUCOccupant::Affiliation); + virtual void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation); + virtual void changeSubject(const std::string& subject); + virtual void requestConfigurationForm(); + virtual void configureRoom(Form::ref); + virtual void cancelConfigureRoom(); + virtual void destroyRoom(); + /** Send an invite for the person to join the MUC */ + virtual void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false); + virtual void setCreateAsReservedIfNew() {createAsReservedIfNew = true;} + virtual void setPassword(const boost::optional<std::string>& password); + + private: + bool isFromMUC(const JID& j) const { + return ownMUCJID.equals(j, JID::WithoutResource); + } + + const std::string& getOwnNick() const { + return ownMUCJID.getResource(); + } + + private: + void handleIncomingPresence(Presence::ref presence); + void internalJoin(const std::string& nick); + void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref); + void handleOccupantRoleChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Role); + void handleAffiliationChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Affiliation); + void handleAffiliationListResponse(MUCAdminPayload::ref, ErrorPayload::ref, MUCOccupant::Affiliation); + void handleConfigurationFormReceived(MUCOwnerPayload::ref, ErrorPayload::ref); + void handleConfigurationResultReceived(MUCOwnerPayload::ref, ErrorPayload::ref); + + private: + JID ownMUCJID; + StanzaChannel* stanzaChannel; + IQRouter* iqRouter_; + DirectedPresenceSender* presenceSender; + MUCRegistry* mucRegistry; + std::map<std::string, MUCOccupant> occupants; + bool joinSucceeded_; + bool joinComplete_; + boost::bsignals::scoped_connection scopedConnection_; + boost::posix_time::ptime joinSince_; + bool createAsReservedIfNew; + bool unlocking; + bool isUnlocked_; + boost::optional<std::string> password; + }; +} diff --git a/Swiften/MUC/MUCManager.cpp b/Swiften/MUC/MUCManager.cpp index 6e9b820..0581829 100644 --- a/Swiften/MUC/MUCManager.cpp +++ b/Swiften/MUC/MUCManager.cpp @@ -5,6 +5,7 @@ */ #include <Swiften/MUC/MUCManager.h> +#include <Swiften/MUC/MUCImpl.h> namespace Swift { @@ -12,7 +13,7 @@ MUCManager::MUCManager(StanzaChannel* stanzaChannel, IQRouter* iqRouter, Directe } MUC::ref MUCManager::createMUC(const JID& jid) { - return MUC::ref(new MUC(stanzaChannel, iqRouter, presenceSender, jid, mucRegistry)); + return boost::make_shared<MUCImpl>(stanzaChannel, iqRouter, presenceSender, jid, mucRegistry); } } diff --git a/Swiften/MUC/UnitTest/MUCTest.cpp b/Swiften/MUC/UnitTest/MUCTest.cpp index 427e938..d1a21b0 100644 --- a/Swiften/MUC/UnitTest/MUCTest.cpp +++ b/Swiften/MUC/UnitTest/MUCTest.cpp @@ -10,7 +10,7 @@ #include <boost/smart_ptr/make_shared.hpp> #include <boost/bind.hpp> -#include <Swiften/MUC/MUC.h> +#include <Swiften/MUC/MUCImpl.h> #include <Swiften/Client/DummyStanzaChannel.h> #include <Swiften/Presence/StanzaChannelPresenceSender.h> #include <Swiften/Presence/DirectedPresenceSender.h> @@ -158,7 +158,7 @@ class MUCTest : public CppUnit::TestFixture { private: MUC::ref createMUC(const JID& jid) { - return boost::make_shared<MUC>(channel, router, presenceSender, jid, mucRegistry); + return boost::make_shared<MUCImpl>(channel, router, presenceSender, jid, mucRegistry); } void handleJoinFinished(const std::string& nick, ErrorPayload::ref error) { diff --git a/Swiften/MUC/UnitTest/MockMUC.cpp b/Swiften/MUC/UnitTest/MockMUC.cpp new file mode 100644 index 0000000..9ca35ec --- /dev/null +++ b/Swiften/MUC/UnitTest/MockMUC.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/MUC/UnitTest/MockMUC.h> + +namespace Swift { + +MockMUC::MockMUC(const JID &muc) +: ownMUCJID(muc) +{ +} + +MockMUC::~MockMUC() { +} + +void MockMUC::insertOccupant(const MUCOccupant& occupant) +{ + occupants_.insert(std::make_pair(occupant.getNick(), occupant)); + onOccupantJoined(occupant); +} + +const MUCOccupant& MockMUC::getOccupant(const std::string& nick) { + return occupants_.find(nick)->second; +} + +bool MockMUC::hasOccupant(const std::string& nick) { + return occupants_.find(nick) != occupants_.end(); +} + +void MockMUC::changeAffiliation(const JID &jid, MUCOccupant::Affiliation newAffilation) { + std::map<std::string, MUCOccupant>::iterator i = occupants_.find(jid.getResource()); + if (i != occupants_.end()) { + const MUCOccupant old = i->second; + i->second = MUCOccupant(old.getNick(), old.getRole(), newAffilation); + onOccupantAffiliationChanged(i->first, newAffilation, old.getAffiliation()); + } +} + +void MockMUC::changeOccupantRole(const JID &jid, MUCOccupant::Role newRole) { + std::map<std::string, MUCOccupant>::iterator i = occupants_.find(jid.getResource()); + if (i != occupants_.end()) { + const MUCOccupant old = i->second; + i->second = MUCOccupant(old.getNick(), newRole, old.getAffiliation()); + onOccupantRoleChanged(i->first, i->second, old.getRole()); + } +} + +} diff --git a/Swiften/MUC/UnitTest/MockMUC.h b/Swiften/MUC/UnitTest/MockMUC.h new file mode 100644 index 0000000..78c2fb5 --- /dev/null +++ b/Swiften/MUC/UnitTest/MockMUC.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2013 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/MUC/MUC.h> +#include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/Elements/MUCOwnerPayload.h> +#include <Swiften/Elements/MUCAdminPayload.h> +#include <Swiften/Elements/Form.h> +#include <Swiften/Base/API.h> +#include <Swiften/Base/boost_bsignals.h> +#include <boost/signals/connection.hpp> +#include <boost/shared_ptr.hpp> +#include <string> +#include <map> + +namespace Swift { + class StanzaChannel; + class IQRouter; + class DirectedPresenceSender; + + class SWIFTEN_API MockMUC : public MUC{ + public: + typedef boost::shared_ptr<MockMUC> ref; + + public: + MockMUC(const JID &muc); + virtual ~MockMUC(); + + /** + * Cause a user to appear to have entered the room. For testing only. + */ + void insertOccupant(const MUCOccupant& occupant); + + /** + * Returns the (bare) JID of the MUC. + */ + virtual JID getJID() const { + return ownMUCJID.toBare(); + } + /** + * Returns if the room is unlocked and other people can join the room. + * @return True if joinable by others; false otherwise. + */ + virtual bool isUnlocked() const { return true; } + + virtual void joinAs(const std::string&) {} + virtual void joinWithContextSince(const std::string&, const boost::posix_time::ptime&) {} + /*virtual void queryRoomInfo(); */ + /*virtual void queryRoomItems(); */ + /*virtual std::string getCurrentNick() = 0; */ + virtual std::map<std::string, MUCOccupant> getOccupants() const { return occupants_; } + virtual void part() {} + /*virtual void handleIncomingMessage(Message::ref message) = 0; */ + /** Expose public so it can be called when e.g. user goes offline */ + virtual void handleUserLeft(LeavingType) {} + /** Get occupant information*/ + virtual const MUCOccupant& getOccupant(const std::string&); + virtual bool hasOccupant(const std::string&); + virtual void kickOccupant(const JID&) {} + virtual void changeOccupantRole(const JID&, MUCOccupant::Role); + virtual void requestAffiliationList(MUCOccupant::Affiliation) {} + virtual void changeAffiliation(const JID&, MUCOccupant::Affiliation); + virtual void changeSubject(const std::string&) {} + virtual void requestConfigurationForm() {} + virtual void configureRoom(Form::ref) {} + virtual void cancelConfigureRoom() {} + virtual void destroyRoom() {} + /** Send an invite for the person to join the MUC */ + virtual void invitePerson(const JID&, const std::string&, bool, bool) {} + virtual void setCreateAsReservedIfNew() {} + virtual void setPassword(const boost::optional<std::string>&) {} + + protected: + virtual bool isFromMUC(const JID& j) const { + return ownMUCJID.equals(j, JID::WithoutResource); + } + + virtual const std::string& getOwnNick() const { + return ownMUCJID.getResource(); + } + + private: + JID ownMUCJID; + std::map<std::string, MUCOccupant> occupants_; + }; +} diff --git a/Swiften/SConscript b/Swiften/SConscript index a8daac5..75944f0 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -142,6 +142,7 @@ if env["SCONS_STAGE"] == "build" : "Entity/Entity.cpp", "Entity/PayloadPersister.cpp", "MUC/MUC.cpp", + "MUC/MUCImpl.cpp", "MUC/MUCManager.cpp", "MUC/MUCRegistry.cpp", "MUC/MUCBookmarkManager.cpp", @@ -365,6 +366,7 @@ if env["SCONS_STAGE"] == "build" : File("LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp"), File("LinkLocal/UnitTest/LinkLocalServiceTest.cpp"), File("MUC/UnitTest/MUCTest.cpp"), + File("MUC/UnitTest/MockMUC.cpp"), File("Network/UnitTest/HostAddressTest.cpp"), File("Network/UnitTest/ConnectorTest.cpp"), File("Network/UnitTest/ChainedConnectorTest.cpp"), |