summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2014-10-07 11:58:34 (GMT)
committerSwift Review <review@swift.im>2014-10-17 18:18:54 (GMT)
commit1722d220533a78e8b2acbcd571631960656e78f8 (patch)
tree4da1095da990626652774962bab8a1c8db9ff2ea
parent0e7940bf2ede0147ee1eafced53bc9ad08a4015e (diff)
downloadswift-contrib-1722d220533a78e8b2acbcd571631960656e78f8.zip
swift-contrib-1722d220533a78e8b2acbcd571631960656e78f8.tar.bz2
Implement support for displaying nickname changes.
This implements Swiften API for changing nicknames in MUC and correctly detecting nick name changes. In addition Swift now displays nickname changes as such and not as join/leave of a user. In addition, handling of nickname changes is integrated in ChatsManager and ChatControllers so that they are forwarded to PM chats of MUCs. Test-Information: Added unit tests for change of own nickname and nickname changes of others. Tested correct detection of nickname changes in a MUC with a Psi user changing its nickname and Swift correctly detecting and displaying it. Change-Id: I3287ba6ceeccd3be5cfb591acd6f88bffc9a43b2
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp24
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h1
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp45
-rw-r--r--Swift/Controllers/Chat/MUCController.h3
-rw-r--r--Swiften/JID/JID.h8
-rw-r--r--Swiften/MUC/MUC.h4
-rw-r--r--Swiften/MUC/MUCImpl.cpp41
-rw-r--r--Swiften/MUC/MUCImpl.h6
-rw-r--r--Swiften/MUC/UnitTest/MUCTest.cpp74
-rw-r--r--Swiften/MUC/UnitTest/MockMUC.h1
10 files changed, 194 insertions, 13 deletions
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index c180024..32e25a2 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -792,83 +792,107 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti
}
mucBookmarkManager_->addBookmark(bookmark);
}
std::map<JID, MUCController*>::iterator it = mucControllers_.find(mucJID);
if (it != mucControllers_.end()) {
it->second->rejoin();
} else {
std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_);
muc = mucManager->createMUC(mucJID);
if (createAsReservedIfNew) {
muc->setCreateAsReservedIfNew();
}
if (isImpromptu) {
muc->setCreateAsReservedIfNew();
}
MUCController* controller = NULL;
SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL;
if (reuseChatwindow) {
chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
}
boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */
controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_);
if (chatWindowFactoryAdapter) {
/* The adapters are only passed to chat windows, which are deleted in their
* controllers' dtor, which are deleted in ChatManager's dtor. The adapters
* are also deleted there.*/
chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter;
}
mucControllers_[mucJID] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true));
+ controller->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2));
controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true));
controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
handleChatActivity(mucJID.toBare(), "", true);
}
mucControllers_[mucJID]->showChatWindow();
return muc;
}
void ChatsManager::handleSearchMUCRequest() {
mucSearchController_->openSearchWindow();
}
+void ChatsManager::handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname) {
+ JID oldMUCChatJID = mucController->getToJID().withResource(oldNickname);
+ JID newMUCChatJID = mucController->getToJID().withResource(newNickname);
+
+ SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname << std::endl;
+
+ // get current chat controller
+ ChatController *chatController = getChatControllerIfExists(oldMUCChatJID);
+ if (chatController) {
+ // adjust chat controller
+ chatController->setToJID(newMUCChatJID);
+ nickResolver_->onNickChanged(newMUCChatJID, oldNickname);
+ chatControllers_.erase(oldMUCChatJID);
+ chatControllers_[newMUCChatJID] = chatController;
+
+ chatController->onActivity.disconnect(boost::bind(&ChatsManager::handleChatActivity, this, oldMUCChatJID, _1, false));
+ chatController->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, newMUCChatJID, _1, false));
+ /*for (std::list<ChatListWindow::Chat>::iterator i = recentChats_.begin(); i != recentChats_.end(); i++) {
+ if (i->jid ==
+ }*/
+ }
+}
+
void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
JID jid = message->getFrom();
boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
bool isInvite = !!message->getPayload<MUCInvitationPayload>();
bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite());
if (isMediatedInvite) {
jid = (*message->getPayload<MUCUserPayload>()->getInvite()).from;
}
if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !isMediatedInvite && !message->hasSubject()) {
return;
}
// Try to deliver it to a MUC
if (message->getType() == Message::Groupchat || message->getType() == Message::Error /*|| (isInvite && message->getType() == Message::Normal)*/) {
std::map<JID, MUCController*>::iterator i = mucControllers_.find(jid.toBare());
if (i != mucControllers_.end()) {
i->second->handleIncomingMessage(event);
return;
}
else if (message->getType() == Message::Groupchat) {
//FIXME: Error handling - groupchat messages from an unknown muc.
return;
}
}
// check for impromptu invite to potentially auto-accept
MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
if (invite->getIsContinuation()) {
// check for existing chat controller for the from JID
ChatController* controller = getChatControllerIfExists(jid);
if (controller) {
ChatWindow* window = controller->detachChatWindow();
chatControllers_.erase(jid);
delete controller;
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 575b3cb..82daf67 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -69,70 +69,71 @@ namespace Swift {
ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController, VCardManager* vcardManager);
virtual ~ChatsManager();
void setAvatarManager(AvatarManager* avatarManager);
void setOnline(bool enabled);
void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
void handleIncomingMessage(boost::shared_ptr<Message> message);
std::vector<ChatListWindow::Chat> getRecentChats() const;
virtual std::vector<Contact::ref> getContacts(bool withMUCNicks);
boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered;
private:
class SingleChatWindowFactoryAdapter : public ChatWindowFactory {
public:
SingleChatWindowFactoryAdapter(ChatWindow* chatWindow);
virtual ~SingleChatWindowFactoryAdapter();
virtual ChatWindow* createChatWindow(const JID &, UIEventStream*);
private:
ChatWindow* chatWindow_;
};
private:
ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage);
void handleChatRequest(const std::string& contact);
void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>());
MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = 0);
void handleSearchMUCRequest();
void handleMUCSelectedAfterSearch(const JID&);
void rebindControllerJID(const JID& from, const JID& to);
void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
void handleUIEvent(boost::shared_ptr<UIEvent> event);
void handleMUCBookmarkAdded(const MUCBookmark& bookmark);
void handleMUCBookmarkRemoved(const MUCBookmark& bookmark);
void handleUserLeftMUC(MUCController* mucController);
+ void handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname);
void handleBookmarksReady();
void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC);
void handleChatClosed(const JID& jid);
void handleNewFileTransferController(FileTransferController*);
void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf);
void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state);
boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat);
void cleanupPrivateMessageRecents();
void appendRecent(const ChatListWindow::Chat& chat);
void prependRecent(const ChatListWindow::Chat& chat);
void setupBookmarks();
void loadRecents();
void saveRecents();
void handleChatMadeRecent();
void handleMUCBookmarkActivated(const MUCBookmark&);
void handleRecentActivated(const ChatListWindow::Chat&);
void handleUnreadCountChanged(ChatControllerBase* controller);
void handleAvatarChanged(const JID& jid);
void handleClearRecentsRequested();
void handleJIDAddedToRoster(const JID&);
void handleJIDRemovedFromRoster(const JID&);
void handleJIDUpdatedInRoster(const JID&);
void handleRosterCleared();
void handleSettingChanged(const std::string& settingPath);
void markAllRecentsOffline();
void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason);
void handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info);
void handleLocalServiceWalkFinished();
void updatePresenceReceivingStateOnChatController(const JID&);
ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const;
ChatController* getChatControllerOrFindAnother(const JID &contact);
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index b467227..fe90c60 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -71,70 +71,71 @@ MUCController::MUCController (
HistoryController* historyController,
MUCRegistry* mucRegistry,
HighlightManager* highlightManager,
boost::shared_ptr<ChatMessageParser> chatMessageParser,
bool isImpromptu,
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
VCardManager* vcardManager) :
ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
shouldJoinOnReconnect_ = true;
doneGettingHistory_ = false;
events_ = uiEventStream;
xmppRoster_ = roster;
roster_ = new Roster(false, true);
rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource);
completer_ = new TabComplete();
chatWindow_->setRosterModel(roster_);
chatWindow_->setTabComplete(completer_);
chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1));
chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2));
chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1));
chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this));
chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1));
chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this));
chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this));
chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1));
chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this));
chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1));
muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
+ muc_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2));
muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3));
muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2));
muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1));
muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode);
highlighter_->setNick(nick_);
if (timerFactory) {
loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
loginCheckTimer_->start();
}
if (isImpromptu) {
muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this));
chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC);
} else {
muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3));
chatWindow_->convertToMUC(ChatWindow::StandardMUC);
chatWindow_->setName(muc->getJID().getNode());
}
setOnline(true);
if (avatarManager_ != NULL) {
avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
}
handleBareJIDCapsChanged(muc->getJID());
eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1));
}
MUCController::~MUCController() {
eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1));
chatWindow_->setRosterModel(NULL);
delete rosterVCardProvider_;
delete roster_;
@@ -451,71 +452,71 @@ void MUCController::setAvailableRoomActions(const MUCOccupant::Affiliation& affi
}
if (role <= MUCOccupant::Visitor) {
actions.push_back(ChatWindow::Invite);
}
chatWindow_->setAvailableRoomActions(actions);
}
void MUCController::clearPresenceQueue() {
lastWasPresence_ = false;
joinParts_.clear();
}
std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) {
switch (role) {
case MUCOccupant::Moderator: return QT_TRANSLATE_NOOP("", "moderator");
case MUCOccupant::Participant: return QT_TRANSLATE_NOOP("", "participant");
case MUCOccupant::Visitor: return QT_TRANSLATE_NOOP("", "visitor");
case MUCOccupant::NoRole: return "";
}
assert(false);
return "";
}
std::string MUCController::roleToSortName(MUCOccupant::Role role) {
switch (role) {
case MUCOccupant::Moderator: return "1";
case MUCOccupant::Participant: return "2";
case MUCOccupant::Visitor: return "3";
case MUCOccupant::NoRole: return "4";
}
assert(false);
return "5";
}
JID MUCController::nickToJID(const std::string& nick) {
- return JID(toJID_.getNode(), toJID_.getDomain(), nick);
+ return muc_->getJID().withResource(nick);
}
bool MUCController::messageTargetsMe(boost::shared_ptr<Message> message) {
std::string stringRegexp(".*\\b" + boost::to_lower_copy(nick_) + "\\b.*");
boost::regex myRegexp(stringRegexp);
return boost::regex_match(boost::to_lower_copy(message->getBody()), myRegexp);
}
void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
if (messageEvent->getStanza()->getType() == Message::Groupchat) {
lastActivity_ = boost::posix_time::microsec_clock::universal_time();
}
clearPresenceQueue();
boost::shared_ptr<Message> message = messageEvent->getStanza();
if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) {
chatWindow_->flash();
}
else {
messageEvent->setTargetsMe(false);
}
if (messageEvent->isReadable() && isImpromptu_) {
chatWindow_->flash(); /* behave like a regular char*/
}
if (joined_) {
std::string nick = message->getFrom().getResource();
if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) {
completer_->addWord(nick);
}
}
/*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/
receivedActivity();
joined_ = true;
if (message->hasSubject() && message->getBody().empty()) {
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);;
@@ -659,70 +660,108 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving
switch (type) {
case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been kicked out of the room"); break;
case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been banned from the room"); break;
case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You are no longer a member of the room and have been removed"); break;
case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "The room has been destroyed"); break;
case MUC::Disconnect:
case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the room");
}
}
if (!reason.empty()) {
partMessage += " (" + reason + ")";
}
partMessage += ".";
if (occupant.getNick() != nick_) {
if (shouldUpdateJoinParts()) {
updateJoinParts();
} else {
addPresenceMessage(partMessage);
}
roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick()));
} else {
addPresenceMessage(partMessage);
parting_ = true;
processUserPart();
}
if (clearAfter) {
clearPresenceQueue();
}
if (isImpromptu_) {
setImpromptuWindowTitle();
}
}
+void MUCController::handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname) {
+ addPresenceMessage(generateNicknameChangeString(oldNickname, newNickname));
+ JID oldJID = muc_->getJID().withResource(oldNickname);
+ JID newJID = muc_->getJID().withResource(newNickname);
+
+ // adjust occupants
+ currentOccupants_.erase(oldNickname);
+ currentOccupants_.insert(newNickname);
+
+ // adjust completer
+ completer_->removeWord(oldNickname);
+ completer_->addWord(newNickname);
+
+ // update contact
+ roster_->removeContact(oldJID);
+ MUCOccupant occupant = muc_->getOccupant(newNickname);
+
+ JID realJID;
+ if (occupant.getRealJID()) {
+ realJID = occupant.getRealJID().get();
+ }
+ MUCOccupant::Role role = MUCOccupant::Participant;
+ MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation;
+ if (!isImpromptu_) {
+ role = occupant.getRole();
+ affiliation = occupant.getAffiliation();
+ }
+ std::string groupName(roleToGroupName(role));
+ roster_->addContact(newJID, realJID, newNickname, groupName, avatarManager_->getAvatarPath(newJID));
+ roster_->applyOnItems(SetMUC(newJID, role, affiliation));
+ if (avatarManager_ != NULL) {
+ handleAvatarChanged(newJID);
+ }
+
+ clearPresenceQueue();
+ onUserNicknameChanged(oldNickname, newNickname);
+}
+
void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) {
receivedActivity();
roster_->applyOnItems(SetPresence(presence, JID::WithResource));
}
bool MUCController::isIncomingMessageFromMe(boost::shared_ptr<Message> message) {
JID from = message->getFrom();
return nick_ == from.getResource();
}
std::string MUCController::senderDisplayNameFromMessage(const JID& from) {
return from.getResource();
}
void MUCController::preSendMessageRequest(boost::shared_ptr<Message> message) {
message->setType(Swift::Message::Groupchat);
}
boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boost::shared_ptr<Message> message) const {
return message->getTimestampFrom(toJID_);
}
void MUCController::updateJoinParts() {
chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())), ChatWindow::UpdateTimestamp);
}
void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) {
std::vector<NickJoinPart>::iterator it = joinParts.begin();
bool matched = false;
for (; it != joinParts.end(); ++it) {
if ((*it).nick == newEvent.nick) {
matched = true;
JoinPart type = (*it).type;
switch (newEvent.type) {
case Join: type = (type == Part) ? PartThenJoin : Join; break;
@@ -786,70 +825,74 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart
break;
case JoinThenPart:
if (sorted[i].size() > 1) {
eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room"));
}
else {
eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room"));
}
break;
case PartThenJoin:
if (sorted[i].size() > 1) {
eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room"));
}
else {
eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room"));
}
break;
}
populatedEvents.push_back(static_cast<JoinPart>(i));
eventStrings[i] = str(boost::format(eventString) % names);
}
}
for (size_t i = 0; i < populatedEvents.size(); i++) {
if (i > 0) {
if (i < populatedEvents.size() - 1) {
result += ", ";
} else {
result += QT_TRANSLATE_NOOP("", " and ");
}
}
result += eventStrings[populatedEvents[i]];
}
return result;
}
+std::string MUCController::generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname) {
+ return str(boost::format(QT_TRANSLATE_NOOP("", "%1% is now known as %2%.")) % oldNickname % newNickname);
+}
+
void MUCController::handleChangeSubjectRequest(const std::string& subject) {
muc_->changeSubject(subject);
}
void MUCController::handleBookmarkRequest() {
const JID jid = muc_->getJID();
MUCBookmark bookmark(jid, jid.toBare().toString());
bookmark.setPassword(password_);
bookmark.setNick(nick_);
chatWindow_->showBookmarkWindow(bookmark);
}
void MUCController::handleConfigureRequest(Form::ref form) {
if (form) {
muc_->configureRoom(form);
}
else {
muc_->requestConfigurationForm();
}
}
void MUCController::handleConfigurationFailed(ErrorPayload::ref error) {
std::string errorMessage = getErrorMessage(error);
errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage);
chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
}
void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) {
std::string errorMessage = getErrorMessage(error);
errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage);
chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
}
void MUCController::configureAsImpromptuRoom(Form::ref form) {
muc_->configureRoom(buildImpromptuRoomConfiguration(form));
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index b5b5837..2d732a1 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -23,101 +23,104 @@
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <Swift/Controllers/Roster/RosterItem.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class StanzaChannel;
class IQRouter;
class ChatWindowFactory;
class Roster;
class AvatarManager;
class UIEventStream;
class TimerFactory;
class TabComplete;
class XMPPRoster;
class HighlightManager;
class UIEvent;
class VCardManager;
class RosterVCardProvider;
enum JoinPart {Join, Part, JoinThenPart, PartThenJoin};
struct NickJoinPart {
NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {}
std::string nick;
JoinPart type;
};
class MUCController : public ChatControllerBase {
public:
MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);
virtual ~MUCController();
boost::signal<void ()> onUserLeft;
boost::signal<void ()> onUserJoined;
boost::signal<void ()> onImpromptuConfigCompleted;
+ boost::signal<void (const std::string&, const std::string& )> onUserNicknameChanged;
virtual void setOnline(bool online);
void rejoin();
static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu);
static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts);
+ static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname);
bool isJoined();
const std::string& getNick();
const boost::optional<std::string> getPassword() const;
bool isImpromptu() const;
std::map<std::string, JID> getParticipantJIDs() const;
void sendInvites(const std::vector<JID>& jids, const std::string& reason) const;
protected:
void preSendMessageRequest(boost::shared_ptr<Message> message);
bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
std::string senderDisplayNameFromMessage(const JID& from);
boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message> message) const;
void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>);
void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&);
void cancelReplaces();
void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming);
private:
void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role);
void clearPresenceQueue();
void addPresenceMessage(const std::string& message);
void handleWindowOccupantSelectionChanged(ContactRosterItem* item);
void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item);
void handleWindowClosed();
void handleAvatarChanged(const JID& jid);
void handleOccupantJoined(const MUCOccupant& occupant);
+ void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname);
void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason);
void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence);
void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole);
void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation);
void handleJoinComplete(const std::string& nick);
void handleJoinFailed(boost::shared_ptr<ErrorPayload> error);
void handleJoinTimeoutTick();
void handleChangeSubjectRequest(const std::string&);
void handleBookmarkRequest();
std::string roleToGroupName(MUCOccupant::Role role);
std::string roleToSortName(MUCOccupant::Role role);
JID nickToJID(const std::string& nick);
std::string roleToFriendlyName(MUCOccupant::Role role);
void receivedActivity();
bool messageTargetsMe(boost::shared_ptr<Message> message);
void updateJoinParts();
bool shouldUpdateJoinParts();
void dayTicked() {clearPresenceQueue();}
void processUserPart();
void handleBareJIDCapsChanged(const JID& jid);
void handleConfigureRequest(Form::ref);
void handleConfigurationFailed(ErrorPayload::ref);
void handleConfigurationFormReceived(Form::ref);
void handleDestroyRoomRequest();
void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite);
void handleConfigurationCancelled();
void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role);
void handleGetAffiliationsRequest();
void handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids);
void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes);
void handleInviteToMUCWindowDismissed();
void handleInviteToMUCWindowCompleted();
void handleUIEvent(boost::shared_ptr<UIEvent> event);
void addRecentLogs();
void checkDuplicates(boost::shared_ptr<Message> newMessage);
diff --git a/Swiften/JID/JID.h b/Swiften/JID/JID.h
index 126a7b1..aefd3df 100644
--- a/Swiften/JID/JID.h
+++ b/Swiften/JID/JID.h
@@ -102,70 +102,78 @@ namespace Swift {
*/
const std::string& getResource() const {
return resource_;
}
/**
* Is a bare JID, i.e. has no resource part.
*/
bool isBare() const {
return !hasResource_;
}
/**
* Returns the given node, escaped according to XEP-0106.
* The resulting node is a valid node for a JID, whereas the input value can contain characters
* that are not allowed.
*/
static std::string getEscapedNode(const std::string& node);
/**
* Returns the node of the current JID, unescaped according to XEP-0106.
*/
std::string getUnescapedNode() const;
/**
* Get the JID without a resource.
* @return Invalid if the original is invalid.
*/
JID toBare() const {
JID result(*this);
result.hasResource_ = false;
result.resource_ = "";
return result;
}
+ /**
+ * Get the full JID with the supplied resource.
+ */
+ JID withResource(const std::string& resource) const {
+ JID result(this->getNode(), this->getDomain(), resource);
+ return result;
+ }
+
std::string toString() const;
bool equals(const JID& o, CompareType compareType) const {
return compare(o, compareType) == 0;
}
int compare(const JID& o, CompareType compareType) const;
operator std::string() const {
return toString();
}
bool operator<(const Swift::JID& b) const {
return compare(b, Swift::JID::WithResource) < 0;
}
SWIFTEN_API friend std::ostream& operator<<(std::ostream& os, const Swift::JID& j);
friend bool operator==(const Swift::JID& a, const Swift::JID& b) {
return a.compare(b, Swift::JID::WithResource) == 0;
}
friend bool operator!=(const Swift::JID& a, const Swift::JID& b) {
return a.compare(b, Swift::JID::WithResource) != 0;
}
/**
* Returns an empty optional if the JID is invalid, and an
* optional with a value if the JID is valid.
*/
static boost::optional<JID> parse(const std::string&);
/**
* If Swiften was compiled with SWIFTEN_JID_NO_DEFAULT_IDN_CONVERTER (not default), use this method at
* the beginning of the program to set an IDN converter to use for JID IDN conversions.
diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h
index 0dcccd9..8815489 100644
--- a/Swiften/MUC/MUC.h
+++ b/Swiften/MUC/MUC.h
@@ -1,98 +1,100 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#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 MUC {
public:
typedef boost::shared_ptr<MUC> ref;
enum JoinResult { JoinSucceeded, JoinFailed };
enum LeavingType { LeavePart, LeaveKick, LeaveBan, LeaveDestroy, LeaveNotMember, Disconnect };
public:
virtual ~MUC();
/**
* Returns the (bare) JID of the MUC.
*/
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.
*/
virtual bool isUnlocked() const = 0;
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 changeNickname(const std::string& newNickname) = 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 */
virtual void handleUserLeft(LeavingType) = 0;
/** Get occupant information*/
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 */
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;
boost::signal<void (ErrorPayload::ref, const JID&, MUCOccupant::Role)> onRoleChangeFailed;
boost::signal<void (ErrorPayload::ref, const JID&, MUCOccupant::Affiliation)> onAffiliationChangeFailed;
boost::signal<void (ErrorPayload::ref)> onConfigurationFailed;
boost::signal<void (ErrorPayload::ref)> onAffiliationListFailed;
boost::signal<void (Presence::ref)> onOccupantPresenceChange;
boost::signal<void (const std::string&, const MUCOccupant& /*now*/, const MUCOccupant::Role& /*old*/)> onOccupantRoleChanged;
boost::signal<void (const std::string&, const MUCOccupant::Affiliation& /*new*/, const MUCOccupant::Affiliation& /*old*/)> onOccupantAffiliationChanged;
boost::signal<void (const MUCOccupant&)> onOccupantJoined;
+ boost::signal<void (const std::string& /*oldNickname*/, const std::string& /*newNickname*/ )> onOccupantNicknameChanged;
boost::signal<void (const MUCOccupant&, LeavingType, const std::string& /*reason*/)> onOccupantLeft;
boost::signal<void (Form::ref)> onConfigurationFormReceived;
boost::signal<void (MUCOccupant::Affiliation, const std::vector<JID>&)> onAffiliationListReceived;
boost::signal<void ()> onUnlocked;
/* boost::signal<void (const MUCInfo&)> onInfoResult; */
/* boost::signal<void (const blah&)> onItemsResult; */
};
}
diff --git a/Swiften/MUC/MUCImpl.cpp b/Swiften/MUC/MUCImpl.cpp
index a1854e3..ab5faf4 100644
--- a/Swiften/MUC/MUCImpl.cpp
+++ b/Swiften/MUC/MUCImpl.cpp
@@ -1,37 +1,37 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * Copyright (c) 2010-2014 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()
{
@@ -58,162 +58,185 @@ void MUCImpl::setPassword(const boost::optional<std::string>& 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::changeNickname(const std::string& newNickname) {
+ Presence::ref changeNicknamePresence = boost::make_shared<Presence>();
+ changeNicknamePresence->setTo(ownMUCJID.toBare().toString() + std::string("/") + newNickname);
+ presenceSender->sendPresence(changeNicknamePresence);
+}
+
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;
+ boost::optional<std::string> newNickname;
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;
}
+ else if (status.code == 303) {
+ if (mucPayload->getItems().size() == 1) {
+ newNickname = mucPayload->getItems()[0].nick;
+ }
+ }
}
}
-
- if (presence->getFrom() == ownMUCJID) {
- handleUserLeft(type);
- return;
- }
- else {
+ if (newNickname) {
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, "");
+ occupant.setNick(newNickname.get());
+ occupants.insert(std::make_pair(newNickname.get(), occupant));
+ onOccupantNicknameChanged(nick, newNickname.get());
+ }
+ }
+ else {
+ 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);
diff --git a/Swiften/MUC/MUCImpl.h b/Swiften/MUC/MUCImpl.h
index 846ddcf..8eabb80 100644
--- a/Swiften/MUC/MUCImpl.h
+++ b/Swiften/MUC/MUCImpl.h
@@ -26,70 +26,76 @@
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;
+
+ /**
+ * Send a new presence to the MUC indicating a nickname change. Any custom status the user had in the is cleared.
+ * @param newNickname The nickname to change to.
+ */
+ virtual void changeNickname(const std::string& newNickname);
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);
diff --git a/Swiften/MUC/UnitTest/MUCTest.cpp b/Swiften/MUC/UnitTest/MUCTest.cpp
index d1a21b0..773edb6 100644
--- a/Swiften/MUC/UnitTest/MUCTest.cpp
+++ b/Swiften/MUC/UnitTest/MUCTest.cpp
@@ -1,78 +1,80 @@
/*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010-2014 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/bind.hpp>
#include <Swiften/MUC/MUCImpl.h>
#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/Presence/StanzaChannelPresenceSender.h>
#include <Swiften/Presence/DirectedPresenceSender.h>
#include <Swiften/Queries/IQRouter.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Elements/MUCOwnerPayload.h>
#include <Swiften/Elements/VCard.h>
#include <Swiften/Elements/CapsInfo.h>
using namespace Swift;
class MUCTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(MUCTest);
CPPUNIT_TEST(testJoin);
CPPUNIT_TEST(testJoin_ChangePresenceDuringJoinDoesNotSendPresenceBeforeJoinSuccess);
CPPUNIT_TEST(testJoin_ChangePresenceDuringJoinResendsPresenceAfterJoinSuccess);
CPPUNIT_TEST(testCreateInstant);
CPPUNIT_TEST(testReplicateBug);
+ CPPUNIT_TEST(testNicknameChange);
/*CPPUNIT_TEST(testJoin_Success);
CPPUNIT_TEST(testJoin_Fail);*/
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
channel = new DummyStanzaChannel();
router = new IQRouter(channel);
mucRegistry = new MUCRegistry();
stanzaChannelPresenceSender = new StanzaChannelPresenceSender(channel);
presenceSender = new DirectedPresenceSender(stanzaChannelPresenceSender);
+ nickChanges = 0;
}
void tearDown() {
delete presenceSender;
delete stanzaChannelPresenceSender;
delete mucRegistry;
delete router;
delete channel;
}
void testJoin() {
MUC::ref testling = createMUC(JID("foo@bar.com"));
testling->joinAs("Alice");
CPPUNIT_ASSERT(mucRegistry->isMUC(JID("foo@bar.com")));
Presence::ref p = channel->getStanzaAtIndex<Presence>(0);
CPPUNIT_ASSERT(p);
CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/Alice"), p->getTo());
}
void testJoin_ChangePresenceDuringJoinDoesNotSendPresenceBeforeJoinSuccess() {
MUC::ref testling = createMUC(JID("foo@bar.com"));
testling->joinAs("Alice");
presenceSender->sendPresence(Presence::create("Test"));
CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(channel->sentStanzas.size()));
}
void testJoin_ChangePresenceDuringJoinResendsPresenceAfterJoinSuccess() {
MUC::ref testling = createMUC(JID("foo@bar.com"));
testling->joinAs("Alice");
presenceSender->sendPresence(Presence::create("Test"));
receivePresence(JID("foo@bar.com/Rabbit"), "Here");
@@ -109,85 +111,153 @@ class MUCTest : public CppUnit::TestFixture {
Presence::ref initialPresence = boost::make_shared<Presence>();
initialPresence->setStatus("");
VCard::ref vcard = boost::make_shared<VCard>();
vcard->setPhoto(createByteArray("15c30080ae98ec48be94bf0e191d43edd06e500a"));
initialPresence->addPayload(vcard);
CapsInfo::ref caps = boost::make_shared<CapsInfo>();
caps->setNode("http://swift.im");
caps->setVersion("p2UP0DrcVgKM6jJqYN/B92DKK0o=");
initialPresence->addPayload(caps);
channel->sendPresence(initialPresence);
MUC::ref testling = createMUC(JID("test@rooms.swift.im"));
testling->joinAs("Test");
Presence::ref serverRespondsLocked = boost::make_shared<Presence>();
serverRespondsLocked->setFrom(JID("test@rooms.swift.im/Test"));
serverRespondsLocked->setTo(JID("test@swift.im/6913d576d55f0b67"));
serverRespondsLocked->addPayload(vcard);
serverRespondsLocked->addPayload(caps);
serverRespondsLocked->setStatus("");
MUCUserPayload::ref mucPayload(new MUCUserPayload());
MUCItem myItem;
myItem.affiliation = MUCOccupant::Owner;
myItem.role = MUCOccupant::Moderator;
mucPayload->addItem(myItem);
mucPayload->addStatusCode(MUCUserPayload::StatusCode(201));
serverRespondsLocked->addPayload(mucPayload);
channel->onPresenceReceived(serverRespondsLocked);
CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(channel->sentStanzas.size()));
IQ::ref iq = channel->getStanzaAtIndex<IQ>(2);
CPPUNIT_ASSERT(iq);
CPPUNIT_ASSERT(iq->getPayload<MUCOwnerPayload>());
CPPUNIT_ASSERT(iq->getPayload<MUCOwnerPayload>()->getForm());
CPPUNIT_ASSERT_EQUAL(Form::SubmitType, iq->getPayload<MUCOwnerPayload>()->getForm()->getType());
}
+ void testNicknameChange() {
+ MUC::ref testling = createMUC(JID("foo@bar.com"));
+ // Join as Rabbit
+ testling->joinAs("Rabbit");
+
+ // Rabbit joins
+ Presence::ref rabbitJoins = boost::make_shared<Presence>();
+ rabbitJoins->setTo("test@swift.im/6913d576d55f0b67");
+ rabbitJoins->setFrom(testling->getJID().toString() + "/Rabbit");
+ channel->onPresenceReceived(rabbitJoins);
+ CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Rabbit"));
+
+ // Alice joins
+ Presence::ref aliceJoins = boost::make_shared<Presence>();
+ aliceJoins->setTo("test@swift.im/6913d576d55f0b67");
+ aliceJoins->setFrom(testling->getJID().toString() + "/Alice");
+ channel->onPresenceReceived(aliceJoins);
+ CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Alice"));
+
+ // Change nick to Dodo
+ testling->changeNickname("Dodo");
+ Presence::ref stanza = channel->getStanzaAtIndex<Presence>(1);
+ CPPUNIT_ASSERT(stanza);
+ CPPUNIT_ASSERT_EQUAL(std::string("Dodo"), stanza->getTo().getResource());
+
+ // Alice changes nick to Alice2
+ stanza = boost::make_shared<Presence>();
+ stanza->setFrom(JID("foo@bar.com/Alice"));
+ stanza->setTo(JID(router->getJID()));
+ stanza->setType(Presence::Unavailable);
+ MUCUserPayload::ref mucPayload(new MUCUserPayload());
+ MUCItem myItem;
+ myItem.affiliation = MUCOccupant::Member;
+ myItem.nick = "Alice2";
+ myItem.role = MUCOccupant::Participant;
+ mucPayload->addItem(myItem);
+ mucPayload->addStatusCode(303);
+ stanza->addPayload(mucPayload);
+ channel->onPresenceReceived(stanza);
+ CPPUNIT_ASSERT_EQUAL(1, nickChanges);
+ CPPUNIT_ASSERT_EQUAL(false, testling->hasOccupant("Alice"));
+ CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Alice2"));
+
+ // We (Rabbit) change nick to Robot
+ stanza = boost::make_shared<Presence>();
+ stanza->setFrom(JID("foo@bar.com/Rabbit"));
+ stanza->setTo(JID(router->getJID()));
+ stanza->setType(Presence::Unavailable);
+ mucPayload = MUCUserPayload::ref(new MUCUserPayload());
+ myItem.affiliation = MUCOccupant::Member;
+ myItem.nick = "Robot";
+ myItem.role = MUCOccupant::Participant;
+ mucPayload->addItem(myItem);
+ mucPayload->addStatusCode(303);
+ stanza->addPayload(mucPayload);
+ channel->onPresenceReceived(stanza);
+ CPPUNIT_ASSERT_EQUAL(2, nickChanges);
+ CPPUNIT_ASSERT_EQUAL(false, testling->hasOccupant("Rabbit"));
+ CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Robot"));
+ }
+
/*void testJoin_Success() {
MUC::ref testling = createMUC(JID("foo@bar.com"));
testling->onJoinFinished.connect(boost::bind(&MUCTest::handleJoinFinished, this, _1, _2));
testling->joinAs("Alice");
receivePresence(JID("foo@bar.com/Rabbit"), "Here");
CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(joinResults.size()));
CPPUNIT_ASSERT_EQUAL(std::string("Alice"), joinResults[0].nick);
CPPUNIT_ASSERT(joinResults[0].error);
}
void testJoin_Fail() {
//CPPUNIT_ASSERT(!mucRegistry->isMUC(JID("foo@bar.com")));
}*/
private:
MUC::ref createMUC(const JID& jid) {
- return boost::make_shared<MUCImpl>(channel, router, presenceSender, jid, mucRegistry);
+ MUC::ref muc = boost::make_shared<MUCImpl>(channel, router, presenceSender, jid, mucRegistry);
+ muc->onOccupantNicknameChanged.connect(boost::bind(&MUCTest::handleOccupantNicknameChanged, this, _1, _2));
+ return muc;
}
void handleJoinFinished(const std::string& nick, ErrorPayload::ref error) {
JoinResult r;
r.nick = nick;
r.error = error;
joinResults.push_back(r);
}
void receivePresence(const JID& jid, const std::string& status) {
Presence::ref p = Presence::create(status);
p->setFrom(jid);
//MUCUserPayload::ref mucUserPayload = boost::make_shared<MUCUserPayload>();
//mucUserPayload->addItem(item);
//p->addPayload(mucUserPayload);
channel->onPresenceReceived(p);
}
+ void handleOccupantNicknameChanged(const std::string&, const std::string&) {
+ nickChanges++;
+ }
+
private:
DummyStanzaChannel* channel;
IQRouter* router;
MUCRegistry* mucRegistry;
StanzaChannelPresenceSender* stanzaChannelPresenceSender;
DirectedPresenceSender* presenceSender;
struct JoinResult {
std::string nick;
ErrorPayload::ref error;
};
std::vector<JoinResult> joinResults;
+ int nickChanges;
};
CPPUNIT_TEST_SUITE_REGISTRATION(MUCTest);
diff --git a/Swiften/MUC/UnitTest/MockMUC.h b/Swiften/MUC/UnitTest/MockMUC.h
index 78c2fb5..8673a90 100644
--- a/Swiften/MUC/UnitTest/MockMUC.h
+++ b/Swiften/MUC/UnitTest/MockMUC.h
@@ -26,70 +26,71 @@ 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 changeNickname(const std::string&) { }
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_;
};
}