summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swiften')
-rw-r--r--Swiften/MUC/MUC.cpp420
-rw-r--r--Swiften/MUC/MUC.h95
-rw-r--r--Swiften/MUC/MUCImpl.cpp434
-rw-r--r--Swiften/MUC/MUCImpl.h117
-rw-r--r--Swiften/MUC/MUCManager.cpp3
-rw-r--r--Swiften/MUC/UnitTest/MUCTest.cpp4
-rw-r--r--Swiften/MUC/UnitTest/MockMUC.cpp51
-rw-r--r--Swiften/MUC/UnitTest/MockMUC.h95
-rw-r--r--Swiften/SConscript2
9 files changed, 733 insertions, 488 deletions
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"),