diff options
| author | Richard Maudsley <richard.maudsley@isode.com> | 2013-12-16 09:45:40 (GMT) | 
|---|---|---|
| committer | Richard Maudsley <richard.maudsley@isode.com> | 2013-12-18 14:48:12 (GMT) | 
| commit | 26994474c1ebfe874c2cd62ededf9a82b0496136 (patch) | |
| tree | 20feb19c35f438b7789fe0c5113412c87b27b235 | |
| parent | 503a8077c8811c2e9f65a619c33690a36eb5c153 (diff) | |
| download | swift-26994474c1ebfe874c2cd62ededf9a82b0496136.zip swift-26994474c1ebfe874c2cd62ededf9a82b0496136.tar.bz2 | |
Add affiliations to tooltips for MUC occupant lists.
Also extracts MUC into an interface and MUCImpl the existing implementation, adds a MockMUC for using in unit tests, and adds unit tests for the MUCController changes.
Change-Id: I25034384f59d3c274c46ffc37b2d1ae60ec660f4
| -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"), | 
 Swift
 Swift