summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swift/Controllers/Chat')
-rw-r--r--Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h70
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp860
-rw-r--r--Swift/Controllers/Chat/ChatController.h206
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp604
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h247
-rw-r--r--Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h82
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.cpp344
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.h41
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp1787
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h346
-rw-r--r--Swift/Controllers/Chat/Chattables.cpp43
-rw-r--r--Swift/Controllers/Chat/Chattables.h47
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp1856
-rw-r--r--Swift/Controllers/Chat/MUCController.h308
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.cpp252
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.h224
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp162
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp292
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp344
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp2339
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp140
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp1301
-rw-r--r--Swift/Controllers/Chat/UnitTest/MockChatListWindow.h35
-rw-r--r--Swift/Controllers/Chat/UserSearchController.cpp537
-rw-r--r--Swift/Controllers/Chat/UserSearchController.h148
25 files changed, 8364 insertions, 4251 deletions
diff --git a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h
index 0f85a8a..646612b 100644
--- a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h
+++ b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h
@@ -4,39 +4,51 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014-2016 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
#pragma once
-#include <Swiften/Roster/XMPPRoster.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
-#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swiften/Roster/XMPPRoster.h>
+
#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
namespace Swift {
- class AutoAcceptMUCInviteDecider {
- public:
- AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) {
- }
-
- bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) {
- if (!invite->getIsImpromptu() && !invite->getIsContinuation()) {
- return false;
- }
-
- std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE);
- if (auto_accept_mode == "no") {
- return false;
- } else if (auto_accept_mode == "presence") {
- return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both;
- } else if (auto_accept_mode == "domain") {
- return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_;
- } else {
- assert(false);
- }
- }
-
- private:
- JID domain_;
- XMPPRoster* roster_;
- SettingsProvider* settings_;
- };
+ class AutoAcceptMUCInviteDecider {
+ public:
+ AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) {
+ }
+
+ bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) {
+ if (!invite->getIsImpromptu()) {
+ return false; /* always ask the user for normal MUC invites */
+ }
+
+ if (invite->getIsContinuation()) {
+ return true;
+ }
+
+ std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE);
+ if (auto_accept_mode == "no") {
+ return false;
+ } else if (auto_accept_mode == "presence") {
+ return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both;
+ } else if (auto_accept_mode == "domain") {
+ return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_;
+ } else {
+ assert(false);
+ return false;
+ }
+ }
+
+ private:
+ JID domain_;
+ XMPPRoster* roster_;
+ SettingsProvider* settings_;
+ };
}
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 13fd22b..debd83f 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -1,499 +1,607 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2019 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatController.h>
+#include <memory>
+
#include <boost/bind.hpp>
-#include <boost/smart_ptr/make_shared.hpp>
-#include <stdio.h>
-#include <Swiften/Base/format.h>
-#include <Swiften/Base/Algorithm.h>
#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Base/Log.h>
+#include <Swiften/Base/Path.h>
+#include <Swiften/Base/format.h>
#include <Swiften/Chat/ChatStateNotifier.h>
#include <Swiften/Chat/ChatStateTracker.h>
-#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/EntityCapsProvider.h>
-#include <Swiften/Base/foreach.h>
-#include <Swiften/Base/DateTime.h>
+#include <Swiften/Disco/FeatureOracle.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/Elements/Idle.h>
-#include <Swiften/Base/Log.h>
-#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/FileTransfer/FileTransferManager.h>
+#include <Swiften/Network/TimerFactory.h>
-#include <Swift/Controllers/Intl.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/FileTransfer/FileTransferController.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/StatusUtil.h>
-#include <Swift/Controllers/UIEvents/UIEventStream.h>
-#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swift/Controllers/Translator.h>
#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>
#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
-#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
-#include <Swift/Controllers/SettingConstants.h>
-#include <Swift/Controllers/Highlighter.h>
-#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h>
namespace Swift {
-
+
/**
* The controller does not gain ownership of the stanzaChannel, nor the factory.
*/
-ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider)
- : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
- isInMUC_ = isInMUC;
- lastWasPresence_ = false;
- chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
- chatStateTracker_ = new ChatStateTracker();
- nickResolver_ = nickResolver;
- presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1));
- chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1));
- stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1));
- nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
- std::string nick = nickResolver_->jidToNick(toJID_);
- chatWindow_->setName(nick);
- std::string startMessage;
- Presence::ref theirPresence;
- if (isInMUC) {
- startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString());
- theirPresence = presenceOracle->getLastPresence(contact);
- } else {
- startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString());
- theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact);
- }
- Idle::ref idle;
- if (theirPresence && (idle = theirPresence->getPayload<Idle>())) {
- startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince()));
- }
- startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None);
- if (theirPresence && !theirPresence->getStatus().empty()) {
- startMessage += " (" + theirPresence->getStatus() + ")";
- }
- lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None;
- chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available);
- startMessage += ".";
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection);
- chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
- chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
- chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2));
- chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2));
- chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
- chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
- chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this));
- chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this));
- chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this));
- chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this));
- chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this));
- chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1));
- handleBareJIDCapsChanged(toJID_);
-
- settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1));
- eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1));
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, SettingsProvider* settings, Chattables& chattables)
+ : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, nickResolver, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider, settings, chattables), userWantsReceipts_(userWantsReceipts), clientBlockListManager_(clientBlockListManager) {
+ isInMUC_ = isInMUC;
+ lastWasPresence_ = false;
+ chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider, timerFactory, 20000);
+ chatStateTracker_ = new ChatStateTracker();
+ nickResolver_ = nickResolver;
+ presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1));
+ chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1));
+ stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1));
+ nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
+ std::string nick = nickResolver_->jidToNick(toJID_);
+ chatWindow_->setName(nick);
+ std::string startMessage;
+ Presence::ref theirPresence;
+ if (isInMUC) {
+ startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString());
+ theirPresence = presenceOracle->getLastPresence(contact);
+ } else {
+ startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString());
+ theirPresence = contact.isBare() ? presenceOracle->getAccountPresence(contact) : presenceOracle->getLastPresence(contact);
+ }
+ Idle::ref idle;
+ if (theirPresence && (idle = theirPresence->getPayload<Idle>())) {
+ startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % Swift::Translator::getInstance()->ptimeToHumanReadableString(idle->getSince()));
+ }
+ startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None);
+ if (theirPresence && !theirPresence->getStatus().empty()) {
+ startMessage += " (" + theirPresence->getStatus() + ")";
+ }
+ lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None;
+ chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available);
+ startMessage += ".";
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection);
+ continuationsBrokenConnection_ = chatWindow_->onContinuationsBroken.connect([this, startMessage]() { chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); });
+ chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
+ chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+ chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2));
+ chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2));
+ chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
+ chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
+ chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this));
+ chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this));
+ chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this));
+ chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this));
+ chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this));
+ chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1));
+ chatWindow_->onClosed.connect(boost::bind(&ChatController::handleWindowClosed, this));
+ ChatController::handleBareJIDCapsChanged(toJID_);
+
+ settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1));
+ eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1));
}
void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) {
- if (jid.toBare() == toJID_.toBare()) {
- chatWindow_->setName(nickResolver_->jidToNick(jid));
- }
+ if (jid.toBare() == toJID_.toBare()) {
+ chatWindow_->setName(nickResolver_->jidToNick(toJID_));
+ }
}
ChatController::~ChatController() {
- eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1));
- settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1));
- nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
- delete chatStateNotifier_;
- delete chatStateTracker_;
+ eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1));
+ settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1));
+ nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
+ delete chatStateNotifier_;
+ delete chatStateTracker_;
}
JID ChatController::getBaseJID() {
- return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID();
+ return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID();
}
void ChatController::cancelReplaces() {
- lastWasPresence_ = false;
+ lastWasPresence_ = false;
}
void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
- DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_);
- if (disco) {
- if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
- chatWindow_->setCorrectionEnabled(ChatWindow::Yes);
- } else {
- chatWindow_->setCorrectionEnabled(ChatWindow::No);
- }
- if (disco->hasFeature(DiscoInfo::MessageDeliveryReceiptsFeature)) {
- contactSupportsReceipts_ = ChatWindow::Yes;
- } else {
- contactSupportsReceipts_ = ChatWindow::No;
- }
- } else {
- SWIFT_LOG(debug) << "No disco info :(" << std::endl;
- chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
- contactSupportsReceipts_ = ChatWindow::Maybe;
- }
- checkForDisplayingDisplayReceiptsAlert();
+ FeatureOracle featureOracle(entityCapsProvider_, presenceOracle_);
+
+ chatWindow_->setCorrectionEnabled(featureOracle.isMessageCorrectionSupported(toJID_));
+ chatWindow_->setFileTransferEnabled(isInMUC_ ? No : featureOracle.isFileTransferSupported(toJID_));
+ contactSupportsReceipts_ = featureOracle.isMessageReceiptsSupported(toJID_);
+
+ checkForDisplayingDisplayReceiptsAlert();
}
void ChatController::setToJID(const JID& jid) {
- chatStateNotifier_->setContact(jid);
- ChatControllerBase::setToJID(jid);
- Presence::ref presence;
- if (isInMUC_) {
- presence = presenceOracle_->getLastPresence(jid);
- } else {
- presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid);
- }
- chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available);
- handleBareJIDCapsChanged(toJID_);
-}
-
-void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
- ChatControllerBase::setAvailableServerFeatures(info);
- if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
- boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
-
- blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this));
- blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingItemAdded, this, _1));
- blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingItemRemoved, this, _1));
-
- handleBlockingStateChanged();
- }
-}
-
-bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
- return false;
-}
-
-void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
- if (messageEvent->isReadable()) {
- chatWindow_->flash();
- lastWasPresence_ = false;
- }
- boost::shared_ptr<Message> message = messageEvent->getStanza();
- JID from = message->getFrom();
- if (!from.equals(toJID_, JID::WithResource)) {
- if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){
- setToJID(from);
- }
- }
- chatStateTracker_->handleMessageReceived(message);
- chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
-
- if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) {
- SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl;
- if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) {
- chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived);
- requestedReceipts_.erase(receipt->getReceivedID());
- }
- } else if (message->getPayload<DeliveryReceiptRequest>()) {
- if (receivingPresenceFromUs_) {
- boost::shared_ptr<Message> receiptMessage = boost::make_shared<Message>();
- receiptMessage->setTo(toJID_);
- receiptMessage->addPayload(boost::make_shared<DeliveryReceipt>(message->getID()));
- stanzaChannel_->sendMessage(receiptMessage);
- }
- }
-}
-
-void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) {
- eventController_->handleIncomingEvent(messageEvent);
- if (!messageEvent->getConcluded()) {
- highlighter_->handleHighlightAction(highlight);
- }
-}
-
-
-void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
- chatStateNotifier_->addChatStateRequest(message);
- if (userWantsReceipts_ && (contactSupportsReceipts_ != ChatWindow::No) && message) {
- message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
- }
+ chatStateNotifier_->setContact(jid);
+ ChatControllerBase::setToJID(jid);
+ Presence::ref presence;
+ if (isInMUC_) {
+ presence = presenceOracle_->getLastPresence(jid);
+ } else {
+ presence = jid.isBare() ? presenceOracle_->getAccountPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid);
+ }
+ chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available);
+ handleBareJIDCapsChanged(toJID_);
+}
+
+void ChatController::setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) {
+ ChatControllerBase::setAvailableServerFeatures(info);
+ if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
+ std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+
+ blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this));
+ blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingStateChanged, this));
+ blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingStateChanged, this));
+
+ handleBlockingStateChanged();
+ }
+}
+
+bool ChatController::isIncomingMessageFromMe(std::shared_ptr<Message>) {
+ return false;
+}
+
+void ChatController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) {
+ if (messageEvent->isReadable()) {
+ chatWindow_->flash();
+ lastWasPresence_ = false;
+ }
+ std::shared_ptr<Message> message = messageEvent->getStanza();
+ JID from = message->getFrom();
+ if (!from.equals(toJID_, JID::WithResource)) {
+ if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){
+ // Bind controller to a full JID if message contains body text or is a typing chat state.
+ ChatState::ref chatState = message->getPayload<ChatState>();
+ if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) {
+ setToJID(from);
+ }
+ }
+ }
+ chatStateTracker_->handleMessageReceived(message);
+ chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>());
+
+ // handle XEP-0184 Message Receipts
+ // incomming receipts
+ if (std::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) {
+ SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID();
+ if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) {
+ chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived);
+ requestedReceipts_.erase(receipt->getReceivedID());
+ }
+ // incomming errors in response to send out receipts
+ } else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) {
+ if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) {
+ chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed);
+ requestedReceipts_.erase(message->getID());
+ }
+ // incoming receipt requests
+ } else if (message->getPayload<DeliveryReceiptRequest>()) {
+ if (receivingPresenceFromUs_) {
+ std::shared_ptr<Message> receiptMessage = std::make_shared<Message>();
+ receiptMessage->setTo(toJID_);
+ receiptMessage->addPayload(std::make_shared<DeliveryReceipt>(message->getID()));
+ stanzaChannel_->sendMessage(receiptMessage);
+ }
+ }
+}
+
+void ChatController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) {
+ highlighter_->handleSystemNotifications(chatMessage, messageEvent);
+ eventController_->handleIncomingEvent(messageEvent);
+ if (!messageEvent->getConcluded()) {
+ highlighter_->handleSoundNotifications(chatMessage);
+ }
+}
+
+
+void ChatController::preSendMessageRequest(std::shared_ptr<Message> message) {
+ chatStateNotifier_->addChatStateRequest(message);
+ if (userWantsReceipts_ && (contactSupportsReceipts_ != No) && message) {
+ message->addPayload(std::make_shared<DeliveryReceiptRequest>());
+ }
}
void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) {
- receivingPresenceFromUs_ = isReceivingPresence;
+ receivingPresenceFromUs_ = isReceivingPresence;
}
void ChatController::handleSettingChanged(const std::string& settingPath) {
- if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) {
- userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
- checkForDisplayingDisplayReceiptsAlert();
- }
+ if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) {
+ userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
+ checkForDisplayingDisplayReceiptsAlert();
+ }
}
void ChatController::checkForDisplayingDisplayReceiptsAlert() {
- if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) {
- chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts."));
- } else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) {
- chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent."));
- } else {
- chatWindow_->cancelAlert();
- }
+ boost::optional<ChatWindow::AlertID> newDeliverReceiptAlert;
+ if (userWantsReceipts_ && (contactSupportsReceipts_ == No)) {
+ newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts."));
+ } else if (userWantsReceipts_ && (contactSupportsReceipts_ == Maybe)) {
+ newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you send."));
+ } else {
+ if (deliveryReceiptAlert_) {
+ chatWindow_->removeAlert(*deliveryReceiptAlert_);
+ deliveryReceiptAlert_.reset();
+ }
+ }
+ if (newDeliverReceiptAlert) {
+ if (deliveryReceiptAlert_) {
+ chatWindow_->removeAlert(*deliveryReceiptAlert_);
+ }
+ deliveryReceiptAlert_ = newDeliverReceiptAlert;
+ }
}
void ChatController::handleBlockingStateChanged() {
- boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
- if (blockList->getState() == BlockList::Available) {
- if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) {
- chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
- chatWindow_->setInputEnabled(false);
- chatWindow_->setBlockingState(ChatWindow::IsBlocked);
- } else {
- chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
- }
- }
-}
-
-void ChatController::handleBlockingItemAdded(const JID& jid) {
- if (toJID_ == (isInMUC_ ? jid: jid.toBare())) {
- chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
- chatWindow_->setInputEnabled(false);
- chatWindow_->setBlockingState(ChatWindow::IsBlocked);
- }
-}
-
-void ChatController::handleBlockingItemRemoved(const JID& jid) {
- if (toJID_ == (isInMUC_ ? jid: jid.toBare())) {
- // FIXME: Support for different types of alerts.
- chatWindow_->cancelAlert();
- chatWindow_->setInputEnabled(true);
- chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
- }
+ std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+ if (blockList->getState() == BlockList::Available) {
+ if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) {
+ if (!blockedContactAlert_) {
+ blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
+ }
+ chatWindow_->setBlockingState(ChatWindow::IsBlocked);
+
+ // disconnect typing events to prevent chat state notifciations to blocked contacts
+ chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
+ chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+ } else {
+ if (blockedContactAlert_) {
+ chatWindow_->removeAlert(*blockedContactAlert_);
+ blockedContactAlert_.reset();
+ }
+ chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
+
+ chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
+ chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+ }
+ }
}
void ChatController::handleBlockUserRequest() {
- if (isInMUC_) {
- eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_));
- } else {
- eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare()));
- }
+ if (isInMUC_) {
+ eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_));
+ } else {
+ eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare()));
+ }
}
void ChatController::handleUnblockUserRequest() {
- if (isInMUC_) {
- eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_));
- } else {
- eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare()));
- }
+ if (isInMUC_) {
+ eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_));
+ } else {
+ eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare()));
+ }
}
void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) {
- boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs));
- eventStream_->send(event);
+ std::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(getToJID(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu));
+ eventStream_->send(event);
}
-void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
- boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
- if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) {
- onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason());
- }
+void ChatController::handleWindowClosed() {
+ onWindowClosed();
}
+void ChatController::handleUIEvent(std::shared_ptr<UIEvent> event) {
+ std::shared_ptr<InviteToMUCUIEvent> inviteEvent = std::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
+ if (inviteEvent && inviteEvent->getOriginator() == getToJID()) {
+ onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason());
+ }
+}
-void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
- boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
- if (replace) {
- eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_));
- replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time(), HighlightAction());
- } else {
- myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction());
- }
+void ChatController::handleIncomingOwnMessage(std::shared_ptr<Message> message) {
+ if (!message->getBody().get_value_or("").empty()) {
+ postSendMessage(message->getBody().get_value_or(""), message);
+ handleStanzaAcked(message);
+ onActivity(message->getBody().get_value_or(""));
+ }
+}
- if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
- chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
- unackedStanzas_[sentStanza] = myLastMessageUIID_;
- }
+void ChatController::postSendMessage(const std::string& body, std::shared_ptr<Stanza> sentStanza) {
+ std::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
+ if (replace) {
+ eraseIf(unackedStanzas_, PairSecondEquals<std::shared_ptr<Stanza>, std::string>(myLastMessageUIID_));
+ replaceMessage(chatMessageParser_->parseMessageBody(body, "", true), myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time());
+ } else {
+ myLastMessageUIID_ = addMessage(chatMessageParser_->parseMessageBody(body, "", true), QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : std::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time());
+ }
- if (sentStanza->getPayload<DeliveryReceiptRequest>()) {
- requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_;
- chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested);
- }
+ if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
+ chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
+ unackedStanzas_[sentStanza] = myLastMessageUIID_;
+ }
- lastWasPresence_ = false;
- chatStateNotifier_->userSentMessage();
+ if (sentStanza->getPayload<DeliveryReceiptRequest>()) {
+ requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_;
+ chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested);
+ }
+
+ lastWasPresence_ = false;
+ chatStateNotifier_->userSentMessage();
}
-void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
- std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza);
- if (unackedStanza != unackedStanzas_.end()) {
- chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received);
- unackedStanzas_.erase(unackedStanza);
- }
+void ChatController::handleStanzaAcked(std::shared_ptr<Stanza> stanza) {
+ std::map<std::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza);
+ if (unackedStanza != unackedStanzas_.end()) {
+ chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received);
+ unackedStanzas_.erase(unackedStanza);
+ }
}
void ChatController::setOnline(bool online) {
- if (!online) {
- std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin();
- for ( ; it != unackedStanzas_.end(); ++it) {
- chatWindow_->setAckState(it->second, ChatWindow::Failed);
- }
- unackedStanzas_.clear();
+ if (!online) {
+ for (auto& stanzaIdPair : unackedStanzas_) {
+ chatWindow_->setAckState(stanzaIdPair.second, ChatWindow::Failed);
+ failedStanzas_[stanzaIdPair.second] = stanzaIdPair.first;
+ }
+ unackedStanzas_.clear();
- Presence::ref fakeOffline(new Presence());
- fakeOffline->setFrom(toJID_);
- fakeOffline->setType(Presence::Unavailable);
- chatStateTracker_->handlePresenceChange(fakeOffline);
- }
- ChatControllerBase::setOnline(online);
+ Presence::ref fakeOffline(new Presence());
+ fakeOffline->setFrom(toJID_);
+ fakeOffline->setType(Presence::Unavailable);
+ chatStateTracker_->handlePresenceChange(fakeOffline);
+ }
+ ChatControllerBase::setOnline(online);
}
void ChatController::handleNewFileTransferController(FileTransferController* ftc) {
- std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty());
- std::string ftID = ftc->setChatWindow(chatWindow_, nick);
-
- ftControllers[ftID] = ftc;
+ std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty());
+ std::string avatarPath;
+ if (ftc->isIncoming()) {
+ avatarPath = pathToString(avatarManager_->getAvatarPath(ftc->getOtherParty()));
+ }
+ else {
+ avatarPath = pathToString(avatarManager_->getAvatarPath(selfJID_));
+ }
+
+ std::string ftID = ftc->setChatWindow(chatWindow_, nick, avatarPath);
+ ftControllers[ftID] = ftc;
+ lastWasPresence_ = false;
+
+ if (ftc->isIncoming()) {
+ auto incomingFileTransferEvent = std::make_shared<IncomingFileTransferEvent>(ftc->getOtherParty());
+ if (hasOpenWindow()) {
+ incomingFileTransferEvent->conclude();
+ }
+ else {
+ unreadMessages_.push_back(incomingFileTransferEvent);
+ updateMessageCount();
+ }
+ eventController_->handleIncomingEvent(incomingFileTransferEvent);
+ }
}
void ChatController::handleWhiteboardSessionRequest(bool senderIsSelf) {
- lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf);
+ lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf);
}
void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state) {
- chatWindow_->setWhiteboardSessionStatus(lastWbID_, state);
+ chatWindow_->setWhiteboardSessionStatus(lastWbID_, state);
}
void ChatController::handleFileTransferCancel(std::string id) {
- SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl;
- if (ftControllers.find(id) != ftControllers.end()) {
- ftControllers[id]->cancel();
- } else {
- std::cerr << "unknown file transfer UI id" << std::endl;
- }
+ SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")";
+ if (ftControllers.find(id) != ftControllers.end()) {
+ ftControllers[id]->cancel();
+ } else {
+ std::cerr << "unknown file transfer UI id" << std::endl;
+ }
}
void ChatController::handleFileTransferStart(std::string id, std::string description) {
- SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl;
- if (ftControllers.find(id) != ftControllers.end()) {
- ftControllers[id]->start(description);
- } else {
- std::cerr << "unknown file transfer UI id" << std::endl;
- }
+ SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")";
+ if (ftControllers.find(id) != ftControllers.end()) {
+ ftControllers[id]->start(description);
+ } else {
+ std::cerr << "unknown file transfer UI id" << std::endl;
+ }
}
void ChatController::handleFileTransferAccept(std::string id, std::string filename) {
- SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl;
- if (ftControllers.find(id) != ftControllers.end()) {
- ftControllers[id]->accept(filename);
- } else {
- std::cerr << "unknown file transfer UI id" << std::endl;
- }
+ SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")";
+ if (ftControllers.find(id) != ftControllers.end()) {
+ ftControllers[id]->accept(filename);
+ } else {
+ std::cerr << "unknown file transfer UI id" << std::endl;
+ }
}
void ChatController::handleSendFileRequest(std::string filename) {
- SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl;
- eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename));
+ SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")";
+ eventStream_->send(std::make_shared<SendFileUIEvent>(getToJID(), filename));
}
void ChatController::handleWhiteboardSessionAccept() {
- eventStream_->send(boost::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_));
+ eventStream_->send(std::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_));
}
void ChatController::handleWhiteboardSessionCancel() {
- eventStream_->send(boost::make_shared<CancelWhiteboardSessionUIEvent>(toJID_));
+ eventStream_->send(std::make_shared<CancelWhiteboardSessionUIEvent>(toJID_));
}
void ChatController::handleWhiteboardWindowShow() {
- eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(toJID_));
+ eventStream_->send(std::make_shared<ShowWhiteboardUIEvent>(toJID_));
+}
+
+std::string ChatController::senderHighlightNameFromMessage(const JID& from) {
+ if (isInMUC_) {
+ return nickResolver_->jidToNick(from);
+ }
+ else {
+ return from.toBare().toString();
+ }
}
std::string ChatController::senderDisplayNameFromMessage(const JID& from) {
- return nickResolver_->jidToNick(from);
-}
-
-std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> presence) {
- std::string nick = senderDisplayNameFromMessage(presence->getFrom());
- std::string response;
- if (!presence || presence->getType() == Presence::Unavailable || presence->getType() == Presence::Error) {
- response = QT_TRANSLATE_NOOP("", "%1% has gone offline");
- } else if (presence->getType() == Presence::Available) {
- StatusShow::Type show = presence->getShow();
- if (show == StatusShow::Online || show == StatusShow::FFC) {
- response = QT_TRANSLATE_NOOP("", "%1% has become available");
- } else if (show == StatusShow::Away || show == StatusShow::XA) {
- response = QT_TRANSLATE_NOOP("", "%1% has gone away");
- } else if (show == StatusShow::DND) {
- response = QT_TRANSLATE_NOOP("", "%1% is now busy");
- }
- }
- Idle::ref idle;
- if ((idle = presence->getPayload<Idle>())) {
- response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % dateTimeToLocalString(idle->getSince()));
- }
-
- if (!response.empty()) {
- response = str(format(response) % nick);
- }
-
- if (!presence->getStatus().empty()) {
- response += " (" + presence->getStatus() + ")";
- }
- return response + ".";
-}
-
-void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
- bool me = false;
- if (toJID_.isBare()) {
- newPresence = presenceOracle_->getHighestPriorityPresence(toJID_);
- if ((newPresence ? newPresence->getShow() : StatusShow::None) != lastShownStatus_) {
- me = true;
- }
- } else if (toJID_.equals(newPresence->getFrom(), JID::WithResource)) {
- me = true;
- }
- if (!me) {
- return;
- }
- if (!newPresence) {
- newPresence = boost::make_shared<Presence>();
- newPresence->setType(Presence::Unavailable);
- }
- lastShownStatus_ = newPresence->getShow();
-
- chatStateTracker_->handlePresenceChange(newPresence);
- chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available);
- std::string newStatusChangeString = getStatusChangeString(newPresence);
- if (newStatusChangeString != lastStatusChangeString_) {
- if (lastWasPresence_) {
- chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString));
- } else {
- chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);
- }
- lastStatusChangeString_ = newStatusChangeString;
- lastWasPresence_ = true;
- }
-}
-
-boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(boost::shared_ptr<Message> message) const {
- return message->getTimestamp();
+ return nickResolver_->jidToNick(from);
+}
+
+std::string ChatController::getStatusChangeString(std::shared_ptr<Presence> presence) {
+ std::string nick = senderDisplayNameFromMessage(presence->getFrom());
+ std::string response;
+ if (!presence || presence->getType() == Presence::Unavailable || presence->getType() == Presence::Error) {
+ response = QT_TRANSLATE_NOOP("", "%1% has gone offline");
+ } else if (presence->getType() == Presence::Available) {
+ StatusShow::Type show = presence->getShow();
+ if (show == StatusShow::Online || show == StatusShow::FFC) {
+ response = QT_TRANSLATE_NOOP("", "%1% has become available");
+ } else if (show == StatusShow::Away || show == StatusShow::XA) {
+ response = QT_TRANSLATE_NOOP("", "%1% has gone away");
+ } else if (show == StatusShow::DND) {
+ response = QT_TRANSLATE_NOOP("", "%1% is now busy");
+ }
+ }
+ Idle::ref idle;
+ if ((idle = presence->getPayload<Idle>())) {
+ response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % Swift::Translator::getInstance()->ptimeToHumanReadableString(idle->getSince()));
+ }
+
+ if (!response.empty()) {
+ response = str(format(response) % nick);
+ }
+
+ if (!presence->getStatus().empty()) {
+ response += " (" + presence->getStatus() + ")";
+ }
+ return response + ".";
+}
+
+void ChatController::handlePresenceChange(std::shared_ptr<Presence> newPresence) {
+ bool relevantPresence = false;
+
+ if (isInMUC_) {
+ // For MUC participants we only have a single presence to choose one and
+ // even for multi-session nicknames multiple resources are not distinguishable
+ // to other participants.
+ if (toJID_.equals(newPresence->getFrom(), JID::WithResource)) {
+ relevantPresence = true;
+ }
+ }
+ else {
+ // For standard chats we retrieve the account presence from the PresenceOracle,
+ // as there can be multiple presences to choose from.
+ if (toJID_.equals(newPresence->getFrom(), JID::WithoutResource)) {
+ // Presence matches ChatController JID.
+ newPresence = presenceOracle_->getAccountPresence(toJID_);
+ relevantPresence = true;
+ }
+ }
+
+ if (!relevantPresence) {
+ return;
+ }
+
+ if (!newPresence) {
+ newPresence = std::make_shared<Presence>();
+ newPresence->setType(Presence::Unavailable);
+ }
+ lastShownStatus_ = newPresence->getShow();
+
+ chatStateTracker_->handlePresenceChange(newPresence);
+ chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available);
+ std::string newStatusChangeString = getStatusChangeString(newPresence);
+ if (newStatusChangeString != lastStatusChangeString_) {
+ if (lastWasPresence_) {
+ chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp);
+ } else {
+ chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);
+ }
+ lastStatusChangeString_ = newStatusChangeString;
+ lastWasPresence_ = true;
+ }
+}
+
+boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(std::shared_ptr<Message> message) const {
+ return message->getTimestamp();
+}
+
+void ChatController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) {
+ lastMessagesIDs_[from.toBare()] = {messageID, addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp)};
+}
+
+void ChatController::handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) {
+ auto lastMessage = lastMessagesIDs_.find(from.toBare());
+ if ((lastMessage != lastMessagesIDs_.end()) && (lastMessage->second.idInStream == idToReplace)) {
+ replaceMessage(message, lastMessage->second.idInWindow, timeStamp);
+ }
+ else {
+ addMessageHandleIncomingMessage(from, message, messageID, senderIsSelf, label, timeStamp);
+ }
}
void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) {
- HistoryMessage::Type type;
- if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) {
- type = HistoryMessage::PrivateMessage;
- }
- else {
- type = HistoryMessage::Chat;
- }
+ HistoryMessage::Type type;
+ if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) {
+ type = HistoryMessage::PrivateMessage;
+ }
+ else {
+ type = HistoryMessage::Chat;
+ }
+
+ if (historyController_) {
+ historyController_->addMessage(message, fromJID, toJID, type, timeStamp);
+ }
+}
+
+bool ChatController::shouldIgnoreMessage(std::shared_ptr<Message> message) {
+ if (!message->getID().empty()) {
+ if (message->getID() == lastHandledMessageID_) {
+ return true;
+ }
+ else {
+ lastHandledMessageID_ = message->getID();
+ }
+ }
+ return false;
+}
- if (historyController_) {
- historyController_->addMessage(message, fromJID, toJID, type, timeStamp);
- }
+JID ChatController::messageCorrectionJID(const JID& fromJID) {
+ return fromJID.toBare();
}
ChatWindow* ChatController::detachChatWindow() {
- chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
- chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
- return ChatControllerBase::detachChatWindow();
+ continuationsBrokenConnection_.disconnect();
+ chatWindow_->onClosed.disconnect(boost::bind(&ChatController::handleWindowClosed, this));
+ chatWindow_->onInviteToChat.disconnect(boost::bind(&ChatController::handleInviteToChat, this, _1));
+ chatWindow_->onUnblockUserRequest.disconnect(boost::bind(&ChatController::handleUnblockUserRequest, this));
+ chatWindow_->onBlockUserRequest.disconnect(boost::bind(&ChatController::handleBlockUserRequest, this));
+ chatWindow_->onWhiteboardWindowShow.disconnect(boost::bind(&ChatController::handleWhiteboardWindowShow, this));
+ chatWindow_->onWhiteboardSessionCancel.disconnect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this));
+ chatWindow_->onWhiteboardSessionAccept.disconnect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this));
+ chatWindow_->onSendFileRequest.disconnect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
+ chatWindow_->onFileTransferCancel.disconnect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
+ chatWindow_->onFileTransferAccept.disconnect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2));
+ chatWindow_->onFileTransferStart.disconnect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2));
+ chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+ chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
+ return ChatControllerBase::detachChatWindow();
}
}
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index f8b6d8b..447e263 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -1,113 +1,117 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
-#include <Swift/Controllers/Chat/ChatControllerBase.h>
-
#include <map>
#include <string>
+#include <Swiften/Base/Tristate.h>
+
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
- class AvatarManager;
- class ChatStateNotifier;
- class ChatStateTracker;
- class NickResolver;
- class EntityCapsProvider;
- class FileTransferController;
- class SettingsProvider;
- class HistoryController;
- class HighlightManager;
- class ClientBlockListManager;
- class UIEvent;
-
- class ChatController : public ChatControllerBase {
- public:
- ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
- virtual ~ChatController();
- virtual void setToJID(const JID& jid);
- virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
- virtual void setOnline(bool online);
- virtual void handleNewFileTransferController(FileTransferController* ftc);
- virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
- virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state);
- virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/);
- virtual ChatWindow* detachChatWindow();
-
- protected:
- void cancelReplaces();
- JID getBaseJID();
- void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming);
-
- private:
- void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
- std::string getStatusChangeString(boost::shared_ptr<Presence> presence);
- bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
- void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza);
- void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent);
- void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction&);
- void preSendMessageRequest(boost::shared_ptr<Message>);
- std::string senderDisplayNameFromMessage(const JID& from);
- virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const;
- void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
- void dayTicked() {lastWasPresence_ = false;}
- void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/);
- void handleBareJIDCapsChanged(const JID& jid);
-
- void handleFileTransferCancel(std::string /* id */);
- void handleFileTransferStart(std::string /* id */, std::string /* description */);
- void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
- void handleSendFileRequest(std::string filename);
-
- void handleWhiteboardSessionAccept();
- void handleWhiteboardSessionCancel();
- void handleWhiteboardWindowShow();
-
- void handleSettingChanged(const std::string& settingPath);
- void checkForDisplayingDisplayReceiptsAlert();
-
- void handleBlockingStateChanged();
- void handleBlockingItemAdded(const JID&);
- void handleBlockingItemRemoved(const JID&);
-
- void handleBlockUserRequest();
- void handleUnblockUserRequest();
-
- void handleInviteToChat(const std::vector<JID>& droppedJIDs);
- void handleInviteToMUCWindowDismissed();
- void handleInviteToMUCWindowCompleted();
-
- void handleUIEvent(boost::shared_ptr<UIEvent> event);
-
- private:
- NickResolver* nickResolver_;
- ChatStateNotifier* chatStateNotifier_;
- ChatStateTracker* chatStateTracker_;
- std::string myLastMessageUIID_;
- bool isInMUC_;
- bool lastWasPresence_;
- std::string lastStatusChangeString_;
- std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_;
- std::map<std::string, std::string> requestedReceipts_;
- StatusShow::Type lastShownStatus_;
- UIEventStream* eventStream_;
-
- ChatWindow::Tristate contactSupportsReceipts_;
- bool receivingPresenceFromUs_;
- bool userWantsReceipts_;
- std::map<std::string, FileTransferController*> ftControllers;
- SettingsProvider* settings_;
- std::string lastWbID_;
-
- ClientBlockListManager* clientBlockListManager_;
- boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
- boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
- boost::bsignals::scoped_connection blockingOnItemRemovedConnection_;
- };
+ class AvatarManager;
+ class ChatStateNotifier;
+ class ChatStateTracker;
+ class ClientBlockListManager;
+ class EntityCapsProvider;
+ class FileTransferController;
+ class HighlightManager;
+ class HistoryController;
+ class NickResolver;
+ class SettingsProvider;
+ class TimerFactory;
+ class UIEvent;
+
+ class ChatController : public ChatControllerBase {
+ public:
+ ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, SettingsProvider* settings, Chattables& chattables);
+ virtual ~ChatController() override;
+ virtual void setToJID(const JID& jid) override;
+ virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) override;
+ virtual void setOnline(bool online) override;
+ virtual void handleNewFileTransferController(FileTransferController* ftc);
+ virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
+ virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state);
+ virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) override;
+ virtual ChatWindow* detachChatWindow() override;
+ virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) override;
+
+ protected:
+ virtual void cancelReplaces() override;
+ virtual JID getBaseJID() override;
+ virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) override;
+ virtual bool shouldIgnoreMessage(std::shared_ptr<Message> message) override;
+ virtual JID messageCorrectionJID(const JID& fromJID) override;
+
+ private:
+ void handlePresenceChange(std::shared_ptr<Presence> newPresence);
+ std::string getStatusChangeString(std::shared_ptr<Presence> presence);
+ virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) override;
+ virtual void postSendMessage(const std::string &body, std::shared_ptr<Stanza> sentStanza) override;
+ virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) override;
+ virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) override;
+ virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) override;
+ virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) override;
+ virtual void preSendMessageRequest(std::shared_ptr<Message>) override;
+ virtual std::string senderHighlightNameFromMessage(const JID& from) override;
+ virtual std::string senderDisplayNameFromMessage(const JID& from) override;
+ virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const override;
+ void handleStanzaAcked(std::shared_ptr<Stanza> stanza);
+ virtual void dayTicked() override { lastWasPresence_ = false; }
+ void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/);
+ virtual void handleBareJIDCapsChanged(const JID& jid) override;
+
+ void handleFileTransferCancel(std::string /* id */);
+ void handleFileTransferStart(std::string /* id */, std::string /* description */);
+ void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
+ void handleSendFileRequest(std::string filename);
+
+ void handleWhiteboardSessionAccept();
+ void handleWhiteboardSessionCancel();
+ void handleWhiteboardWindowShow();
+
+ void handleSettingChanged(const std::string& settingPath);
+ void checkForDisplayingDisplayReceiptsAlert();
+
+ void handleBlockingStateChanged();
+ void handleBlockUserRequest();
+ void handleUnblockUserRequest();
+
+ void handleInviteToChat(const std::vector<JID>& droppedJIDs);
+
+ void handleWindowClosed();
+
+ void handleUIEvent(std::shared_ptr<UIEvent> event);
+
+ private:
+ NickResolver* nickResolver_;
+ ChatStateNotifier* chatStateNotifier_;
+ ChatStateTracker* chatStateTracker_;
+ std::string myLastMessageUIID_;
+ bool isInMUC_;
+ std::string lastStatusChangeString_;
+ StatusShow::Type lastShownStatus_;
+
+ Tristate contactSupportsReceipts_;
+ bool receivingPresenceFromUs_ = false;
+ bool userWantsReceipts_;
+ std::map<std::string, FileTransferController*> ftControllers;
+ std::string lastWbID_;
+ std::string lastHandledMessageID_;
+
+ ClientBlockListManager* clientBlockListManager_;
+ boost::signals2::scoped_connection blockingOnStateChangedConnection_;
+ boost::signals2::scoped_connection blockingOnItemAddedConnection_;
+ boost::signals2::scoped_connection blockingOnItemRemovedConnection_;
+ boost::signals2::scoped_connection continuationsBrokenConnection_;
+
+ boost::optional<ChatWindow::AlertID> deliveryReceiptAlert_;
+ boost::optional<ChatWindow::AlertID> blockedContactAlert_;
+ };
}
-
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 23137dc..3805084 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,369 +1,421 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatControllerBase.h>
-#include <sstream>
#include <map>
+#include <memory>
+#include <sstream>
+#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/smart_ptr/make_shared.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/numeric/conversion/cast.hpp>
-#include <boost/algorithm/string.hpp>
-#include <Swiften/Base/format.h>
+#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Path.h>
-#include <Swiften/Base/String.h>
+#include <Swiften/Base/format.h>
#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Elements/Delay.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
#include <Swiften/Elements/MUCUserPayload.h>
-#include <Swiften/Base/foreach.h>
-#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
-#include <Swiften/Avatars/AvatarManager.h>
+#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
+#include <Swift/Controllers/Chat/Chattables.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
#include <Swift/Controllers/Intl.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
-#include <Swift/Controllers/HighlightManager.h>
-#include <Swift/Controllers/Highlighter.h>
-#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
-#include <Swift/Controllers/Chat/ChatMessageParser.h>
namespace Swift {
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) {
- chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
- chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
- chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
- chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this));
- entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
- highlighter_ = highlightManager->createHighlighter();
- setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
- createDayChangeTimer();
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, SettingsProvider* settings, Chattables& chattables) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream), roomSecurityMarking_(""), previousMessageSecurityMarking_(""), settings_(settings), chattables_(chattables) {
+ chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
+ chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
+ chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
+ chatWindow_->onContinuationsBroken.connect(boost::bind(&ChatControllerBase::handleContinuationsBroken, this));
+ scopedConnectionResendMessage_ = chatWindow_->onResendMessageRequest.connect(boost::bind(&ChatControllerBase::handleResendMessageRequest, this, _1));
+ entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
+ highlighter_ = highlightManager->createHighlighter(nickResolver);
+ ChatControllerBase::setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
}
ChatControllerBase::~ChatControllerBase() {
- delete chatWindow_;
+ delete highlighter_;
+ delete chatWindow_;
}
-void ChatControllerBase::handleLogCleared() {
- cancelReplaces();
+void ChatControllerBase::handleContinuationsBroken() {
+ cancelReplaces();
}
ChatWindow* ChatControllerBase::detachChatWindow() {
- ChatWindow* chatWindow = chatWindow_;
- chatWindow_ = NULL;
- return chatWindow;
+ chatWindow_->onContinuationsBroken.disconnect(boost::bind(&ChatControllerBase::handleContinuationsBroken, this));
+ chatWindow_->onSendMessageRequest.disconnect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
+ chatWindow_->onAllMessagesRead.disconnect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
+ scopedConnectionResendMessage_.disconnect();
+ ChatWindow* chatWindow = chatWindow_;
+ chatWindow_ = nullptr;
+ return chatWindow;
}
void ChatControllerBase::handleCapsChanged(const JID& jid) {
- if (jid.compare(toJID_, JID::WithoutResource) == 0) {
- handleBareJIDCapsChanged(jid);
- }
+ if (jid.compare(toJID_, JID::WithoutResource) == 0) {
+ handleBareJIDCapsChanged(jid);
+ }
}
void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) {
- if (chatWindow_) {
- chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu);
- }
-}
-
-void ChatControllerBase::createDayChangeTimer() {
- if (timerFactory_) {
- boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
- boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1));
- int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds());
- dateChangeTimer_ = boost::shared_ptr<Timer>(timerFactory_->createTimer(millisecondsUntilMidnight));
- dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this));
- dateChangeTimer_->start();
- }
-}
-
-void ChatControllerBase::handleDayChangeTick() {
- dateChangeTimer_->stop();
- boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
- dayTicked();
- createDayChangeTimer();
+ if (chatWindow_) {
+ chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu);
+ }
}
void ChatControllerBase::setEnabled(bool enabled) {
- chatWindow_->setInputEnabled(enabled);
+ chatWindow_->setOnline(enabled);
+ chatWindow_->setCanInitiateImpromptuChats(false);
}
void ChatControllerBase::setOnline(bool online) {
- setEnabled(online);
+ setEnabled(online);
}
JID ChatControllerBase::getBaseJID() {
- return JID(toJID_.toBare());
+ return JID(toJID_.toBare());
}
-void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
- if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) {
- GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_);
- request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2));
- request->send();
- } else {
- chatWindow_->setSecurityLabelsEnabled(false);
- labelsEnabled_ = false;
- }
+void ChatControllerBase::setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) {
+ if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) {
+ GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_);
+ request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2));
+ request->send();
+ } else {
+ chatWindow_->setSecurityLabelsEnabled(false);
+ labelsEnabled_ = false;
+ }
}
void ChatControllerBase::handleAllMessagesRead() {
- if (!unreadMessages_.empty()) {
- targetedUnreadMessages_.clear();
- foreach (boost::shared_ptr<StanzaEvent> stanzaEvent, unreadMessages_) {
- stanzaEvent->conclude();
- }
- unreadMessages_.clear();
- chatWindow_->setUnreadMessageCount(0);
- onUnreadCountChanged();
- }
+ if (!unreadMessages_.empty()) {
+ targetedUnreadMessages_.clear();
+ for (std::shared_ptr<StanzaEvent> stanzaEvent : unreadMessages_) {
+ stanzaEvent->conclude();
+ }
+ unreadMessages_.clear();
+ updateMessageCount();
+ }
}
-int ChatControllerBase::getUnreadCount() {
- return boost::numeric_cast<int>(targetedUnreadMessages_.size());
+size_t ChatControllerBase::getUnreadCount() {
+ return targetedUnreadMessages_.size();
}
void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) {
- if (!stanzaChannel_->isAvailable() || body.empty()) {
- return;
- }
- boost::shared_ptr<Message> message(new Message());
- message->setTo(toJID_);
- message->setType(Swift::Message::Chat);
- message->setBody(body);
- if (labelsEnabled_) {
- if (!isCorrectionMessage) {
- lastLabel_ = chatWindow_->getSelectedSecurityLabel();
- }
- SecurityLabelsCatalog::Item labelItem = lastLabel_;
- if (labelItem.getLabel()) {
- message->addPayload(labelItem.getLabel());
- }
- }
- preSendMessageRequest(message);
-
- boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
- if (useDelayForLatency_) {
- message->addPayload(boost::make_shared<Delay>(now, selfJID_));
- }
- if (isCorrectionMessage) {
- message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
- }
- message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
- stanzaChannel_->sendMessage(message);
- postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
- onActivity(message->getBody());
+ if (!stanzaChannel_->isAvailable() || body.empty()) {
+ return;
+ }
+ std::shared_ptr<Message> message(new Message());
+ message->setTo(toJID_);
+ message->setType(Swift::Message::Chat);
+ message->setBody(body);
+ if (labelsEnabled_) {
+ if (!isCorrectionMessage) {
+ lastLabel_ = chatWindow_->getSelectedSecurityLabel();
+ }
+ SecurityLabelsCatalog::Item labelItem = lastLabel_;
+ if (labelItem.getLabel()) {
+ message->addPayload(labelItem.getLabel());
+ }
+ }
+ preSendMessageRequest(message);
+
+ boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
+ if (useDelayForLatency_) {
+ message->addPayload(std::make_shared<Delay>(now, selfJID_));
+ }
+ if (isCorrectionMessage) {
+ message->addPayload(std::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
+ }
+ message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
+ stanzaChannel_->sendMessage(message);
+ postSendMessage(message->getBody().get(), std::dynamic_pointer_cast<Stanza>(message));
+ onActivity(message->getBody().get());
#ifdef SWIFT_EXPERIMENTAL_HISTORY
- logMessage(body, selfJID_, toJID_, now, false);
+ logMessage(body, selfJID_, toJID_, now, false);
#endif
}
-void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
- if (catalog && !error) {
- if (catalog->getItems().size() == 0) {
- chatWindow_->setSecurityLabelsEnabled(false);
- labelsEnabled_ = false;
- } else {
- labelsEnabled_ = true;
- chatWindow_->setAvailableSecurityLabels(catalog->getItems());
- chatWindow_->setSecurityLabelsEnabled(true);
- }
- } else {
- labelsEnabled_ = false;
- chatWindow_->setSecurityLabelsError();
- }
+void ChatControllerBase::handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
+ if (catalog && !error) {
+ if (catalog->getItems().size() == 0) {
+ chatWindow_->setSecurityLabelsEnabled(false);
+ labelsEnabled_ = false;
+ } else {
+ labelsEnabled_ = true;
+ chatWindow_->setAvailableSecurityLabels(catalog->getItems());
+ chatWindow_->setSecurityLabelsEnabled(true);
+ }
+ } else {
+ labelsEnabled_ = false;
+ chatWindow_->setSecurityLabelsError();
+ }
}
void ChatControllerBase::showChatWindow() {
- chatWindow_->show();
+ chatWindow_->show();
}
void ChatControllerBase::activateChatWindow() {
- chatWindow_->activate();
+ chatWindow_->activate();
+}
+
+bool ChatControllerBase::hasOpenWindow() const {
+ return chatWindow_ && chatWindow_->isVisible();
+}
+
+ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf) {
+ ChatWindow::ChatMessage chatMessage;
+ chatMessage = chatMessageParser_->parseMessageBody(message, senderName, senderIsSelf);
+ return chatMessage;
}
-std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
- if (boost::starts_with(message, "/me ")) {
- return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
- } else {
- return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
- }
+void ChatControllerBase::updateMessageCount() {
+ auto baseJID = getBaseJID();
+ auto state = chattables_.getState(baseJID);
+ state.unreadCount = unreadMessages_.size();
+ chatWindow_->setUnreadMessageCount(state.unreadCount);
+ chattables_.setState(baseJID, state);
+#ifndef NOT_YET
+ onUnreadCountChanged();
+#endif
+}
+
+std::string ChatControllerBase::addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, const std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time) {
+ auto displayedLabel = label;
+
+ if (settings_->getSetting(SettingConstants::MUC_MARKING_ELISION)) {
+ if (roomSecurityMarking_ != "") {
+
+ if (label && label->getDisplayMarking() == roomSecurityMarking_) {
+ if (label->getDisplayMarking() == previousMessageSecurityMarking_) {
+ displayedLabel = std::make_shared<SecurityLabel>();
+ }
+ previousMessageSecurityMarking_ = label->getDisplayMarking();
+ }
+ else if (!label || label->getDisplayMarking().empty()) {
+ displayedLabel = std::make_shared<SecurityLabel>();
+ displayedLabel->setDisplayMarking("Unmarked");
+ previousMessageSecurityMarking_ = "Unmarked";
+ }
+ else {
+ previousMessageSecurityMarking_ = displayedLabel->getDisplayMarking();
+ }
+ }
+ }
+
+ if (chatMessage.isMeCommand()) {
+ return chatWindow_->addAction(chatMessage, senderName, senderIsSelf, displayedLabel, pathToString(avatarPath), time);
+ }
+ else {
+ return chatWindow_->addMessage(chatMessage, senderName, senderIsSelf, displayedLabel, pathToString(avatarPath), time);
+ }
}
-void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
- if (boost::starts_with(message, "/me ")) {
- chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);
- } else {
- chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message), id, time, highlight);
- }
+void ChatControllerBase::replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time) {
+ if (chatMessage.isMeCommand()) {
+ chatWindow_->replaceWithAction(chatMessage, id, time);
+ }
+ else {
+ chatWindow_->replaceMessage(chatMessage, id, time);
+ }
}
bool ChatControllerBase::isFromContact(const JID& from) {
- return from.toBare() == toJID_.toBare();
+ return from.toBare() == toJID_.toBare();
}
-void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
- preHandleIncomingMessage(messageEvent);
- if (messageEvent->isReadable() && !messageEvent->getConcluded()) {
- unreadMessages_.push_back(messageEvent);
- if (messageEvent->targetsMe()) {
- targetedUnreadMessages_.push_back(messageEvent);
- }
- }
- boost::shared_ptr<Message> message = messageEvent->getStanza();
- std::string body = message->getBody();
- HighlightAction highlight;
- if (message->isError()) {
- if (!message->getTo().getResource().empty()) {
- std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
- chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
- }
- }
- else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) {
- handleMUCInvitation(messageEvent->getStanza());
- return;
- }
- else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) {
- handleMediatedMUCInvitation(messageEvent->getStanza());
- return;
- }
- else {
- if (!messageEvent->isReadable()) {
- return;
- }
- showChatWindow();
- JID from = message->getFrom();
- std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
- for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
- if (!delayPayloads[i]->getFrom()) {
- continue;
- }
- boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
- std::ostringstream s;
- s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
- }
- boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
-
- // Determine the timestamp
- boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time();
- boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message);
- if (messageTimeStamp) {
- timeStamp = *messageTimeStamp;
- }
- onActivity(body);
-
- // Highlight
- if (!isIncomingMessageFromMe(message)) {
- highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from));
- }
-
- boost::shared_ptr<Replace> replace = message->getPayload<Replace>();
- if (replace) {
- std::string body = message->getBody();
- // Should check if the user has a previous message
- std::map<JID, std::string>::iterator lastMessage;
- lastMessage = lastMessagesUIID_.find(from);
- if (lastMessage != lastMessagesUIID_.end()) {
- replaceMessage(body, lastMessagesUIID_[from], timeStamp, highlight);
- }
- }
- else {
- lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight);
- }
-
- logMessage(body, from, selfJID_, timeStamp, true);
- }
- chatWindow_->show();
- chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
- onUnreadCountChanged();
- postHandleIncomingMessage(messageEvent, highlight);
+void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) {
+ std::shared_ptr<Message> message = messageEvent->getStanza();
+ if (shouldIgnoreMessage(message)) {
+ return;
+ }
+
+ preHandleIncomingMessage(messageEvent);
+ if (messageEvent->isReadable() && !messageEvent->getConcluded()) {
+ unreadMessages_.push_back(messageEvent);
+ if (messageEvent->targetsMe()) {
+ targetedUnreadMessages_.push_back(messageEvent);
+ }
+ }
+
+ ChatWindow::ChatMessage chatMessage;
+ boost::optional<std::string> optionalBody = message->getBody();
+ std::string body = optionalBody.get_value_or("");
+ if (message->isError()) {
+ if (!message->getTo().getResource().empty()) {
+ std::string errorMessage;
+ if (message->getPayload<Swift::ErrorPayload>()->getCondition() == ErrorPayload::ItemNotFound) {
+ errorMessage = QT_TRANSLATE_NOOP("", "This user could not be found in the room.");
+ }
+ else {
+ errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
+ }
+ chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
+ }
+ }
+ else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) {
+ handleMUCInvitation(messageEvent->getStanza());
+ return;
+ }
+ else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) {
+ handleMediatedMUCInvitation(messageEvent->getStanza());
+ return;
+ }
+ else {
+ if (!messageEvent->isReadable()) {
+ return;
+ }
+ showChatWindow();
+ JID from = message->getFrom();
+ std::vector<std::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
+ for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
+ if (!delayPayloads[i]->getFrom()) {
+ continue;
+ }
+ boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
+ std::ostringstream s;
+ s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
+ }
+ std::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
+
+ // Determine the timestamp
+ boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time();
+ boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message);
+ if (messageTimeStamp) {
+ timeStamp = *messageTimeStamp;
+ }
+ onActivity(body);
+
+ std::shared_ptr<Replace> replace = message->getPayload<Replace>();
+ bool senderIsSelf = isIncomingMessageFromMe(message);
+ chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf);
+ if (replace) {
+ handleIncomingReplaceMessage(from, chatMessage, message->getID(), replace->getID(), senderIsSelf, label, timeStamp);
+ }
+ else {
+ addMessageHandleIncomingMessage(from, chatMessage, message->getID(), senderIsSelf, label, timeStamp);
+ }
+
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+ logMessage(body, from, selfJID_, timeStamp, true);
+#endif
+ }
+ chatWindow_->show();
+ updateMessageCount();
+ postHandleIncomingMessage(messageEvent, chatMessage);
}
-std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) {
- std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message");
- if (!error->getText().empty()) {
- return error->getText();
- }
- else {
- switch (error->getCondition()) {
- case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request");
- case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict");
- case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented");
- case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden");
- case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted");
- case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error");
- case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found");
- case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed");
- case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected");
- case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed");
- case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized");
- case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required");
- case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable");
- case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect");
- case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required");
- case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found");
- case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout");
- case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources");
- case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable");
- case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required");
- case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition");
- case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request");
- }
- }
- assert(false);
- return defaultMessage;
+std::string ChatControllerBase::getErrorMessage(std::shared_ptr<ErrorPayload> error) {
+ std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message");
+ if (!error->getText().empty()) {
+ return error->getText();
+ }
+ else {
+ switch (error->getCondition()) {
+ case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request");
+ case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict");
+ case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented");
+ case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden");
+ case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted");
+ case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error");
+ case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found");
+ case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed");
+ case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected");
+ case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed");
+ case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized");
+ case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required");
+ case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable");
+ case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect");
+ case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required");
+ case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found");
+ case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout");
+ case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources");
+ case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable");
+ case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required");
+ case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition");
+ case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request");
+ }
+ }
+ assert(false);
+ return defaultMessage;
}
void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) {
- unreadMessages_.push_back(event);
- chatWindow_->show();
- chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
- onUnreadCountChanged();
- chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu());
- eventController_->handleIncomingEvent(event);
+ unreadMessages_.push_back(event);
+ chatWindow_->show();
+ updateMessageCount();
+ chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu());
+ eventController_->handleIncomingEvent(event);
+ lastWasPresence_ = false;
}
void ChatControllerBase::handleMUCInvitation(Message::ref message) {
- MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
-
- if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
- eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true));
- } else {
- MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu());
- handleGeneralMUCInvitation(inviteEvent);
- }
+ MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
+
+ if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
+ eventStream_->send(std::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, true));
+ } else {
+ MUCInviteEvent::ref inviteEvent = std::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu());
+ handleGeneralMUCInvitation(inviteEvent);
+ }
}
void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
- MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite();
- JID from = message->getFrom();
- std::string reason;
- if (!invite.reason.empty()) {
- reason = invite.reason;
- }
- std::string password;
- if (message->getPayload<MUCUserPayload>()->getPassword()) {
- password = *message->getPayload<MUCUserPayload>()->getPassword();
- }
-
- MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false);
- handleGeneralMUCInvitation(inviteEvent);
+ MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite();
+ JID from = message->getFrom();
+ std::string reason;
+ if (!invite.reason.empty()) {
+ reason = invite.reason;
+ }
+ std::string password;
+ if (message->getPayload<MUCUserPayload>()->getPassword()) {
+ password = *message->getPayload<MUCUserPayload>()->getPassword();
+ }
+
+ MUCInviteEvent::ref inviteEvent = std::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false);
+ handleGeneralMUCInvitation(inviteEvent);
+}
+
+void ChatControllerBase::handleResendMessageRequest(const std::string& id) {
+ if (failedStanzas_.find(id) != failedStanzas_.end()) {
+ if (auto resendMsg = std::dynamic_pointer_cast<Message>(failedStanzas_[id])) {
+ stanzaChannel_->sendMessage(resendMsg);
+ if (stanzaChannel_->getStreamManagementEnabled()) {
+ chatWindow_->setAckState(id, ChatWindow::Pending);
+ unackedStanzas_[failedStanzas_[id]] = id;
+ }
+ if (resendMsg->getPayload<DeliveryReceiptRequest>()) {
+ requestedReceipts_[resendMsg->getID()] = id;
+ chatWindow_->setMessageReceiptState(id, ChatWindow::ReceiptRequested);
+ }
+ lastWasPresence_ = false;
+ failedStanzas_.erase(id);
+ }
+ }
}
}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 7db94a4..92c6175 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -1,134 +1,165 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
#include <map>
-#include <vector>
+#include <memory>
#include <string>
+#include <vector>
-#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/optional.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/signals2.hpp>
-#include <Swiften/Network/Timer.h>
-#include <Swiften/Network/TimerFactory.h>
-#include <Swiften/Elements/Stanza.h>
-#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Base/IDGenerator.h>
#include <Swiften/Elements/DiscoInfo.h>
-#include <Swiften/JID/JID.h>
-#include <Swiften/Elements/SecurityLabelsCatalog.h>
#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Elements/SecurityLabelsCatalog.h>
+#include <Swiften/Elements/Stanza.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/MUC/MUCRegistry.h>
#include <Swiften/Presence/PresenceOracle.h>
#include <Swiften/Queries/IQRouter.h>
-#include <Swiften/Base/IDGenerator.h>
-#include <Swiften/MUC/MUCRegistry.h>
-#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
-#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
#include <Swift/Controllers/HistoryController.h>
-#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
+#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
namespace Swift {
- class IQRouter;
- class StanzaChannel;
- class ChatWindowFactory;
- class AvatarManager;
- class UIEventStream;
- class EventController;
- class EntityCapsProvider;
- class HighlightManager;
- class Highlighter;
- class ChatMessageParser;
- class AutoAcceptMUCInviteDecider;
+ class AutoAcceptMUCInviteDecider;
+ class AvatarManager;
+ class ChatMessageParser;
+ class Chattables;
+ class ChatWindowFactory;
+ class EntityCapsProvider;
+ class EventController;
+ class Highlighter;
+ class HighlightManager;
+ class IQRouter;
+ class NickResolver;
+ class StanzaChannel;
+ class UIEventStream;
+
+ class ChatControllerBase : public boost::signals2::trackable {
+ public:
+ class StreamWindowMessageIDPair {
+ public:
+ std::string idInStream;
+ std::string idInWindow;
+ };
+
+ public:
+ virtual ~ChatControllerBase();
+ void showChatWindow();
+ void activateChatWindow();
+ bool hasOpenWindow() const;
+ virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info);
+ virtual void handleIncomingOwnMessage(std::shared_ptr<Message> /*message*/) {}
+ void handleIncomingMessage(std::shared_ptr<MessageEvent> message);
+ std::string addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time);
+ void replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time);
+ virtual void setOnline(bool online);
+ void setEnabled(bool enabled);
+ virtual void setToJID(const JID& jid) {toJID_ = jid;}
+ /** Used for determining when something is recent.*/
+ boost::signals2::signal<void (const std::string& /*activity*/)> onActivity;
+ boost::signals2::signal<void ()> onUnreadCountChanged;
+ boost::signals2::signal<void ()> onWindowClosed;
+ size_t getUnreadCount();
+ const JID& getToJID() {return toJID_;}
+ void handleCapsChanged(const JID& jid);
+ void setCanStartImpromptuChats(bool supportsImpromptu);
+ virtual ChatWindow* detachChatWindow();
+ boost::signals2::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC;
+
+ protected:
+ ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, SettingsProvider* settings, Chattables& chattables);
- class ChatControllerBase : public boost::bsignals::trackable {
- public:
- virtual ~ChatControllerBase();
- void showChatWindow();
- void activateChatWindow();
- virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
- void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
- std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
- void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
- virtual void setOnline(bool online);
- virtual void setEnabled(bool enabled);
- virtual void setToJID(const JID& jid) {toJID_ = jid;}
- /** Used for determining when something is recent.*/
- boost::signal<void (const std::string& /*activity*/)> onActivity;
- boost::signal<void ()> onUnreadCountChanged;
- int getUnreadCount();
- const JID& getToJID() {return toJID_;}
- void handleCapsChanged(const JID& jid);
- void setCanStartImpromptuChats(bool supportsImpromptu);
- virtual ChatWindow* detachChatWindow();
- boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC;
+ /**
+ * Pass the Message appended, and the stanza used to send it.
+ */
+ virtual void postSendMessage(const std::string&, std::shared_ptr<Stanza>) {}
+ virtual std::string senderDisplayNameFromMessage(const JID& from) = 0;
+ virtual std::string senderHighlightNameFromMessage(const JID& from) = 0;
+ virtual bool isIncomingMessageFromMe(std::shared_ptr<Message>) = 0;
+ virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) {}
+ virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) = 0;
+ virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& chatMessage, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) = 0;
+ virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage&) {}
+ virtual void preSendMessageRequest(std::shared_ptr<Message>) {}
+ virtual bool isFromContact(const JID& from);
+ virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const = 0;
+ virtual void dayTicked() {}
+ virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
+ std::string getErrorMessage(std::shared_ptr<ErrorPayload>);
+ virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {}
+ virtual void cancelReplaces() = 0;
+ /** JID any iq for account should go to - bare except for PMs */
+ virtual JID getBaseJID();
+ virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
+ ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf);
+ void updateMessageCount();
+ virtual bool shouldIgnoreMessage(std::shared_ptr<Message> /* message */) {
+ return false;
+ }
- protected:
- ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
+ /**
+ * What JID should be used for last message correction (XEP-0308) tracking.
+ */
+ virtual JID messageCorrectionJID(const JID& fromJID) = 0;
+ virtual void handleResendMessageRequest(const std::string& id);
- /**
- * Pass the Message appended, and the stanza used to send it.
- */
- virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {}
- virtual std::string senderDisplayNameFromMessage(const JID& from) = 0;
- virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0;
- virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}
- virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) {}
- virtual void preSendMessageRequest(boost::shared_ptr<Message>) {}
- virtual bool isFromContact(const JID& from);
- virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0;
- virtual void dayTicked() {}
- virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
- std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
- virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {}
- virtual void cancelReplaces() = 0;
- /** JID any iq for account should go to - bare except for PMs */
- virtual JID getBaseJID();
- virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
+ private:
+ IDGenerator idGenerator_;
+ std::string lastSentMessageStanzaID_;
- private:
- IDGenerator idGenerator_;
- std::string lastSentMessageStanzaID_;
- void createDayChangeTimer();
- void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
- void handleAllMessagesRead();
- void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
- void handleDayChangeTick();
- void handleMUCInvitation(Message::ref message);
- void handleMediatedMUCInvitation(Message::ref message);
- void handleGeneralMUCInvitation(MUCInviteEvent::ref event);
- void handleLogCleared();
+ void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
+ void handleAllMessagesRead();
+ void handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
+ void handleMUCInvitation(Message::ref message);
+ void handleMediatedMUCInvitation(Message::ref message);
+ void handleGeneralMUCInvitation(MUCInviteEvent::ref event);
+ void handleContinuationsBroken();
- protected:
- JID selfJID_;
- std::vector<boost::shared_ptr<StanzaEvent> > unreadMessages_;
- std::vector<boost::shared_ptr<StanzaEvent> > targetedUnreadMessages_;
- StanzaChannel* stanzaChannel_;
- IQRouter* iqRouter_;
- ChatWindowFactory* chatWindowFactory_;
- ChatWindow* chatWindow_;
- JID toJID_;
- bool labelsEnabled_;
- std::map<JID, std::string> lastMessagesUIID_;
- PresenceOracle* presenceOracle_;
- AvatarManager* avatarManager_;
- bool useDelayForLatency_;
- EventController* eventController_;
- boost::shared_ptr<Timer> dateChangeTimer_;
- TimerFactory* timerFactory_;
- EntityCapsProvider* entityCapsProvider_;
- SecurityLabelsCatalog::Item lastLabel_;
- HistoryController* historyController_;
- MUCRegistry* mucRegistry_;
- Highlighter* highlighter_;
- ChatMessageParser* chatMessageParser_;
- AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
- UIEventStream* eventStream_;
- };
+ protected:
+ JID selfJID_;
+ std::vector<std::shared_ptr<StanzaEvent> > unreadMessages_;
+ std::vector<std::shared_ptr<StanzaEvent> > targetedUnreadMessages_;
+ StanzaChannel* stanzaChannel_;
+ IQRouter* iqRouter_;
+ ChatWindowFactory* chatWindowFactory_;
+ ChatWindow* chatWindow_;
+ JID toJID_;
+ bool labelsEnabled_;
+ std::map<JID, StreamWindowMessageIDPair> lastMessagesIDs_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ bool useDelayForLatency_;
+ EventController* eventController_;
+ EntityCapsProvider* entityCapsProvider_;
+ SecurityLabelsCatalog::Item lastLabel_;
+ HistoryController* historyController_;
+ MUCRegistry* mucRegistry_;
+ Highlighter* highlighter_;
+ std::shared_ptr<ChatMessageParser> chatMessageParser_;
+ AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
+ UIEventStream* eventStream_;
+ bool lastWasPresence_ = false;
+ std::string roomSecurityMarking_;
+ std::string previousMessageSecurityMarking_;
+ SettingsProvider* settings_;
+ boost::signals2::scoped_connection scopedConnectionResendMessage_;
+ std::map<std::string, std::string> requestedReceipts_;
+ std::map<std::shared_ptr<Stanza>, std::string> unackedStanzas_;
+ std::map<std::string, std::shared_ptr<Stanza>> failedStanzas_;
+ Chattables& chattables_;
+ };
}
diff --git a/Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h b/Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h
new file mode 100644
index 0000000..1e053b2
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <boost/version.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/bind.hpp>
+#include <boost/serialization/map.hpp>
+#include <boost/serialization/optional.hpp>
+#include <boost/serialization/split_free.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
+
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+
+BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 3)
+namespace Swift {
+ const boost::archive::library_version_type BoostArchiveSkipVersion(15);
+}
+
+namespace boost {
+ namespace serialization {
+ template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) {
+ std::string jidStr = jid.toString();
+ ar << jidStr;
+ }
+
+ template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) {
+ std::string stringJID;
+ ar >> stringJID;
+ jid = Swift::JID(stringJID);
+ }
+
+ template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version) {
+ split_free(ar, t, file_version);
+ }
+
+ template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) {
+ auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION();
+ int archiveVersion = 0;
+ archive::text_oarchive* outputStream = dynamic_cast<archive::text_oarchive*>(&ar);
+ if (outputStream) {
+ archiveVersion = outputStream->get_library_version();
+ }
+ archive::text_iarchive* inputStream = dynamic_cast<archive::text_iarchive*>(&ar);
+ if (inputStream) {
+ archiveVersion = inputStream->get_library_version();
+ //Due to https://svn.boost.org/trac10/ticket/13050 the password field may fail to load and crash the client. Therefore we skip loading the values from previous versions.
+ if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion && archiveLibraryVersion > archiveVersion) {
+ return;
+ }
+ }
+ ar & chat.jid;
+ ar & chat.chatName;
+ ar & chat.activity;
+ ar & chat.isMUC;
+ ar & chat.nick;
+ ar & chat.impromptuJIDs;
+ if (version > 0) {
+ if (outputStream && archiveLibraryVersion == Swift::BoostArchiveSkipVersion) {
+ //The bug does not affect the case boost::optional doesn't have a value. Therefore we store always that value, to avoid problem on future launches of the client.
+ boost::optional<std::string> empty;
+ ar & empty;
+ }
+ else {
+ ar & chat.password;
+ }
+ }
+ if (version > 1) {
+ ar & chat.inviteesNames;
+ }
+ }
+ }
+}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp
index ce184ea..31be451 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.cpp
+++ b/Swift/Controllers/Chat/ChatMessageParser.cpp
@@ -1,112 +1,264 @@
/*
- * Copyright (c) 2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2013-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatMessageParser.h>
-#include <vector>
+#include <algorithm>
+#include <memory>
#include <utility>
+#include <vector>
-#include <boost/smart_ptr/make_shared.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
#include <Swiften/Base/Regex.h>
-#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/String.h>
#include <SwifTools/Linkify.h>
-
namespace Swift {
- ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons) : emoticons_(emoticons) {
-
- }
-
- typedef std::pair<std::string, std::string> StringPair;
-
- ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body) {
- ChatWindow::ChatMessage parsedMessage;
- std::string remaining = body;
- /* Parse one, URLs */
- while (!remaining.empty()) {
- bool found = false;
- std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
- remaining = "";
- for (size_t i = 0; i < links.first.size(); i++) {
- const std::string& part = links.first[i];
- if (found) {
- // Must be on the last part, then
- remaining = part;
- }
- else {
- if (i == links.second) {
- found = true;
- parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part));
- }
- else {
- parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part));
- }
- }
- }
- }
-
-
-
- std::string regexString;
- /* Parse two, emoticons */
- foreach (StringPair emoticon, emoticons_) {
- /* Construct a regexp that finds an instance of any of the emoticons inside a group */
- regexString += regexString.empty() ? "(" : "|";
- regexString += Regex::escape(emoticon.first);
- }
- if (!regexString.empty()) {
- regexString += ")";
- boost::regex emoticonRegex(regexString);
-
- ChatWindow::ChatMessage newMessage;
- foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
- boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
- if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
- try {
- boost::match_results<std::string::const_iterator> match;
- const std::string& text = textPart->text;
- std::string::const_iterator start = text.begin();
- while (regex_search(start, text.end(), match, emoticonRegex)) {
- std::string::const_iterator matchStart = match[0].first;
- std::string::const_iterator matchEnd = match[0].second;
- if (start != matchStart) {
- /* If we're skipping over plain text since the previous emoticon, record it as plain text */
- newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
- }
- boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>();
- std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(match.str());
- assert (emoticonIterator != emoticons_.end());
- const StringPair& emoticon = *emoticonIterator;
- emoticonPart->imagePath = emoticon.second;
- emoticonPart->alternativeText = emoticon.first;
- newMessage.append(emoticonPart);
- start = matchEnd;
- }
- if (start != text.end()) {
- /* If there's plain text after the last emoticon, record it */
- newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
- }
-
- }
- catch (std::runtime_error) {
- /* Basically too expensive to compute the regex results and it gave up, so pass through as text */
- newMessage.append(part);
- }
- }
- else {
- newMessage.append(part);
- }
- }
- parsedMessage = newMessage;
-
- }
- return parsedMessage;
- }
+ ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, std::shared_ptr<HighlightConfiguration> highlightConfiguration, Mode mode) : emoticons_(emoticons), highlightConfiguration_(highlightConfiguration), mode_(mode) {
+ }
+
+ typedef std::pair<std::string, std::string> StringPair;
+
+ ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& senderNickname, bool senderIsSelf) {
+ ChatWindow::ChatMessage parsedMessage;
+
+ std::string remaining = body;
+ if (boost::starts_with(body, "/me ")) {
+ remaining = String::getSplittedAtFirst(body, ' ').second;
+ parsedMessage.setIsMeCommand(true);
+ }
+
+ /* Parse one, URLs */
+ while (!remaining.empty()) {
+ bool found = false;
+ std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
+ remaining = "";
+ for (size_t i = 0; i < links.first.size(); i++) {
+ const std::string& part = links.first[i];
+ if (found) {
+ // Must be on the last part, then
+ remaining = part;
+ }
+ else {
+ if (i == links.second) {
+ found = true;
+ parsedMessage.append(std::make_shared<ChatWindow::ChatURIMessagePart>(part));
+ }
+ else {
+ parsedMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(part));
+ }
+ }
+ }
+ }
+
+ /* do emoticon substitution */
+ parsedMessage = emoticonHighlight(parsedMessage);
+
+ if (!senderIsSelf) { /* do not highlight our own messsages */
+ // Highlight keywords and own mentions.
+ parsedMessage = splitHighlight(parsedMessage);
+
+ // Highlight full message events like, specific sender, general
+ // incoming group message, or general incoming direct message.
+ parsedMessage = fullMessageHighlight(parsedMessage, senderNickname);
+ }
+
+ return parsedMessage;
+ }
+
+ ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) {
+ ChatWindow::ChatMessage parsedMessage = message;
+
+ std::string regexString;
+ /* Parse two, emoticons */
+ for (StringPair emoticon : emoticons_) {
+ /* Construct a regexp that finds an instance of any of the emoticons inside a group
+ * at the start or end of the line, or beside whitespace.
+ */
+ regexString += regexString.empty() ? "" : "|";
+ std::string escaped = "(" + Regex::escape(emoticon.first) + ")";
+ regexString += "^" + escaped + "|";
+ regexString += escaped + "$|";
+ regexString += "\\s" + escaped + "|";
+ regexString += escaped + "\\s";
+
+ }
+ if (!regexString.empty()) {
+ regexString += "";
+ boost::regex emoticonRegex(regexString);
+
+ ChatWindow::ChatMessage newMessage;
+ for (const auto& part : parsedMessage.getParts()) {
+ std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+ if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+ try {
+ boost::match_results<std::string::const_iterator> match;
+ const std::string& text = textPart->text;
+ std::string::const_iterator start = text.begin();
+ while (regex_search(start, text.end(), match, emoticonRegex)) {
+ int matchIndex = 0;
+ for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) {
+ if (match[matchIndex].length() > 0) {
+ //This is the matching subgroup
+ break;
+ }
+ }
+ std::string::const_iterator matchStart = match[matchIndex].first;
+ std::string::const_iterator matchEnd = match[matchIndex].second;
+ if (start != matchStart) {
+ /* If we're skipping over plain text since the previous emoticon, record it as plain text */
+ newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
+ }
+ std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = std::make_shared<ChatWindow::ChatEmoticonMessagePart>();
+ std::string matchString = match[matchIndex].str();
+ std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString);
+ assert (emoticonIterator != emoticons_.end());
+ const StringPair& emoticon = *emoticonIterator;
+ emoticonPart->imagePath = emoticon.second;
+ emoticonPart->alternativeText = emoticon.first;
+ newMessage.append(emoticonPart);
+ start = matchEnd;
+ }
+ if (start != text.end()) {
+ /* If there's plain text after the last emoticon, record it */
+ newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
+ }
+
+ }
+ catch (const std::runtime_error&) {
+ /* Basically too expensive to compute the regex results and it gave up, so pass through as text */
+ newMessage.append(part);
+ }
+ }
+ else {
+ newMessage.append(part);
+ }
+ }
+ parsedMessage.setParts(newMessage.getParts());
+ }
+
+ return parsedMessage;
+ }
+
+ ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message) {
+ auto keywordToRegEx = [](const std::string& keyword, bool matchCaseSensitive) {
+ std::string escaped = Regex::escape(keyword);
+ boost::regex::flag_type flags = boost::regex::normal;
+ if (!matchCaseSensitive) {
+ flags |= boost::regex::icase;
+ }
+ return boost::regex("\\b" + escaped + "\\b", flags);
+ };
+
+ auto highlightKeywordInChatMessage = [&](const ChatWindow::ChatMessage& message, const std::string& keyword, bool matchCaseSensitive, const HighlightAction& action) {
+ ChatWindow::ChatMessage resultMessage;
+
+ for (const auto& part : message.getParts()) {
+ std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+ if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+ try {
+ boost::match_results<std::string::const_iterator> match;
+ const std::string& text = textPart->text;
+ std::string::const_iterator start = text.begin();
+ while (regex_search(start, text.end(), match, keywordToRegEx(keyword, matchCaseSensitive))) {
+ std::string::const_iterator matchStart = match[0].first;
+ std::string::const_iterator matchEnd = match[0].second;
+ if (start != matchStart) {
+ /* If we're skipping over plain text since the previous emoticon, record it as plain text */
+ resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
+ }
+ std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = std::make_shared<ChatWindow::ChatHighlightingMessagePart>();
+ highlightPart->text = match.str();
+ highlightPart->action = action;
+ resultMessage.append(highlightPart);
+ start = matchEnd;
+ }
+ if (start != text.end()) {
+ /* If there's plain text after the last emoticon, record it */
+ resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
+ }
+ }
+ catch (const std::runtime_error&) {
+ /* Basically too expensive to compute the regex results and it gave up, so pass through as text */
+ resultMessage.append(part);
+ }
+ } else {
+ resultMessage.append(part);
+ }
+ }
+ return resultMessage;
+ };
+
+ ChatWindow::ChatMessage parsedMessage = message;
+
+ // detect mentions of own nickname
+ HighlightAction ownMentionKeywordAction = highlightConfiguration_->ownMentionAction;
+ ownMentionKeywordAction.setSoundFilePath(boost::optional<std::string>());
+ ownMentionKeywordAction.setSystemNotificationEnabled(false);
+ if (!getNick().empty() && !highlightConfiguration_->ownMentionAction.isEmpty()) {
+ auto nicknameHighlightedMessage = highlightKeywordInChatMessage(parsedMessage, nick_, false, ownMentionKeywordAction);
+ auto highlightedParts = nicknameHighlightedMessage.getParts();
+ auto ownNicknamePart = std::find_if(highlightedParts.begin(), highlightedParts.end(), [&](std::shared_ptr<ChatWindow::ChatMessagePart>& part){
+ auto highlightPart = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part);
+ if (highlightPart && highlightPart->text == nick_) {
+ return true;
+ }
+ return false;
+ });
+ if (ownNicknamePart != highlightedParts.end()) {
+ parsedMessage.setHighlightActionOwnMention(highlightConfiguration_->ownMentionAction);
+ }
+ parsedMessage.setParts(nicknameHighlightedMessage.getParts());
+ }
+
+ // detect keywords
+ for (const auto& keywordHighlight : highlightConfiguration_->keywordHighlights) {
+ if (keywordHighlight.keyword.empty() || keywordHighlight.action.isEmpty()) {
+ continue;
+ }
+ auto newMessage = highlightKeywordInChatMessage(parsedMessage, keywordHighlight.keyword, keywordHighlight.matchCaseSensitive, keywordHighlight.action);
+ parsedMessage.setParts(newMessage.getParts());
+ }
+
+ return parsedMessage;
+ }
+
+ ChatWindow::ChatMessage ChatMessageParser::fullMessageHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& sender) {
+ auto fullHighlightedMessage = parsedMessage;
+
+ // contact highlighting
+ for (const auto& contactHighlight : highlightConfiguration_->contactHighlights) {
+ if (sender == contactHighlight.name) {
+ fullHighlightedMessage.setHighlightActionSender(contactHighlight.action);
+ break;
+ }
+ }
+
+ // general incoming messages
+ HighlightAction groupAction;
+ HighlightAction chatAction;
+
+ switch (mode_) {
+ case Mode::GroupChat:
+ groupAction.setSoundFilePath(highlightConfiguration_->playSoundOnIncomingGroupchatMessages ? boost::optional<std::string>("") : boost::optional<std::string>());
+ groupAction.setSystemNotificationEnabled(highlightConfiguration_->showNotificationOnIncomingGroupchatMessages);
+ fullHighlightedMessage.setHighlightActionGroupMessage(groupAction);
+ break;
+
+ case Mode::Chat:
+ chatAction.setSoundFilePath(highlightConfiguration_->playSoundOnIncomingDirectMessages ? boost::optional<std::string>("") : boost::optional<std::string>());
+ chatAction.setSystemNotificationEnabled(highlightConfiguration_->showNotificationOnIncomingDirectMessages);
+ fullHighlightedMessage.setHighlightActonDirectMessage(chatAction);
+ break;
+ }
+
+ return fullHighlightedMessage;
+ }
}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h
index c9b9456..de5eac9 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.h
+++ b/Swift/Controllers/Chat/ChatMessageParser.h
@@ -1,23 +1,44 @@
/*
- * Copyright (c) 2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2013-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
+#include <memory>
#include <string>
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
- class ChatMessageParser {
- public:
- ChatMessageParser(const std::map<std::string, std::string>& emoticons);
- ChatWindow::ChatMessage parseMessageBody(const std::string& body);
- private:
- std::map<std::string, std::string> emoticons_;
+ /**
+ * @brief The ChatMessageParser class takes an emoticon map, a \ref HighlightConfiguration, and a boolean that indicates if the message context is in a MUC or not.
+ * The class handles parsing a message string and identifies emoticons, URLs, and various highlights.
+ */
+ class ChatMessageParser {
+ public:
+ enum class Mode { Chat, GroupChat };
- };
+ public:
+ ChatMessageParser(const std::map<std::string, std::string>& emoticons, std::shared_ptr<HighlightConfiguration> highlightConfiguration, Mode mode = Mode::Chat);
+
+ void setNick(const std::string& nick) { nick_ = nick; }
+ std::string getNick() const { return nick_; }
+
+ ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& sender = "", bool senderIsSelf = false);
+
+ private:
+ ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage);
+ ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage);
+ ChatWindow::ChatMessage fullMessageHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& sender);
+
+ private:
+ std::map<std::string, std::string> emoticons_;
+ std::shared_ptr<HighlightConfiguration> highlightConfiguration_;
+ Mode mode_;
+ std::string nick_;
+ };
}
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 5d69019..193af7f 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,945 +1,1200 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2019 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatsManager.h>
-#include <boost/bind.hpp>
+#include <memory>
+
#include <boost/algorithm/string.hpp>
-#include <boost/smart_ptr/make_shared.hpp>
-#include <boost/archive/text_oarchive.hpp>
-#include <boost/archive/text_iarchive.hpp>
-#include <boost/serialization/vector.hpp>
-#include <boost/serialization/map.hpp>
-#include <boost/serialization/string.hpp>
-#include <boost/serialization/split_free.hpp>
-
-#include <Swiften/Base/foreach.h>
-#include <Swiften/Presence/PresenceSender.h>
+#include <boost/bind.hpp>
+
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Base/Log.h>
+#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/NickResolver.h>
-#include <Swiften/MUC/MUCManager.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Disco/DiscoServiceWalker.h>
+#include <Swiften/Disco/FeatureOracle.h>
+#include <Swiften/Elements/CarbonsReceived.h>
+#include <Swiften/Elements/CarbonsSent.h>
#include <Swiften/Elements/ChatState.h>
-#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
-#include <Swiften/MUC/MUCBookmarkManager.h>
-#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Elements/Forwarded.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/MUC/MUCBookmarkManager.h>
+#include <Swiften/MUC/MUCManager.h>
+#include <Swiften/Presence/PresenceSender.h>
#include <Swiften/Roster/XMPPRoster.h>
-#include <Swiften/Client/ClientBlockListManager.h>
-#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/StringCodecs/Base64.h>
+#include <Swiften/VCards/VCardManager.h>
+#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
+#include <Swift/Controllers/Chat/Chattables.h>
#include <Swift/Controllers/Chat/ChatController.h>
#include <Swift/Controllers/Chat/ChatControllerBase.h>
-#include <Swift/Controllers/Chat/MUCSearchController.h>
-#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/Chat/MUCController.h>
-#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
-#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h>
-#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/Chat/MUCSearchController.h>
+#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <Swift/Controllers/FileTransfer/FileTransferController.h>
+#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
+#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
-#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
-#include <Swift/Controllers/FileTransfer/FileTransferController.h>
-#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
-#include <Swift/Controllers/ProfileSettingsProvider.h>
-#include <Swift/Controllers/Settings/SettingsProvider.h>
-#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/WhiteboardManager.h>
-#include <Swift/Controllers/Chat/ChatMessageParser.h>
-#include <Swift/Controllers/Chat/UserSearchController.h>
-#include <Swiften/Disco/DiscoServiceWalker.h>
-#include <Swiften/Client/ClientBlockListManager.h>
-#include <Swiften/StringCodecs/Base64.h>
-#include <Swiften/Base/Log.h>
-
-namespace boost {
-namespace serialization {
- template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) {
- std::string jidStr = jid.toString();
- ar << jidStr;
- }
-
- template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) {
- std::string stringJID;
- ar >> stringJID;
- jid = Swift::JID(stringJID);
- }
-
- template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){
- split_free(ar, t, file_version);
- }
-
- template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int /*version*/) {
- ar & chat.jid;
- ar & chat.chatName;
- ar & chat.activity;
- ar & chat.isMUC;
- ar & chat.nick;
- ar & chat.impromptuJIDs;
- }
-}
-}
+#include <Swift/Controllers/XMPPEvents/EventController.h>
namespace Swift {
typedef std::pair<JID, ChatController*> JIDChatControllerPair;
typedef std::pair<JID, MUCController*> JIDMUCControllerPair;
+#ifndef NOT_YET
#define RECENT_CHATS "recent_chats"
+#endif
ChatsManager::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) :
- jid_(jid),
- joinMUCWindowFactory_(joinMUCWindowFactory),
- useDelayForLatency_(useDelayForLatency),
- mucRegistry_(mucRegistry),
- entityCapsProvider_(entityCapsProvider),
- mucManager(mucManager),
- ftOverview_(ftOverview),
- roster_(roster),
- eagleMode_(eagleMode),
- settings_(settings),
- historyController_(historyController),
- whiteboardManager_(whiteboardManager),
- highlightManager_(highlightManager),
- clientBlockListManager_(clientBlockListManager),
- inviteUserSearchController_(inviteUserSearchController) {
- timerFactory_ = timerFactory;
- eventController_ = eventController;
- stanzaChannel_ = stanzaChannel;
- iqRouter_ = iqRouter;
- chatWindowFactory_ = chatWindowFactory;
- nickResolver_ = nickResolver;
- presenceOracle_ = presenceOracle;
- avatarManager_ = NULL;
- serverDiscoInfo_ = boost::make_shared<DiscoInfo>();
- presenceSender_ = presenceSender;
- uiEventStream_ = uiEventStream;
- mucBookmarkManager_ = NULL;
- profileSettings_ = profileSettings;
- chatMessageParser_ = new ChatMessageParser(emoticons);
- presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
- uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
-
- chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_);
- chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1));
- chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1));
- chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this));
-
- joinMUCWindow_ = NULL;
- mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_);
- mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
- ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
- whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2));
- whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted));
- whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated));
- whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected));
- roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
- roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
- roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
- roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this));
-
- settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1));
-
- userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
-
- setupBookmarks();
- loadRecents();
-
- autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_);
+ JID jid, StanzaChannel* stanzaChannel,
+ IQRouter* iqRouter,
+ EventController* eventController,
+ ChatWindowFactory* chatWindowFactory,
+ JoinMUCWindowFactory* joinMUCWindowFactory,
+ NickResolver* nickResolver,
+ PresenceOracle* presenceOracle,
+ PresenceSender* presenceSender,
+ UIEventStream* uiEventStream,
+#ifndef NOT_YET
+ ChatListWindowFactory* chatListWindowFactory,
+#endif
+ 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,
+ VCardManager* vcardManager,
+ Chattables& chattables) :
+ jid_(jid),
+ joinMUCWindowFactory_(joinMUCWindowFactory),
+ useDelayForLatency_(useDelayForLatency),
+ mucRegistry_(mucRegistry),
+ entityCapsProvider_(entityCapsProvider),
+ mucManager(mucManager),
+ ftOverview_(ftOverview),
+ roster_(roster),
+ eagleMode_(eagleMode),
+ settings_(settings),
+ historyController_(historyController),
+ whiteboardManager_(whiteboardManager),
+ highlightManager_(highlightManager),
+ emoticons_(emoticons),
+ clientBlockListManager_(clientBlockListManager),
+ vcardManager_(vcardManager),
+ chattables_(chattables) {
+ timerFactory_ = timerFactory;
+ eventController_ = eventController;
+ stanzaChannel_ = stanzaChannel;
+ iqRouter_ = iqRouter;
+ chatWindowFactory_ = chatWindowFactory;
+ nickResolver_ = nickResolver;
+ presenceOracle_ = presenceOracle;
+ avatarManager_ = nullptr;
+ serverDiscoInfo_ = std::make_shared<DiscoInfo>();
+ presenceSender_ = presenceSender;
+ uiEventStream_ = uiEventStream;
+ mucBookmarkManager_ = nullptr;
+ profileSettings_ = profileSettings;
+ presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
+ uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
+#ifndef NOT_YET
+ chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_);
+ chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1));
+ chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1));
+ chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this));
+#endif
+ joinMUCWindow_ = nullptr;
+ mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_);
+ mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
+ ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
+ whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2));
+ whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted));
+ whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated));
+ whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected));
+ roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
+ roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
+ roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
+ roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this));
+
+ chattables_.onActivated.connect(boost::bind(&ChatsManager::handleChattableActivated, this, _1));
+
+ settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1));
+
+ userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
+
+ setupBookmarks();
+#ifndef NOT_YET
+ loadRecents();
+#endif
+ autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_);
}
ChatsManager::~ChatsManager() {
- settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1));
- roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
- roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
- roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
- roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this));
- delete joinMUCWindow_;
- foreach (JIDChatControllerPair controllerPair, chatControllers_) {
- delete controllerPair.second;
- }
- foreach (JIDMUCControllerPair controllerPair, mucControllers_) {
- delete controllerPair.second;
- }
- delete mucBookmarkManager_;
- delete mucSearchController_;
- delete chatMessageParser_;
- delete autoAcceptMUCInviteDecider_;
+ settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1));
+ chattables_.onActivated.disconnect(boost::bind(&ChatsManager::handleChattableActivated, this, _1));
+ roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
+ roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
+ roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
+ roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this));
+ ftOverview_->onNewFileTransferController.disconnect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
+ delete joinMUCWindow_;
+ SWIFT_LOG(debug) << "Destroying ChatsManager, containing " << chatControllers_.size() << " chats and " << mucControllers_.size() << " MUCs";
+ for (JIDChatControllerPair controllerPair : chatControllers_) {
+ delete controllerPair.second;
+ }
+ for (JIDMUCControllerPair controllerPair : mucControllers_) {
+ delete controllerPair.second;
+ }
+ delete mucBookmarkManager_;
+ delete mucSearchController_;
+ delete autoAcceptMUCInviteDecider_;
}
+#ifndef NOT_YET
void ChatsManager::saveRecents() {
- std::stringstream serializeStream;
- boost::archive::text_oarchive oa(serializeStream);
- std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
- if (recentsLimited.size() > 25) {
- recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end());
- }
- if (eagleMode_) {
- foreach(ChatListWindow::Chat& chat, recentsLimited) {
- chat.activity = "";
- }
- }
-
- oa << recentsLimited;
- std::string serializedStr = Base64::encode(createByteArray(serializeStream.str()));
- profileSettings_->storeString(RECENT_CHATS, serializedStr);
+ std::stringstream serializeStream;
+ boost::archive::text_oarchive oa(serializeStream);
+ std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
+ if (recentsLimited.size() > 25) {
+ recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end());
+ }
+ if (eagleMode_) {
+ for (ChatListWindow::Chat& chat : recentsLimited) {
+ chat.activity = "";
+ }
+ }
+
+ class RemoveRecent {
+ public:
+ static bool ifPrivateMessage(const ChatListWindow::Chat& chat) {
+ return chat.isPrivateMessage;
+ }
+ };
+
+ recentsLimited.erase(std::remove_if(recentsLimited.begin(), recentsLimited.end(), RemoveRecent::ifPrivateMessage), recentsLimited.end());
+
+ oa & recentsLimited;
+ std::string serializedStr = Base64::encode(createByteArray(serializeStream.str()));
+ profileSettings_->storeString(RECENT_CHATS, serializedStr);
}
void ChatsManager::handleClearRecentsRequested() {
- recentChats_.clear();
- saveRecents();
- handleUnreadCountChanged(NULL);
+ recentChats_.clear();
+ saveRecents();
+ handleUnreadCountChanged(nullptr);
}
+#endif
void ChatsManager::handleJIDAddedToRoster(const JID &jid) {
- updatePresenceReceivingStateOnChatController(jid);
+ updatePresenceReceivingStateOnChatController(jid);
}
void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) {
- updatePresenceReceivingStateOnChatController(jid);
+ updatePresenceReceivingStateOnChatController(jid);
}
void ChatsManager::handleJIDUpdatedInRoster(const JID &jid) {
- updatePresenceReceivingStateOnChatController(jid);
+ updatePresenceReceivingStateOnChatController(jid);
}
void ChatsManager::handleRosterCleared() {
- /* Setting that all chat controllers aren't receiving presence anymore;
- including MUC 1-to-1 chats due to the assumtion that this handler
- is only called on log out. */
- foreach(JIDChatControllerPair pair, chatControllers_) {
- pair.second->setContactIsReceivingPresence(false);
- }
+ /* Setting that all chat controllers aren't receiving presence anymore;
+ including MUC 1-to-1 chats due to the assumtion that this handler
+ is only called on log out. */
+ for (JIDChatControllerPair pair : chatControllers_) {
+ pair.second->setContactIsReceivingPresence(false);
+ }
}
void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) {
- ChatController* controller = getChatControllerIfExists(jid);
- if (controller) {
- if (!mucRegistry_->isMUC(jid.toBare())) {
- RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid);
- controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both);
- } else {
- controller->setContactIsReceivingPresence(true);
- }
- }
+ ChatController* controller = getChatControllerIfExists(jid);
+ if (controller) {
+ if (!mucRegistry_->isMUC(jid.toBare())) {
+ RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid);
+ controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both);
+ } else {
+ controller->setContactIsReceivingPresence(true);
+ }
+ }
}
+#ifndef NOT_YET
ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const {
- ChatListWindow::Chat fixedChat = chat;
- if (fixedChat.isMUC) {
- if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) {
- fixedChat.statusType = StatusShow::Online;
- }
- } else {
- if (avatarManager_) {
- fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid);
- }
- Presence::ref presence = presenceOracle_->getHighestPriorityPresence(fixedChat.jid.toBare());
- fixedChat.statusType = presence ? presence->getShow() : StatusShow::None;
- }
- return fixedChat;
+ ChatListWindow::Chat fixedChat = chat;
+ if (fixedChat.isMUC) {
+ if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) {
+ fixedChat.statusType = StatusShow::Online;
+ }
+ } else {
+ if (avatarManager_) {
+ fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid);
+ }
+ Presence::ref presence = presenceOracle_->getAccountPresence(fixedChat.jid.toBare());
+ fixedChat.statusType = presence ? presence->getShow() : StatusShow::None;
+ }
+ return fixedChat;
}
void ChatsManager::loadRecents() {
- std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
- if (recentsString.find("\t") != std::string::npos) {
- // old format
- std::vector<std::string> recents;
- boost::split(recents, recentsString, boost::is_any_of("\n"));
- int i = 0;
- foreach (std::string recentString, recents) {
- if (i++ > 30) {
- break;
- }
- std::vector<std::string> recent;
- boost::split(recent, recentString, boost::is_any_of("\t"));
- if (recent.size() < 4) {
- continue;
- }
- JID jid(recent[0]);
- if (!jid.isValid()) {
- continue;
- }
- std::string activity(recent[1]);
- bool isMUC = recent[2] == "true";
- std::string nick(recent[3]);
- StatusShow::Type type = StatusShow::None;
- boost::filesystem::path path;
-
- ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick);
- chat = updateChatStatusAndAvatarHelper(chat);
- prependRecent(chat);
- }
- } else if (!recentsString.empty()){
- // boost searilaize based format
- ByteArray debase64 = Base64::decode(recentsString);
- std::vector<ChatListWindow::Chat> recentChats;
- std::stringstream deserializeStream(std::string((const char*)debase64.data(), debase64.size()));
- try {
- boost::archive::text_iarchive ia(deserializeStream);
- ia >> recentChats;
- } catch (const boost::archive::archive_exception& e) {
- SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl;
- return;
- }
-
- foreach(ChatListWindow::Chat chat, recentChats) {
- chat.statusType = StatusShow::None;
- chat = updateChatStatusAndAvatarHelper(chat);
- prependRecent(chat);
- }
- }
- handleUnreadCountChanged(NULL);
+ std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
+ if (recentsString.find("\t") != std::string::npos) {
+ // old format
+ std::vector<std::string> recents;
+ boost::split(recents, recentsString, boost::is_any_of("\n"));
+ int i = 0;
+ for (std::string recentString : recents) {
+ if (i++ > 30) {
+ break;
+ }
+ std::vector<std::string> recent;
+ boost::split(recent, recentString, boost::is_any_of("\t"));
+ if (recent.size() < 4) {
+ continue;
+ }
+ JID jid(recent[0]);
+ if (!jid.isValid()) {
+ continue;
+ }
+ std::string activity(recent[1]);
+ bool isMUC = recent[2] == "true";
+ std::string nick(recent[3]);
+ StatusShow::Type type = StatusShow::None;
+ boost::filesystem::path path;
+
+ ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, false, nick);
+ chat = updateChatStatusAndAvatarHelper(chat);
+ prependRecent(chat);
+ }
+ } else if (!recentsString.empty()){
+ // boost searilaize based format
+ ByteArray debase64 = Base64::decode(recentsString);
+ std::vector<ChatListWindow::Chat> recentChats;
+ std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size()));
+ try {
+ boost::archive::text_iarchive ia(deserializeStream);
+ ia >> recentChats;
+ } catch (const boost::archive::archive_exception& e) {
+ SWIFT_LOG(debug) << "Failed to load recents: " << e.what();
+ return;
+ }
+ recentChats.erase(std::remove(recentChats.begin(), recentChats.end(), ChatListWindow::Chat()), recentChats.end());
+ for (auto chat : recentChats) {
+ chat.statusType = StatusShow::None;
+ chat = updateChatStatusAndAvatarHelper(chat);
+ prependRecent(chat);
+ }
+ }
+ handleUnreadCountChanged(nullptr);
}
+#endif
void ChatsManager::setupBookmarks() {
- if (!mucBookmarkManager_) {
- mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
- mucBookmarkManager_->onBookmarksReady.connect(boost::bind(&ChatsManager::handleBookmarksReady, this));
- mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&ChatsManager::handleMUCBookmarkAdded, this, _1));
- mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&ChatsManager::handleMUCBookmarkRemoved, this, _1));
-
- if (chatListWindow_) {
- chatListWindow_->setBookmarksEnabled(false);
- chatListWindow_->clearBookmarks();
- }
- }
+ if (!mucBookmarkManager_) {
+ mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
+ mucBookmarkManager_->onBookmarksReady.connect(boost::bind(&ChatsManager::handleBookmarksReady, this));
+ mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&ChatsManager::handleMUCBookmarkAdded, this, _1));
+ mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&ChatsManager::handleMUCBookmarkRemoved, this, _1));
+
+#ifndef NOT_YET
+ if (chatListWindow_) {
+ chatListWindow_->setBookmarksEnabled(false);
+ chatListWindow_->clearBookmarks();
+ }
+#endif
+ }
}
void ChatsManager::handleBookmarksReady() {
- if (chatListWindow_) {
- chatListWindow_->setBookmarksEnabled(true);
- }
+#ifndef NOT_YET
+ if (chatListWindow_) {
+ chatListWindow_->setBookmarksEnabled(true);
+ }
+#endif
}
void ChatsManager::handleMUCBookmarkAdded(const MUCBookmark& bookmark) {
- std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom());
- if (it == mucControllers_.end() && bookmark.getAutojoin()) {
- handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false );
- }
- chatListWindow_->addMUCBookmark(bookmark);
+ if (bookmark.getRoom().isBare() && !bookmark.getRoom().getNode().empty()) {
+ std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom());
+ if (it == mucControllers_.end() && bookmark.getAutojoin()) {
+ handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false);
+ }
+#ifndef NOT_YET
+ //Only one entry of the bookmark should exist
+ chatListWindow_->removeMUCBookmark(bookmark);
+ chatListWindow_->addMUCBookmark(bookmark);
+#endif
+ chattables_.addJID(bookmark.getRoom(), Chattables::State::Type::Room);
+ }
}
void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) {
- chatListWindow_->removeMUCBookmark(bookmark);
-}
-
-ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) {
- int unreadCount = 0;
- if (mucRegistry_->isMUC(jid)) {
- MUCController* controller = mucControllers_[jid.toBare()];
- StatusShow::Type type = StatusShow::None;
- std::string nick = "";
- if (controller) {
- unreadCount = controller->getUnreadCount();
- if (controller->isJoined()) {
- type = StatusShow::Online;
- }
- nick = controller->getNick();
-
- if (controller->isImpromptu()) {
- ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick);
- typedef std::pair<std::string, JID> StringJIDPair;
- std::map<std::string, JID> participants = controller->getParticipantJIDs();
- chat.impromptuJIDs = participants;
- return chat;
- }
- }
- return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick);
- } else {
- ChatController* controller = getChatControllerIfExists(jid, false);
- if (controller) {
- unreadCount = controller->getUnreadCount();
- }
- JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare();
- Presence::ref presence = presenceOracle_->getHighestPriorityPresence(bareishJID);
- StatusShow::Type type = presence ? presence->getShow() : StatusShow::None;
- boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path();
- return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false);
- }
+#ifndef NOT_YET
+ chatListWindow_->removeMUCBookmark(bookmark);
+#endif
}
+#ifndef NOT_YET
+ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage) {
+ size_t unreadCount = 0;
+ if (mucRegistry_->isMUC(jid)) {
+ MUCController* controller = mucControllers_[jid.toBare()];
+ StatusShow::Type type = StatusShow::None;
+ std::string nick = "";
+ std::string password = "";
+ if (controller) {
+ unreadCount = controller->getUnreadCount();
+ if (controller->isJoined()) {
+ type = StatusShow::Online;
+ }
+ nick = controller->getNick();
+
+ if (controller->getPassword()) {
+ password = *controller->getPassword();
+ }
+
+ if (controller->isImpromptu()) {
+ ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password);
+ std::map<std::string, JID> participants = controller->getParticipantJIDs();
+ chat.impromptuJIDs = participants;
+
+ std::map<JID, std::string> participantsNames;
+ for (auto& i : invitees_[jid]) {
+ participantsNames.emplace(i, roster_->getNameForJID(i));
+ }
+ chat.inviteesNames = participantsNames;
+ return chat;
+ }
+ }
+ return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password);
+ } else {
+ ChatController* controller = getChatControllerIfExists(jid, false);
+ if (controller) {
+ unreadCount = controller->getUnreadCount();
+ }
+ JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare();
+ Presence::ref presence = presenceOracle_->getAccountPresence(bareishJID);
+ StatusShow::Type type = presence ? presence->getShow() : StatusShow::None;
+ boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path();
+ return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false, privateMessage);
+ }
+}
+#endif
+
void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) {
- if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) {
- /* Don't include PMs in MUC rooms.*/
- return;
- }
- ChatListWindow::Chat chat = createChatListChatItem(jid, activity);
- /* FIXME: handle nick changes */
- appendRecent(chat);
- handleUnreadCountChanged(NULL);
- saveRecents();
+#ifndef NOT_YET
+ const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC;
+ ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage);
+ /* FIXME: handle nick changes */
+ appendRecent(chat);
+ handleUnreadCountChanged(nullptr);
+ saveRecents();
+
+ // Look up potential MUC controller and update title accordingly as people
+ // join impromptu chats.
+ if (mucControllers_.find(jid) != mucControllers_.end()) {
+ auto chatListWindowIter = std::find_if(recentChats_.begin(), recentChats_.end(), [&](const ChatListWindow::Chat& chatListWindow) { return jid == (chatListWindow.jid); });
+ if (chatListWindowIter != recentChats_.end() && (mucControllers_[jid]->isImpromptu() || !chatListWindowIter->impromptuJIDs.empty())) {
+ mucControllers_[jid]->setChatWindowTitle(chatListWindowIter->getTitle());
+ }
+ }
+#endif
}
+void ChatsManager::handleChatClosed(const JID& /*jid*/) {
+ cleanupPrivateMessageRecents();
+#ifndef NOT_YET
+ chatListWindow_->setRecents(recentChats_);
+#endif
+}
+
+#ifndef NOT_YET
+
void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) {
- int unreadTotal = 0;
- bool controllerIsMUC = dynamic_cast<MUCController*>(controller);
- bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare());
- foreach (ChatListWindow::Chat& chatItem, recentChats_) {
- bool match = false;
- if (controller) {
- /* Matching MUC item */
- match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
- /* Matching PM */
- match |= isPM && chatItem.jid == controller->getToJID();
- /* Matching non-PM */
- match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
- }
- if (match) {
- chatItem.setUnreadCount(controller->getUnreadCount());
- }
- unreadTotal += chatItem.unreadCount;
- }
- chatListWindow_->setRecents(recentChats_);
- chatListWindow_->setUnreadCount(unreadTotal);
+ size_t unreadTotal = 0;
+ bool controllerIsMUC = dynamic_cast<MUCController*>(controller);
+ bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare());
+ for (ChatListWindow::Chat& chatItem : recentChats_) {
+ bool match = false;
+ if (controller) {
+ /* Matching MUC item */
+ match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
+ /* Matching PM */
+ match |= isPM && chatItem.jid == controller->getToJID();
+ /* Matching non-PM */
+ match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
+ }
+ if (match) {
+ chatItem.setUnreadCount(controller->getUnreadCount());
+ }
+ unreadTotal += chatItem.unreadCount;
+ }
+ chatListWindow_->setRecents(recentChats_);
+ chatListWindow_->setUnreadCount(unreadTotal);
}
boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const ChatListWindow::Chat& chat) {
- std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat);
- if (result != recentChats_.end()) {
- ChatListWindow::Chat existingChat = *result;
- recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
- return boost::optional<ChatListWindow::Chat>(existingChat);
- } else {
- return boost::optional<ChatListWindow::Chat>();
- }
+ std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat);
+ if (result != recentChats_.end()) {
+ ChatListWindow::Chat existingChat = *result;
+ recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
+ return boost::optional<ChatListWindow::Chat>(existingChat);
+ } else {
+ return boost::optional<ChatListWindow::Chat>();
+ }
+}
+#endif
+
+#ifndef NOT_YET
+void ChatsManager::cleanupPrivateMessageRecents() {
+ /* if we leave a MUC and close a PM, remove it's recent chat entry */
+ const std::list<ChatListWindow::Chat> chats = recentChats_;
+ for (const ChatListWindow::Chat& chat : chats) {
+ if (chat.isPrivateMessage) {
+ typedef std::map<JID, MUCController*> ControllerMap;
+ ControllerMap::iterator muc = mucControllers_.find(chat.jid.toBare());
+ if (muc == mucControllers_.end() || !muc->second->isJoined()) {
+ ChatController* chatController = getChatControllerIfExists(chat.jid);
+ if (!chatController || !chatController->hasOpenWindow()) {
+ removeExistingChat(chat);
+ break;
+ }
+ }
+ }
+ }
}
void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) {
- boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
- ChatListWindow::Chat mergedChat = chat;
- if (oldChat && !oldChat->impromptuJIDs.empty()) {
- mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
- }
- recentChats_.push_front(mergedChat);
+ boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
+ ChatListWindow::Chat mergedChat = chat;
+ if (oldChat) {
+ mergedChat.inviteesNames.insert(oldChat->inviteesNames.begin(), oldChat->inviteesNames.end());
+ mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
+ }
+ recentChats_.push_front(mergedChat);
}
void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) {
- boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
- ChatListWindow::Chat mergedChat = chat;
- if (oldChat && !oldChat->impromptuJIDs.empty()) {
- mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
- }
- recentChats_.push_back(mergedChat);
+ boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
+ ChatListWindow::Chat mergedChat = chat;
+ if (oldChat) {
+ mergedChat.inviteesNames.insert(oldChat->inviteesNames.begin(), oldChat->inviteesNames.end());
+ mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
+ }
+ recentChats_.push_back(mergedChat);
}
+#endif
void ChatsManager::handleUserLeftMUC(MUCController* mucController) {
- std::map<JID, MUCController*>::iterator it;
- for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) {
- if ((*it).second == mucController) {
- foreach (ChatListWindow::Chat& chat, recentChats_) {
- if (chat.isMUC && chat.jid == (*it).first) {
- chat.statusType = StatusShow::None;
- chatListWindow_->setRecents(recentChats_);
- break;
- }
- }
- mucControllers_.erase(it);
- delete mucController;
- return;
- }
- }
+ std::map<JID, MUCController*>::iterator it;
+ for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) {
+ if ((*it).second == mucController) {
+#ifndef NOT_YET
+ for (ChatListWindow::Chat& chat : recentChats_) {
+ if (chat.isMUC && chat.jid == (*it).first) {
+ chat.statusType = StatusShow::None;
+ }
+ }
+#endif
+ const auto& jid = it->first;
+ auto state = chattables_.getState(jid);
+ state.status = StatusShow::None;
+ chattables_.setState(jid, state);
+ //If user deletes bookmark from chatListWindow_ and then decides to leave the room, or if the server doesn't support bookmarks, the bookmark will not exist.
+ if (auto bookmarkFound = mucBookmarkManager_->lookupBookmark(jid)) {
+ MUCBookmark newBookmark(bookmarkFound.get());
+ newBookmark.setAutojoin(false);
+ mucBookmarkManager_->replaceBookmark(bookmarkFound.get(), newBookmark);
+ }
+ mucControllers_.erase(it);
+ delete mucController;
+ break;
+ }
+ }
+ cleanupPrivateMessageRecents();
+#ifndef NOT_YET
+ chatListWindow_->setRecents(recentChats_);
+#endif
}
void ChatsManager::handleSettingChanged(const std::string& settingPath) {
- if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) {
- userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
- return;
- }
+ if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) {
+ userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
+ return;
+ }
}
void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID) {
- // send impromptu invites for the new MUC
- std::vector<JID> missingJIDsToInvite = jidsToInvite;
-
- typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
- std::map<std::string, MUCOccupant> occupants = muc->getOccupants();
- foreach(StringMUCOccupantPair occupant, occupants) {
- boost::optional<JID> realJID = occupant.second.getRealJID();
- if (realJID) {
- missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end());
- }
- }
-
- if (reuseChatJID) {
- muc->invitePerson(reuseChatJID.get(), reason, true, true);
- }
- foreach(const JID& jid, missingJIDsToInvite) {
- muc->invitePerson(jid, reason, true);
- }
-}
-
-void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
- boost::shared_ptr<RequestChatUIEvent> chatEvent = boost::dynamic_pointer_cast<RequestChatUIEvent>(event);
- if (chatEvent) {
- handleChatRequest(chatEvent->getContact());
- return;
- }
- boost::shared_ptr<RemoveMUCBookmarkUIEvent> removeMUCBookmarkEvent = boost::dynamic_pointer_cast<RemoveMUCBookmarkUIEvent>(event);
- if (removeMUCBookmarkEvent) {
- mucBookmarkManager_->removeBookmark(removeMUCBookmarkEvent->getBookmark());
- return;
- }
- boost::shared_ptr<AddMUCBookmarkUIEvent> addMUCBookmarkEvent = boost::dynamic_pointer_cast<AddMUCBookmarkUIEvent>(event);
- if (addMUCBookmarkEvent) {
- mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark());
- return;
- }
-
- boost::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = boost::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event);
- if (createImpromptuMUCEvent) {
- assert(!localMUCServiceJID_.toString().empty());
- // create new muc
- JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID();
-
- // join muc
- MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true);
- mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>()));
- mucControllers_[roomJID]->activateChatWindow();
- }
-
- boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event);
- if (editMUCBookmarkEvent) {
- mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark());
- }
- else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
- handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu());
- mucControllers_[joinEvent->getJID()]->activateChatWindow();
- }
- else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
- if (!joinMUCWindow_) {
- joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_);
- joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
- }
- joinMUCWindow_->setMUC(joinEvent->getRoom());
- joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
- joinMUCWindow_->show();
- }
+ // send impromptu invites for the new MUC
+ std::vector<JID> missingJIDsToInvite = jidsToInvite;
+
+ typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
+ std::map<std::string, MUCOccupant> occupants = muc->getOccupants();
+ for (StringMUCOccupantPair occupant : occupants) {
+ boost::optional<JID> realJID = occupant.second.getRealJID();
+ if (realJID) {
+ missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end());
+ }
+ }
+
+ if (reuseChatJID) {
+ muc->invitePerson(reuseChatJID.get(), reason, true, true);
+ }
+ for (const JID& jid : missingJIDsToInvite) {
+ muc->invitePerson(jid, reason, true);
+ }
+}
+
+void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) {
+ std::shared_ptr<RequestChatUIEvent> chatEvent = std::dynamic_pointer_cast<RequestChatUIEvent>(event);
+ if (chatEvent) {
+ handleChatRequest(chatEvent->getContact());
+ return;
+ }
+ std::shared_ptr<RemoveMUCBookmarkUIEvent> removeMUCBookmarkEvent = std::dynamic_pointer_cast<RemoveMUCBookmarkUIEvent>(event);
+ if (removeMUCBookmarkEvent) {
+ mucBookmarkManager_->removeBookmark(removeMUCBookmarkEvent->getBookmark());
+ return;
+ }
+ std::shared_ptr<AddMUCBookmarkUIEvent> addMUCBookmarkEvent = std::dynamic_pointer_cast<AddMUCBookmarkUIEvent>(event);
+ if (addMUCBookmarkEvent) {
+ mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark());
+ return;
+ }
+ std::shared_ptr<SendFileUIEvent> sendFileEvent = std::dynamic_pointer_cast<SendFileUIEvent>(event);
+ if (sendFileEvent) {
+ JID fileReceiver = sendFileEvent->getJID();
+ if (fileReceiver.isBare()) {
+ // See if there is a chat controller for a conversation with a bound
+ // full JID. Check if this JID supports file transfer and use it instead
+ // of the bare JID.
+ ChatController* controller = getChatControllerIfExists(fileReceiver, false);
+ if (controller) {
+ JID controllerJID = controller->getToJID();
+ if (!controllerJID.isBare() && (FeatureOracle(entityCapsProvider_, presenceOracle_).isFileTransferSupported(controllerJID) == Yes)) {
+ fileReceiver = controllerJID;
+ }
+ }
+ }
+ ftOverview_->sendFile(fileReceiver, sendFileEvent->getFilename());
+ return;
+ }
+
+ std::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = std::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event);
+ if (createImpromptuMUCEvent) {
+ assert(!localMUCServiceJID_.toString().empty());
+ // The room JID is random for new impromptu rooms, or a predefined JID for impromptu rooms resumed from the 'Recent chats' list.
+ JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID();
+
+ std::vector<JID> missingJIDsToInvite = createImpromptuMUCEvent->getJIDs();
+ for (const JID& jid : missingJIDsToInvite) {
+ invitees_[roomJID].insert(jid);
+ }
+ // join muc
+ MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), true, true);
+ mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>()));
+ mucControllers_[roomJID]->activateChatWindow();
+ }
+
+ std::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = std::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event);
+ if (editMUCBookmarkEvent) {
+ mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark());
+ }
+ else if (JoinMUCUIEvent::ref joinEvent = std::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
+ handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu());
+ mucControllers_[joinEvent->getJID()]->activateChatWindow();
+ }
+ else if (std::shared_ptr<RequestJoinMUCUIEvent> joinEvent = std::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
+ if (!joinMUCWindow_) {
+ joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_);
+ joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
+ }
+ joinMUCWindow_->setMUC(joinEvent->getRoom());
+ joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
+ joinMUCWindow_->show();
+ }
}
+#ifndef NOT_YET
void ChatsManager::markAllRecentsOffline() {
- foreach (ChatListWindow::Chat& chat, recentChats_) {
- chat.setStatusType(StatusShow::None);
- }
+ for (ChatListWindow::Chat& chat : recentChats_) {
+ chat.setStatusType(StatusShow::None);
+ }
- chatListWindow_->setRecents(recentChats_);
+ chatListWindow_->setRecents(recentChats_);
}
+#endif
void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) {
- JID reuseChatInvite = chatController->getToJID();
- chatControllers_.erase(chatController->getToJID());
- delete chatController;
+ JID reuseChatInvite = chatController->getToJID();
+ chatControllers_.erase(chatController->getToJID());
+ delete chatController;
- // join new impromptu muc
- assert(!localMUCServiceJID_.toString().empty());
+ // join new impromptu muc
+ assert(!localMUCServiceJID_.toString().empty());
- // create new muc
- JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_);
+ // create new muc
+ JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_);
- // join muc
- MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow);
- mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite)));
+ // join muc
+ MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), true, true, chatWindow);
+ mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite)));
}
/**
* If a resource goes offline, release bound chatdialog to that resource.
*/
-void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
- if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return;
-
- foreach (ChatListWindow::Chat& chat, recentChats_) {
- if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) {
- Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare());
- chat.setStatusType(presence ? presence->getShow() : StatusShow::None);
- chatListWindow_->setRecents(recentChats_);
- break;
- }
- }
-
- //if (newPresence->getType() != Presence::Unavailable) return;
- JID fullJID(newPresence->getFrom());
- std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
- if (it == chatControllers_.end()) return;
- JID bareJID(fullJID.toBare());
- //It doesn't make sense to have two unbound dialogs.
- if (chatControllers_.find(bareJID) != chatControllers_.end()) return;
- rebindControllerJID(fullJID, bareJID);
+void ChatsManager::handlePresenceChange(std::shared_ptr<Presence> newPresence) {
+ if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return;
+#ifndef NOT_YET
+ for (ChatListWindow::Chat& chat : recentChats_) {
+ if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) {
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare());
+ chat.setStatusType(presence ? presence->getShow() : StatusShow::None);
+ chatListWindow_->setRecents(recentChats_);
+ break;
+ }
+ }
+#endif
+ //if (newPresence->getType() != Presence::Unavailable) return;
+
+ JID fullJID(newPresence->getFrom());
+ std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
+ if (it == chatControllers_.end()) return;
+ JID bareJID(fullJID.toBare());
+ //It doesn't make sense to have two unbound dialogs.
+ if (chatControllers_.find(bareJID) != chatControllers_.end()) return;
+ rebindControllerJID(fullJID, bareJID);
}
void ChatsManager::setAvatarManager(AvatarManager* avatarManager) {
- if (avatarManager_) {
- avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
- }
- avatarManager_ = avatarManager;
- foreach (ChatListWindow::Chat& chat, recentChats_) {
- if (!chat.isMUC) {
- chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid));
- }
- }
- avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
+ if (avatarManager_) {
+ avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
+ }
+ avatarManager_ = avatarManager;
+#ifndef NOT_YET
+ for (ChatListWindow::Chat& chat : recentChats_) {
+ if (!chat.isMUC) {
+ chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid));
+ }
+ }
+#endif
+ avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
}
void ChatsManager::handleAvatarChanged(const JID& jid) {
- foreach (ChatListWindow::Chat& chat, recentChats_) {
- if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) {
- chat.setAvatarPath(avatarManager_->getAvatarPath(jid));
- break;
- }
- }
+#ifndef NOT_YET
+ for (ChatListWindow::Chat& chat : recentChats_) {
+ if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) {
+ chat.setAvatarPath(avatarManager_->getAvatarPath(jid));
+ break;
+ }
+ }
+#endif
}
-void ChatsManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) {
- serverDiscoInfo_ = info;
- foreach (JIDChatControllerPair pair, chatControllers_) {
- pair.second->setAvailableServerFeatures(info);
- }
- foreach (JIDMUCControllerPair pair, mucControllers_) {
- pair.second->setAvailableServerFeatures(info);
- }
+void ChatsManager::setServerDiscoInfo(std::shared_ptr<DiscoInfo> info) {
+ serverDiscoInfo_ = info;
+ for (JIDChatControllerPair pair : chatControllers_) {
+ pair.second->setAvailableServerFeatures(info);
+ }
+ for (JIDMUCControllerPair pair : mucControllers_) {
+ pair.second->setAvailableServerFeatures(info);
+ }
}
/**
* This is to be called on connect/disconnect.
- */
+ */
void ChatsManager::setOnline(bool enabled) {
- foreach (JIDChatControllerPair controllerPair, chatControllers_) {
- controllerPair.second->setOnline(enabled);
- }
- foreach (JIDMUCControllerPair controllerPair, mucControllers_) {
- controllerPair.second->setOnline(enabled);
- if (enabled) {
- controllerPair.second->rejoin();
- }
- }
- if (!enabled) {
- delete mucBookmarkManager_;
- mucBookmarkManager_ = NULL;
- chatListWindow_->setBookmarksEnabled(false);
- markAllRecentsOffline();
- } else {
- setupBookmarks();
- localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_);
- localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2));
- localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
- localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
- localMUCServiceFinderWalker_->beginWalk();
- }
-
+ for (JIDChatControllerPair controllerPair : chatControllers_) {
+ controllerPair.second->setOnline(enabled);
+ }
+ for (JIDMUCControllerPair controllerPair : mucControllers_) {
+ controllerPair.second->setOnline(enabled);
+ if (enabled) {
+ controllerPair.second->rejoin();
+ }
+ }
+ if (!enabled) {
+ markAllRecentsOffline();
+ } else {
+ setupBookmarks();
+ localMUCServiceJID_ = JID();
+ localMUCServiceFinderWalker_ = std::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_);
+ localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2));
+ localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
+ localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
+ localMUCServiceFinderWalker_->beginWalk();
+ }
+#ifndef NOT_YET
+ if (chatListWindow_) {
+ chatListWindow_->setBookmarksEnabled(enabled);
+ }
+#endif
}
void ChatsManager::handleChatRequest(const std::string &contact) {
- ChatController* controller = getChatControllerOrFindAnother(JID(contact));
- controller->activateChatWindow();
+ ChatController* controller = getChatControllerOrFindAnother(JID(contact));
+ controller->activateChatWindow();
}
ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) {
- ChatController* controller = getChatControllerIfExists(contact);
- if (!controller && !mucRegistry_->isMUC(contact.toBare())) {
- foreach (JIDChatControllerPair pair, chatControllers_) {
- if (pair.first.toBare() == contact.toBare()) {
- controller = pair.second;
- break;
- }
- }
- }
- return controller ? controller : createNewChatController(contact);
+ ChatController* controller = getChatControllerIfExists(contact);
+ if (!controller && !mucRegistry_->isMUC(contact.toBare())) {
+ for (JIDChatControllerPair pair : chatControllers_) {
+ if (pair.first.toBare() == contact.toBare()) {
+ controller = pair.second;
+ break;
+ }
+ }
+ }
+ return controller ? controller : createNewChatController(contact);
}
ChatController* ChatsManager::createNewChatController(const JID& contact) {
- assert(chatControllers_.find(contact) == chatControllers_.end());
- ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, autoAcceptMUCInviteDecider_);
- chatControllers_[contact] = controller;
- controller->setAvailableServerFeatures(serverDiscoInfo_);
- controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
- controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
- controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3));
- updatePresenceReceivingStateOnChatController(contact);
- controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty());
- return controller;
+ assert(chatControllers_.find(contact) == chatControllers_.end());
+ std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getConfiguration(), ChatMessageParser::Mode::Chat); /* a message parser that knows this is a chat (not a room/MUC) */
+ auto controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, timerFactory_, eventController_, entityCapsProvider_, userWantsReceipts_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_, settings_, chattables_);
+ chatControllers_[contact] = controller;
+ controller->setAvailableServerFeatures(serverDiscoInfo_);
+ controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
+ controller->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact));
+#ifndef NOT_YET
+ controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+#endif
+ controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3));
+ updatePresenceReceivingStateOnChatController(contact);
+ controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty());
+ return controller;
}
ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) {
- ChatController* controller = getChatControllerIfExists(contact);
- return controller ? controller : createNewChatController(contact);
+ ChatController* controller = getChatControllerIfExists(contact);
+ return controller ? controller : createNewChatController(contact);
}
ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) {
- if (chatControllers_.find(contact) == chatControllers_.end()) {
- if (mucRegistry_->isMUC(contact.toBare())) {
- return NULL;
- }
- //Need to look for an unbound window to bind first
- JID bare(contact.toBare());
- if (chatControllers_.find(bare) != chatControllers_.end()) {
- rebindControllerJID(bare, contact);
- } else {
- foreach (JIDChatControllerPair pair, chatControllers_) {
- if (pair.first.toBare() == contact.toBare()) {
- if (rebindIfNeeded) {
- rebindControllerJID(pair.first, contact);
- return chatControllers_[contact];
- } else {
- return pair.second;
- }
- }
- }
- return NULL;
- }
- }
- return chatControllers_[contact];
+ if (chatControllers_.find(contact) == chatControllers_.end()) {
+ if (mucRegistry_->isMUC(contact.toBare())) {
+ return nullptr;
+ }
+ //Need to look for an unbound window to bind first
+ JID bare(contact.toBare());
+ if (chatControllers_.find(bare) != chatControllers_.end()) {
+ if (rebindIfNeeded) {
+ rebindControllerJID(bare, contact);
+ }
+ else {
+ return chatControllers_[bare];
+ }
+ } else {
+ for (JIDChatControllerPair pair : chatControllers_) {
+ if (pair.first.toBare() == contact.toBare()) {
+ if (rebindIfNeeded) {
+ rebindControllerJID(pair.first, contact);
+ return chatControllers_[contact];
+ } else {
+ return pair.second;
+ }
+ }
+ }
+ return nullptr;
+ }
+ }
+ return chatControllers_[contact];
}
void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
- chatControllers_[to] = chatControllers_[from];
- chatControllers_.erase(from);
- chatControllers_[to]->setToJID(to);
-}
-
-MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) {
- MUC::ref muc;
- if (!stanzaChannel_->isAvailable()) {
- /* This is potentially not the optimal solution, but it will avoid consistency issues.*/
- return muc;
- }
- if (addAutoJoin) {
- MUCBookmark bookmark(mucJID, mucJID.getNode());
- bookmark.setAutojoin(true);
- if (nickMaybe) {
- bookmark.setNick(*nickMaybe);
- }
- if (password) {
- bookmark.setPassword(*password);
- }
- 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);
- }
- 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_);
- 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->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;
+ chatControllers_[to] = chatControllers_[from];
+ chatControllers_.erase(from);
+ chatControllers_[to]->setToJID(to);
+}
+
+MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) {
+ MUC::ref muc;
+ std::map<JID, MUCController*>::iterator it = mucControllers_.find(mucJID);
+ if (it != mucControllers_.end()) {
+ if (stanzaChannel_->isAvailable()) {
+ 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 = nullptr;
+ SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = nullptr;
+ if (reuseChatwindow) {
+ chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
+ }
+ std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat); /* 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_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_, settings_, chattables_);
+ chattables_.addJID(muc->getJID(), Chattables::State::Type::Room);
+ auto state = chattables_.getState(muc->getJID());
+ state.status = StatusShow::Online;
+ chattables_.setState(muc->getJID(), state);
+ 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));
+#ifndef NOT_YET
+ controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+#endif
+ if (!stanzaChannel_->isAvailable()) {
+ /* When online, the MUC is added to the registry in MUCImpl::internalJoin. This method is not
+ * called when Swift is offline, so we add it here as only MUCs in the registry are rejoined
+ * when going back online.
+ */
+ mucRegistry_->addMUC(mucJID.toBare());
+ }
+ handleChatActivity(mucJID.toBare(), "", true);
+ }
+#ifndef NOT_YET
+ auto chatListWindowIter = std::find_if(recentChats_.begin(), recentChats_.end(), [&](const ChatListWindow::Chat& chatListWindow) { return mucJID == (chatListWindow.jid); });
+ if (chatListWindowIter != recentChats_.end() && (mucControllers_[mucJID]->isImpromptu() || !chatListWindowIter->impromptuJIDs.empty())) {
+ mucControllers_[mucJID]->setChatWindowTitle(chatListWindowIter->getTitle());
+ }
+#endif
+ if (auto existingBookmark = mucBookmarkManager_->lookupBookmark(mucJID)) {
+ if (!existingBookmark->getAutojoin()) {
+ MUCBookmark newbookmark(existingBookmark.get());
+ newbookmark.setAutojoin(true);
+ mucBookmarkManager_->replaceBookmark(*existingBookmark, newbookmark);
+ }
+ }
+ else {
+ MUCBookmark bookmark(mucJID, mucJID.getNode());
+ bookmark.setAutojoin(true);
+ if (nickMaybe) {
+ bookmark.setNick(*nickMaybe);
+ }
+ if (password) {
+ bookmark.setPassword(*password);
+ }
+ mucBookmarkManager_->addBookmark(bookmark);
+ }
+ mucControllers_[mucJID]->showChatWindow();
+ return muc;
}
void ChatsManager::handleSearchMUCRequest() {
- mucSearchController_->openSearchWindow();
-}
-
-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;
- handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window);
- }
- } else {
- handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true);
- return;
- }
- }
-
- //if not a mucroom
- if (!event->isReadable() && !isInvite && !isMediatedInvite) {
- /* Only route such messages if a window exists, don't open new windows for them.*/
- ChatController* controller = getChatControllerIfExists(jid);
- if (controller) {
- controller->handleIncomingMessage(event);
- }
- } else {
- getChatControllerOrCreate(jid)->handleIncomingMessage(event);
- }
+ 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;
+
+ // 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));
+ }
+}
+
+bool ChatsManager::messageCausesSessionBinding(std::shared_ptr<Message> message) {
+ bool causesRebind = false;
+ ChatState::ref chatState = message->getPayload<ChatState>();
+ if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) {
+ causesRebind = true;
+ }
+ return causesRebind;
+}
+
+void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> incomingMessage) {
+ std::shared_ptr<Message> message = incomingMessage;
+ if (message->getFrom().toBare() == jid_.toBare()) {
+ CarbonsReceived::ref carbonsReceived;
+ CarbonsSent::ref carbonsSent;
+ Forwarded::ref forwarded;
+ Message::ref forwardedMessage;
+ if ((carbonsReceived = incomingMessage->getPayload<CarbonsReceived>()) &&
+ (forwarded = carbonsReceived->getForwarded()) &&
+ (forwardedMessage = std::dynamic_pointer_cast<Message>(forwarded->getStanza()))) {
+ message = forwardedMessage;
+ }
+ else if ((carbonsSent = incomingMessage->getPayload<CarbonsSent>()) &&
+ (forwarded = carbonsSent->getForwarded()) &&
+ (forwardedMessage = std::dynamic_pointer_cast<Message>(forwarded->getStanza()))) {
+ JID toJID = forwardedMessage->getTo();
+
+ ChatController* controller = getChatControllerOrCreate(toJID);
+ if (controller) {
+ controller->handleIncomingOwnMessage(forwardedMessage);
+ }
+ else {
+ SWIFT_LOG(error) << "Carbons message ignored.";
+ }
+ return;
+ }
+ }
+ JID fromJID = message->getFrom();
+
+ std::shared_ptr<MessageEvent> event(new MessageEvent(message));
+ bool isInvite = !!message->getPayload<MUCInvitationPayload>();
+ bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite());
+ if (isMediatedInvite) {
+ fromJID = (*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 MUC errors to a MUC PM window if a suitable window is open.
+ if (message->getType() == Message::Error) {
+ auto controller = getChatControllerIfExists(fromJID, messageCausesSessionBinding(message));
+ if (controller) {
+ controller->handleIncomingMessage(event);
+ return;
+ }
+ }
+
+ // Try to deliver it to a MUC.
+ if (message->getType() == Message::Groupchat || message->getType() == Message::Error) {
+ // Try to deliver it to a MUC room.
+ std::map<JID, MUCController*>::iterator i = mucControllers_.find(fromJID.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(fromJID);
+ if (controller) {
+ ChatWindow* window = controller->detachChatWindow();
+ chatControllers_.erase(fromJID);
+ delete controller;
+ handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, true, window);
+ return;
+ }
+ } else {
+ handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, true);
+ return;
+ }
+ }
+
+ //if not a mucroom
+ if (!event->isReadable() && !isInvite && !isMediatedInvite) {
+ /* Only route such messages if a window exists, don't open new windows for them.*/
+
+ // Do not bind a controller to a full JID, for delivery receipts or chat state notifications.
+ ChatController* controller = getChatControllerIfExists(fromJID, messageCausesSessionBinding(message));
+ if (controller) {
+ controller->handleIncomingMessage(event);
+ }
+ } else {
+ getChatControllerOrCreate(fromJID)->handleIncomingMessage(event);
+ }
}
void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) {
- if (joinMUCWindow_) {
- joinMUCWindow_->setMUC(muc.toString());
- }
+ if (joinMUCWindow_) {
+ joinMUCWindow_->setMUC(muc.toString());
+ }
}
void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) {
- uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getPassword(), mucBookmark.getNick()));
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getPassword(), mucBookmark.getNick()));
}
void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) {
- ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty());
- chatController->handleNewFileTransferController(ftc);
- chatController->activateChatWindow();
+ ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty());
+ chatController->handleNewFileTransferController(ftc);
+ if (!ftc->isIncoming()) {
+ chatController->activateChatWindow();
+ }
}
void ChatsManager::handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf) {
- ChatController* chatController = getChatControllerOrCreate(contact);
- chatController->handleWhiteboardSessionRequest(senderIsSelf);
- chatController->activateChatWindow();
+ ChatController* chatController = getChatControllerOrCreate(contact);
+ chatController->handleWhiteboardSessionRequest(senderIsSelf);
+ chatController->activateChatWindow();
}
void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state) {
- ChatController* chatController = getChatControllerOrCreate(contact);
- chatController->handleWhiteboardStateChange(state);
- chatController->activateChatWindow();
- if (state == ChatWindow::WhiteboardAccepted) {
- boost::filesystem::path path;
- JID bareJID = contact.toBare();
- if (avatarManager_) {
- path = avatarManager_->getAvatarPath(bareJID);
- }
- ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false);
- chatListWindow_->addWhiteboardSession(chat);
- } else {
- chatListWindow_->removeWhiteboardSession(contact.toBare());
- }
+ ChatController* chatController = getChatControllerOrCreate(contact);
+ chatController->handleWhiteboardStateChange(state);
+ chatController->activateChatWindow();
+ if (state == ChatWindow::WhiteboardAccepted) {
+ boost::filesystem::path path;
+ JID bareJID = contact.toBare();
+ if (avatarManager_) {
+ path = avatarManager_->getAvatarPath(bareJID);
+ }
+ ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false);
+ chatListWindow_->addWhiteboardSession(chat);
+ } else {
+ chatListWindow_->removeWhiteboardSession(contact.toBare());
+ }
}
-
+#ifndef NOT_YET
void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) {
- if (chat.isMUC && !chat.impromptuJIDs.empty()) {
- typedef std::pair<std::string, JID> StringJIDPair;
- std::vector<JID> inviteJIDs;
- foreach(StringJIDPair pair, chat.impromptuJIDs) {
- inviteJIDs.push_back(pair.second);
- }
- uiEventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, ""));
- }
- else if (chat.isMUC) {
- /* FIXME: This means that recents requiring passwords will just flat-out not work */
- uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick));
- }
- else {
- uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(chat.jid));
- }
-}
-
-void ChatsManager::handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info) {
- foreach (DiscoInfo::Identity identity, info->getIdentities()) {
- if ((identity.getCategory() == "directory"
- && identity.getType() == "chatroom")
- || (identity.getCategory() == "conference"
- && identity.getType() == "text")) {
- localMUCServiceJID_ = service;
- localMUCServiceFinderWalker_->endWalk();
- SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl;
- break;
- }
- }
+ if (chat.isMUC && !chat.impromptuJIDs.empty()) {
+ typedef std::pair<std::string, JID> StringJIDPair;
+ std::vector<JID> inviteJIDs;
+ for (StringJIDPair pair : chat.impromptuJIDs) {
+ inviteJIDs.push_back(pair.second);
+ }
+ uiEventStream_->send(std::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, ""));
+ }
+ else if (chat.isMUC) {
+ bool isImpromptu = (!chat.inviteesNames.empty() || !chat.impromptuJIDs.empty());
+ /* FIXME: This means that recents requiring passwords will just flat-out not work */
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick, false, isImpromptu));
+ }
+ else {
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(chat.jid));
+ }
+}
+
+#endif
+
+void ChatsManager::handleChattableActivated(const JID& jid) {
+ auto state = chattables_.getState(jid);
+ if (state.type == Chattables::State::Type::Person) {
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(jid));
+ }
+ else if (state.type == Chattables::State::Type::Room) {
+ if (auto foundBookmark = mucBookmarkManager_->lookupBookmark(jid)) {
+ handleMUCBookmarkActivated(foundBookmark.get());
+ }
+ else {
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(jid, boost::optional<std::string>(), boost::optional<std::string>())); // Just a quick hack to reuse already open MUCs
+ }
+ }
+}
+
+void ChatsManager::handleLocalServiceFound(const JID& service, std::shared_ptr<DiscoInfo> info) {
+ for (DiscoInfo::Identity identity : info->getIdentities()) {
+ if ((identity.getCategory() == "directory"
+ && identity.getType() == "chatroom")
+ || (identity.getCategory() == "conference"
+ && identity.getType() == "text")) {
+ localMUCServiceJID_ = service;
+ localMUCServiceFinderWalker_->endWalk();
+ SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_;
+ break;
+ }
+ }
}
void ChatsManager::handleLocalServiceWalkFinished() {
- onImpromptuMUCServiceDiscovered(!localMUCServiceJID_.toString().empty());
+ bool impromptuMUCSupported = !localMUCServiceJID_.toString().empty();
+ for (JIDChatControllerPair controllerPair : chatControllers_) {
+ controllerPair.second->setCanStartImpromptuChats(impromptuMUCSupported);
+ }
+ for (JIDMUCControllerPair controllerPair : mucControllers_) {
+ controllerPair.second->setCanStartImpromptuChats(impromptuMUCSupported);
+ }
+ onImpromptuMUCServiceDiscovered(impromptuMUCSupported);
}
+#ifndef NOT_YET
std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const {
- return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
+ return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
}
-
-std::vector<Contact> Swift::ChatsManager::getContacts() {
- std::vector<Contact> result;
- foreach (ChatListWindow::Chat chat, recentChats_) {
- if (!chat.isMUC) {
- result.push_back(Contact(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath));
- }
- }
- return result;
+#endif
+
+std::vector<Contact::ref> Swift::ChatsManager::getContacts(bool withMUCNicks) {
+ std::vector<Contact::ref> result;
+#ifndef NOT_YET
+ for (ChatListWindow::Chat chat : recentChats_) {
+ if (!chat.isMUC) {
+ result.push_back(std::make_shared<Contact>(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath));
+ }
+ }
+#endif
+ if (withMUCNicks) {
+ /* collect MUC nicks */
+ typedef std::map<JID, MUCController*>::value_type Item;
+ for (const Item& item : mucControllers_) {
+ JID mucJID = item.second->getToJID();
+ std::map<std::string, JID> participants = item.second->getParticipantJIDs();
+ typedef std::map<std::string, JID>::value_type ParticipantType;
+ for (const ParticipantType& participant : participants) {
+ const JID nickJID = JID(mucJID.getNode(), mucJID.getDomain(), participant.first);
+ Presence::ref presence = presenceOracle_->getLastPresence(nickJID);
+ const boost::filesystem::path avatar = avatarManager_->getAvatarPath(nickJID);
+ result.push_back(std::make_shared<Contact>(participant.first, JID(), presence->getShow(), avatar));
+ }
+ }
+ }
+ return result;
}
ChatsManager::SingleChatWindowFactoryAdapter::SingleChatWindowFactoryAdapter(ChatWindow* chatWindow) : chatWindow_(chatWindow) {}
ChatsManager::SingleChatWindowFactoryAdapter::~SingleChatWindowFactoryAdapter() {}
ChatWindow* ChatsManager::SingleChatWindowFactoryAdapter::createChatWindow(const JID &, UIEventStream*) {
- return chatWindow_;
+ return chatWindow_;
}
}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index c9dd856..e120831 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -1,25 +1,24 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
#include <map>
+#include <memory>
#include <string>
-
-#include <boost/shared_ptr.hpp>
+#include <set>
#include <Swiften/Base/IDGenerator.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/Elements/Message.h>
#include <Swiften/Elements/Presence.h>
#include <Swiften/JID/JID.h>
-#include <Swiften/MUC/MUCRegistry.h>
-#include <Swiften/MUC/MUCBookmark.h>
#include <Swiften/MUC/MUC.h>
-
+#include <Swiften/MUC/MUCBookmark.h>
+#include <Swiften/MUC/MUCRegistry.h>
#include <Swift/Controllers/ContactProvider.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
@@ -27,158 +26,183 @@
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
-
namespace Swift {
- class EventController;
- class ChatController;
- class ChatControllerBase;
- class MUCController;
- class MUCManager;
- class JoinMUCWindow;
- class JoinMUCWindowFactory;
- class NickResolver;
- class PresenceOracle;
- class AvatarManager;
- class StanzaChannel;
- class IQRouter;
- class PresenceSender;
- class MUCBookmarkManager;
- class ChatListWindowFactory;
- class TimerFactory;
- class EntityCapsProvider;
- class DirectedPresenceSender;
- class MUCSearchWindowFactory;
- class ProfileSettingsProvider;
- class MUCSearchController;
- class FileTransferOverview;
- class FileTransferController;
- class XMPPRoster;
- class SettingsProvider;
- class WhiteboardManager;
- class HistoryController;
- class HighlightManager;
- class ClientBlockListManager;
- class ChatMessageParser;
- class DiscoServiceWalker;
- class AutoAcceptMUCInviteDecider;
- class UserSearchController;
-
- class ChatsManager : public ContactProvider {
- public:
- 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);
- 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> getContacts();
-
- 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);
- 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 handleBookmarksReady();
- void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC);
- 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 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);
- ChatController* createNewChatController(const JID &contact);
- ChatController* getChatControllerOrCreate(const JID &contact);
- ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true);
-
- private:
- std::map<JID, MUCController*> mucControllers_;
- std::map<JID, ChatController*> chatControllers_;
- std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_;
- EventController* eventController_;
- JID jid_;
- StanzaChannel* stanzaChannel_;
- IQRouter* iqRouter_;
- ChatWindowFactory* chatWindowFactory_;
- JoinMUCWindowFactory* joinMUCWindowFactory_;
- NickResolver* nickResolver_;
- PresenceOracle* presenceOracle_;
- AvatarManager* avatarManager_;
- PresenceSender* presenceSender_;
- UIEventStream* uiEventStream_;
- MUCBookmarkManager* mucBookmarkManager_;
- boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
- ChatListWindow* chatListWindow_;
- JoinMUCWindow* joinMUCWindow_;
- boost::bsignals::scoped_connection uiEventConnection_;
- bool useDelayForLatency_;
- TimerFactory* timerFactory_;
- MUCRegistry* mucRegistry_;
- EntityCapsProvider* entityCapsProvider_;
- MUCManager* mucManager;
- MUCSearchController* mucSearchController_;
- std::list<ChatListWindow::Chat> recentChats_;
- ProfileSettingsProvider* profileSettings_;
- FileTransferOverview* ftOverview_;
- XMPPRoster* roster_;
- bool eagleMode_;
- bool userWantsReceipts_;
- SettingsProvider* settings_;
- HistoryController* historyController_;
- WhiteboardManager* whiteboardManager_;
- HighlightManager* highlightManager_;
- ClientBlockListManager* clientBlockListManager_;
- ChatMessageParser* chatMessageParser_;
- JID localMUCServiceJID_;
- boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_;
- AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
- UserSearchController* inviteUserSearchController_;
- IDGenerator idGenerator_;
- };
+ class AutoAcceptMUCInviteDecider;
+ class AvatarManager;
+ class ChatController;
+ class ChatControllerBase;
+ class ChatListWindowFactory;
+ class ChatMessageParser;
+ class Chattables;
+ class ClientBlockListManager;
+ class DirectedPresenceSender;
+ class DiscoServiceWalker;
+ class EntityCapsProvider;
+ class EventController;
+ class FileTransferController;
+ class FileTransferOverview;
+ class HighlightManager;
+ class HistoryController;
+ class IQRouter;
+ class JoinMUCWindow;
+ class JoinMUCWindowFactory;
+ class MUCBookmarkManager;
+ class MUCController;
+ class MUCManager;
+ class MUCSearchController;
+ class MUCSearchWindowFactory;
+ class NickResolver;
+ class PresenceOracle;
+ class PresenceSender;
+ class ProfileSettingsProvider;
+ class SettingsProvider;
+ class StanzaChannel;
+ class TimerFactory;
+ class VCardManager;
+ class WhiteboardManager;
+ class XMPPRoster;
+
+ class ChatsManager : public ContactProvider {
+ public:
+ 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, VCardManager* vcardManager, Chattables& chattables);
+ virtual ~ChatsManager();
+ void setAvatarManager(AvatarManager* avatarManager);
+ void setOnline(bool enabled);
+ void setServerDiscoInfo(std::shared_ptr<DiscoInfo> info);
+ void handleIncomingMessage(std::shared_ptr<Message> incomingMessage);
+#ifndef NOT_YET
+ std::vector<ChatListWindow::Chat> getRecentChats() const;
+#endif
+ virtual std::vector<Contact::ref> getContacts(bool withMUCNicks);
+
+ boost::signals2::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:
+#ifndef NOT_YET
+ ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage);
+#endif
+ 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 createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = nullptr);
+ void handleSearchMUCRequest();
+ void handleMUCSelectedAfterSearch(const JID&);
+ void rebindControllerJID(const JID& from, const JID& to);
+ void handlePresenceChange(std::shared_ptr<Presence> newPresence);
+ void handleUIEvent(std::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);
+#ifndef NOT_YET
+ boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat);
+#endif
+ bool messageCausesSessionBinding(std::shared_ptr<Message> message);
+ void cleanupPrivateMessageRecents();
+#ifndef NOT_YET
+ void appendRecent(const ChatListWindow::Chat& chat);
+ void prependRecent(const ChatListWindow::Chat& chat);
+#endif
+ void setupBookmarks();
+#ifndef NOT_YET
+ void loadRecents();
+ void saveRecents();
+ void handleChatMadeRecent();
+ void handleMUCBookmarkActivated(const MUCBookmark&);
+ void handleRecentActivated(const ChatListWindow::Chat&);
+ void handleUnreadCountChanged(ChatControllerBase* controller);
+#endif
+ void handleChattableActivated(const JID& jid);
+ 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, std::shared_ptr<DiscoInfo> info);
+ void handleLocalServiceWalkFinished();
+ void updatePresenceReceivingStateOnChatController(const JID&);
+#ifndef NOT_YET
+ ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const;
+#endif
+
+
+
+ ChatController* getChatControllerOrFindAnother(const JID &contact);
+ ChatController* createNewChatController(const JID &contact);
+ ChatController* getChatControllerOrCreate(const JID &contact);
+ ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true);
+
+ protected:
+ MUCBookmarkManager* mucBookmarkManager_;
+
+ private:
+ std::map<JID, MUCController*> mucControllers_;
+ std::map<JID, ChatController*> chatControllers_;
+ std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_;
+ EventController* eventController_;
+ JID jid_;
+ StanzaChannel* stanzaChannel_;
+ IQRouter* iqRouter_;
+ ChatWindowFactory* chatWindowFactory_;
+ JoinMUCWindowFactory* joinMUCWindowFactory_;
+ NickResolver* nickResolver_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ PresenceSender* presenceSender_;
+ UIEventStream* uiEventStream_;
+ std::shared_ptr<DiscoInfo> serverDiscoInfo_;
+#ifndef NOT_YET
+ ChatListWindow* chatListWindow_;
+#endif
+ JoinMUCWindow* joinMUCWindow_;
+ boost::signals2::scoped_connection uiEventConnection_;
+ bool useDelayForLatency_;
+ TimerFactory* timerFactory_;
+ MUCRegistry* mucRegistry_;
+ EntityCapsProvider* entityCapsProvider_;
+ MUCManager* mucManager;
+ MUCSearchController* mucSearchController_;
+#ifndef NOT_YET
+ std::list<ChatListWindow::Chat> recentChats_;
+#endif
+ ProfileSettingsProvider* profileSettings_;
+ FileTransferOverview* ftOverview_;
+ XMPPRoster* roster_;
+ bool eagleMode_;
+ bool userWantsReceipts_;
+ SettingsProvider* settings_;
+ HistoryController* historyController_;
+ WhiteboardManager* whiteboardManager_;
+ HighlightManager* highlightManager_;
+ std::map<std::string, std::string> emoticons_;
+ ClientBlockListManager* clientBlockListManager_;
+ JID localMUCServiceJID_;
+ std::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_;
+ AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
+ IDGenerator idGenerator_;
+ VCardManager* vcardManager_;
+ Chattables& chattables_;
+
+ std::map<JID, std::set<JID>> invitees_;
+ };
}
diff --git a/Swift/Controllers/Chat/Chattables.cpp b/Swift/Controllers/Chat/Chattables.cpp
new file mode 100644
index 0000000..707046f
--- /dev/null
+++ b/Swift/Controllers/Chat/Chattables.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/Chat/Chattables.h>
+
+namespace Swift {
+
+const std::vector<JID>& Chattables::get() const {
+ return list_;
+}
+
+const Chattables::State& Chattables::getState(const JID& jid) const {
+ auto it = states_.find(jid);
+ return it != states_.end() ? it->second : unknown_;
+}
+
+void Chattables::addJID(const JID& jid, State::Type type) {
+ if (states_.find(jid) != states_.end()) {
+ return;
+ }
+ State state;
+ state.type = type;
+ state.jid = jid;
+ onBeginAdd(static_cast<int>(list_.size()));
+ list_.push_back(jid);
+ states_[jid] = state;
+ onAdded();
+}
+
+void Chattables::setState(const JID& jid, State state) {
+ auto stateIter = states_.find(jid);
+ if (stateIter == states_.end()) {
+ return;
+ }
+ stateIter->second = state;
+ auto listPos = static_cast<int>(std::distance(list_.begin(), std::find(list_.begin(), list_.end(), jid)));
+ onChanged(jid, listPos);
+}
+
+}
diff --git a/Swift/Controllers/Chat/Chattables.h b/Swift/Controllers/Chat/Chattables.h
new file mode 100644
index 0000000..3b5817a
--- /dev/null
+++ b/Swift/Controllers/Chat/Chattables.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include <boost/signals2.hpp>
+
+#include <Swiften/Elements/StatusShow.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+class Chattables {
+ public:
+ struct State {
+ enum class Type {Room, Person};
+ JID jid;
+ /// Empty for no name
+ std::string name;
+ size_t unreadCount = 0;
+ Type type;
+ StatusShow::Type status = StatusShow::None;
+ //avatar
+ //status
+ };
+ const std::vector<JID>& get() const;
+ const State& getState(const JID& jid) const;
+
+ void addJID(const JID& jid, State::Type type);
+ void setState(const JID& jid, State state);
+
+ boost::signals2::signal<void (int)> onBeginAdd;
+ boost::signals2::signal<void ()> onAdded;
+ boost::signals2::signal<void (const JID&, int)> onChanged;
+ /// The UI has activated a chattable item (e.g. clicked in the roster)
+ boost::signals2::signal<void (const JID&)> onActivated;
+ private:
+ std::vector<JID> list_;
+ std::map<JID, State> states_;
+ State unknown_;
+};
+}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 37631a5..30d4933 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -1,989 +1,1295 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/MUCController.h>
+#include <algorithm>
+#include <cassert>
+#include <memory>
+
#include <boost/bind.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/range/adaptor/reversed.hpp>
-#include <Swift/Controllers/Intl.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Base/Log.h>
#include <Swiften/Base/format.h>
+#include <Swiften/Base/Tristate.h>
+#include <Swiften/Client/BlockList.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
+#include <Swiften/Disco/GetDiscoInfoRequest.h>
+#include <Swiften/Elements/Delay.h>
+#include <Swiften/Elements/Thread.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/MUC/MUCBookmark.h>
+#include <Swiften/MUC/MUCBookmarkManager.h>
#include <Swiften/Network/Timer.h>
#include <Swiften/Network/TimerFactory.h>
-#include <Swiften/Base/foreach.h>
+#include <Swiften/Roster/XMPPRoster.h>
+
#include <SwifTools/TabComplete.h>
-#include <Swiften/Base/foreach.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
-#include <Swift/Controllers/UIEvents/UIEventStream.h>
-#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
-#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
-#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
-#include <Swift/Controllers/Roster/GroupRosterItem.h>
+
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Chat/Chattables.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
+#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/Roster/ContactRosterItem.h>
-#include <Swiften/Avatars/AvatarManager.h>
-#include <Swiften/Elements/Delay.h>
-#include <Swiften/MUC/MUC.h>
-#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h>
+#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h>
+#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h>
#include <Swift/Controllers/Roster/Roster.h>
-#include <Swift/Controllers/Roster/SetAvatar.h>
-#include <Swift/Controllers/Roster/SetPresence.h>
-#include <Swiften/Disco/EntityCapsProvider.h>
-#include <Swiften/Roster/XMPPRoster.h>
-#include <Swift/Controllers/Highlighter.h>
-#include <Swift/Controllers/Chat/ChatMessageParser.h>
-
-#include <Swiften/Base/Log.h>
+#include <Swift/Controllers/Roster/RosterVCardProvider.h>
+#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
namespace Swift {
-
+
/**
* The controller does not gain ownership of the stanzaChannel, nor the factory.
*/
MUCController::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* uiEventStream,
- bool useDelayForLatency,
- TimerFactory* timerFactory,
- EventController* eventController,
- EntityCapsProvider* entityCapsProvider,
- XMPPRoster* roster,
- HistoryController* historyController,
- MUCRegistry* mucRegistry,
- HighlightManager* highlightManager,
- ChatMessageParser* chatMessageParser,
- bool isImpromptu,
- AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) :
- 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);
- 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_->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_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
- muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
- muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
- muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, 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(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(true);
- } else {
- chatWindow_->convertToMUC();
- 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));
+ const JID& self,
+ MUC::ref muc,
+ const boost::optional<std::string>& password,
+ const std::string &nick,
+ StanzaChannel* stanzaChannel,
+ IQRouter* iqRouter,
+ ChatWindowFactory* chatWindowFactory,
+ NickResolver* nickResolver,
+ PresenceOracle* presenceOracle,
+ AvatarManager* avatarManager,
+ UIEventStream* uiEventStream,
+ bool useDelayForLatency,
+ TimerFactory* timerFactory,
+ EventController* eventController,
+ EntityCapsProvider* entityCapsProvider,
+ XMPPRoster* xmppRoster,
+ HistoryController* historyController,
+ MUCRegistry* mucRegistry,
+ HighlightManager* highlightManager,
+ ClientBlockListManager* clientBlockListManager,
+ std::shared_ptr<ChatMessageParser> chatMessageParser,
+ bool isImpromptu,
+ AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
+ VCardManager* vcardManager,
+ MUCBookmarkManager* mucBookmarkManager,
+ SettingsProvider* settings,
+ Chattables& chattables) :
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), nickResolver, presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider, settings, chattables), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) {
+ assert(avatarManager_);
+
+ parting_ = true;
+ joined_ = false;
+ lastWasPresence_ = false;
+ shouldJoinOnReconnect_ = true;
+ doneGettingHistory_ = false;
+ xmppRoster_ = xmppRoster;
+ subject_ = "";
+ isInitialJoin_ = true;
+ chatWindowTitle_ = "";
+
+ roster_ = std::make_unique<Roster>(false, true);
+ rosterVCardProvider_ = new RosterVCardProvider(roster_.get(), vcardManager, JID::WithResource);
+ completer_ = new TabComplete();
+ chatWindow_->setRosterModel(roster_.get());
+ 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));
+ chatWindow_->onUnblockUserRequest.connect(boost::bind(&MUCController::handleUnblockUserRequest, this));
+ chatWindow_->onContinuationsBroken.connect(boost::bind(&MUCController::addChatSystemMessage, this));
+ muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
+ muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
+ muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
+ muc_->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));
+ chatMessageParser_->setNick(nick_);
+ if (timerFactory && stanzaChannel_->isAvailable()) {
+ loginCheckTimer_ = std::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
+ loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
+ loginCheckTimer_->start();
+ }
+ else {
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You are currently offline. You will enter this room when you are connected.")), ChatWindow::DefaultDirection);
+ }
+ 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());
+ }
+ if (stanzaChannel->isAvailable()) {
+ MUCController::setOnline(true);
+ }
+ avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
+
+ MUCController::handleBareJIDCapsChanged(muc->getJID());
+ eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1));
+
+
+ // setup handling of MUC bookmark changes
+ mucBookmarkManagerBookmarkAddedConnection_ = (mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&MUCController::handleMUCBookmarkAdded, this, _1)));
+ mucBookmarkManagerBookmarkRemovedConnection_ = (mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&MUCController::handleMUCBookmarkRemoved, this, _1)));
+
+ updateChatWindowBookmarkStatus(mucBookmarkManager_->lookupBookmark(muc->getJID()));
}
MUCController::~MUCController() {
- eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1));
- chatWindow_->setRosterModel(NULL);
- delete roster_;
- if (loginCheckTimer_) {
- loginCheckTimer_->stop();
- }
- chatWindow_->setTabComplete(NULL);
- delete completer_;
+ eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1));
+ chatWindow_->setRosterModel(nullptr);
+ delete rosterVCardProvider_;
+ if (loginCheckTimer_) {
+ loginCheckTimer_->stop();
+ }
+ chatWindow_->setTabComplete(nullptr);
+ delete completer_;
}
void MUCController::cancelReplaces() {
- lastWasPresence_ = false;
+ lastWasPresence_ = false;
}
void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) {
- std::vector<ChatWindow::OccupantAction> actions;
-
- if (item) {
- MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation();
- MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole();
- if (role == MUCOccupant::Moderator)
- {
- if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) {
- actions.push_back(ChatWindow::Ban);
- }
-
- actions.push_back(ChatWindow::Kick);
- actions.push_back(ChatWindow::MakeModerator);
- actions.push_back(ChatWindow::MakeParticipant);
- actions.push_back(ChatWindow::MakeVisitor);
- }
- // Add contact is available only if the real JID is also available
- if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) {
- actions.push_back(ChatWindow::AddContact);
- }
- actions.push_back(ChatWindow::ShowProfile);
- }
- chatWindow_->setAvailableOccupantActions(actions);
+ std::vector<ChatWindow::OccupantAction> actions;
+
+ if (item) {
+ MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation();
+ MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole();
+ if (role == MUCOccupant::Moderator && !isImpromptu_)
+ {
+ if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) {
+ actions.push_back(ChatWindow::Ban);
+ }
+
+ actions.push_back(ChatWindow::Kick);
+ actions.push_back(ChatWindow::MakeModerator);
+ actions.push_back(ChatWindow::MakeParticipant);
+ actions.push_back(ChatWindow::MakeVisitor);
+ }
+ // Add contact is available only if the real JID is also available
+ if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) {
+ actions.push_back(ChatWindow::AddContact);
+ }
+ actions.push_back(ChatWindow::ShowProfile);
+ }
+ chatWindow_->setAvailableOccupantActions(actions);
}
void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) {
- JID mucJID = item->getJID();
- MUCOccupant occupant = muc_->getOccupant(mucJID.getResource());
- JID realJID;
- if (occupant.getRealJID()) {
- realJID = occupant.getRealJID().get();
- }
- switch (action) {
- case ChatWindow::Kick: muc_->kickOccupant(mucJID);break;
- case ChatWindow::Ban: muc_->changeAffiliation(realJID, MUCOccupant::Outcast);break;
- case ChatWindow::MakeModerator: muc_->changeOccupantRole(mucJID, MUCOccupant::Moderator);break;
- case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break;
- case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break;
- case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(boost::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break;
- case ChatWindow::ShowProfile: events_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break;
- }
+ JID mucJID = item->getJID();
+ MUCOccupant occupant = muc_->getOccupant(mucJID.getResource());
+ JID realJID;
+ if (occupant.getRealJID()) {
+ realJID = occupant.getRealJID().get();
+ }
+ switch (action) {
+ case ChatWindow::Kick: muc_->kickOccupant(mucJID);break;
+ case ChatWindow::Ban: muc_->changeAffiliation(realJID, MUCOccupant::Outcast);break;
+ case ChatWindow::MakeModerator: muc_->changeOccupantRole(mucJID, MUCOccupant::Moderator);break;
+ case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break;
+ case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break;
+ case ChatWindow::AddContact: if (occupant.getRealJID()) eventStream_->send(std::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break;
+ case ChatWindow::ShowProfile: eventStream_->send(std::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break;
+ }
}
void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) {
- ChatWindow::Tristate support = ChatWindow::Yes;
- bool any = false;
- foreach (const std::string& nick, currentOccupants_) {
- DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick);
- if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
- any = true;
- } else {
- support = ChatWindow::Maybe;
- }
- }
- if (!any) {
- support = ChatWindow::No;
- }
- chatWindow_->setCorrectionEnabled(support);
+ Tristate support = Yes;
+ bool any = false;
+ for (const auto& nick : currentOccupants_) {
+ DiscoInfo::ref disco = entityCapsProvider_->getCapsCached(toJID_.toBare().toString() + "/" + nick);
+ if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
+ any = true;
+ } else {
+ support = Maybe;
+ }
+ }
+ if (!any) {
+ support = No;
+ }
+ chatWindow_->setCorrectionEnabled(support);
}
/**
* Join the MUC if not already in it.
*/
void MUCController::rejoin() {
- if (parting_) {
- joined_ = false;
- parting_ = false;
- if (password_) {
- muc_->setPassword(*password_);
- }
- //FIXME: check for received activity
+ if (parting_) {
+ joined_ = false;
+ parting_ = false;
+ if (password_) {
+ muc_->setPassword(*password_);
+ }
+ //FIXME: check for received activity
#ifdef SWIFT_EXPERIMENTAL_HISTORY
- if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) {
- lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_);
- }
+ if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) {
+ lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_);
+ }
#endif
- if (lastActivity_ == boost::posix_time::not_a_date_time) {
- muc_->joinAs(nick_);
- }
- else {
- muc_->joinWithContextSince(nick_, lastActivity_);
- }
- }
+
+ requestSecurityMarking();
+
+ if (lastActivity_ == boost::posix_time::not_a_date_time) {
+ muc_->joinAs(nick_);
+ }
+ else {
+ muc_->joinWithContextSince(nick_, lastActivity_);
+ }
+ }
}
bool MUCController::isJoined() {
- return joined_;
+ return joined_;
}
const std::string& MUCController::getNick() {
- return nick_;
+ return nick_;
+}
+
+const boost::optional<std::string> MUCController::getPassword() const {
+ return password_;
}
bool MUCController::isImpromptu() const {
- return isImpromptu_;
+ return isImpromptu_;
}
std::map<std::string, JID> MUCController::getParticipantJIDs() const {
- std::map<std::string, JID> participants;
- typedef std::pair<std::string, MUCOccupant> MUCOccupantPair;
- std::map<std::string, MUCOccupant> occupants = muc_->getOccupants();
- foreach(const MUCOccupantPair& occupant, occupants) {
- if (occupant.first != nick_) {
- participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID();
- }
- }
- return participants;
+ std::map<std::string, JID> participants;
+ std::map<std::string, MUCOccupant> occupants = muc_->getOccupants();
+ for (const auto& occupant : occupants) {
+ if (occupant.first != nick_) {
+ participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID();
+ }
+ }
+ return participants;
}
void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const {
- foreach (const JID& jid, jids) {
- muc_->invitePerson(jid, reason, isImpromptu_);
- }
+ for (const auto& jid : jids) {
+ muc_->invitePerson(jid, reason, isImpromptu_);
+ }
}
void MUCController::handleJoinTimeoutTick() {
- receivedActivity();
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
+ receivedActivity();
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
}
void MUCController::receivedActivity() {
- if (loginCheckTimer_) {
- loginCheckTimer_->stop();
- }
+ if (loginCheckTimer_) {
+ loginCheckTimer_->stop();
+ }
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wswitch-enum"
-void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
- receivedActivity();
- std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room");
- std::string rejoinNick;
- if (error) {
- switch (error->getCondition()) {
- case ErrorPayload::Conflict:
- rejoinNick = nick_ + "_";
- errorMessage = str(format(QT_TRANSLATE_NOOP("", "Unable to enter this room as %1%, retrying as %2%")) % nick_ % rejoinNick);
- break;
- case ErrorPayload::JIDMalformed:
- errorMessage += ": ";
- errorMessage += QT_TRANSLATE_NOOP("", "No nickname specified");
- break;
- case ErrorPayload::NotAuthorized:
- errorMessage += ": ";
- errorMessage += QT_TRANSLATE_NOOP("", "The correct room password is needed");
- break;
- case ErrorPayload::RegistrationRequired:
- errorMessage += ": ";
- errorMessage += QT_TRANSLATE_NOOP("", "Only members may enter");
- break;
- case ErrorPayload::Forbidden:
- errorMessage += ": ";
- errorMessage += QT_TRANSLATE_NOOP("", "You are banned from the room");
- break;
- case ErrorPayload::ServiceUnavailable:
- errorMessage += ": ";
- errorMessage += QT_TRANSLATE_NOOP("", "The room is full");
- break;
- case ErrorPayload::ItemNotFound:
- errorMessage += ": ";
- errorMessage += QT_TRANSLATE_NOOP("", "The room does not exist");
- break;
-
- default: break;
- }
- }
- errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage);
- chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
- parting_ = true;
- if (!rejoinNick.empty() && renameCounter_ < 10) {
- renameCounter_++;
- setNick(rejoinNick);
- rejoin();
- }
+void MUCController::handleJoinFailed(std::shared_ptr<ErrorPayload> error) {
+ receivedActivity();
+ std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room");
+ std::string rejoinNick;
+ if (error) {
+ switch (error->getCondition()) {
+ case ErrorPayload::Conflict:
+ rejoinNick = nick_ + "_";
+ errorMessage = str(format(QT_TRANSLATE_NOOP("", "Unable to enter this room as %1%, retrying as %2%")) % nick_ % rejoinNick);
+ break;
+ case ErrorPayload::JIDMalformed:
+ errorMessage += ": ";
+ errorMessage += QT_TRANSLATE_NOOP("", "No nickname specified");
+ break;
+ case ErrorPayload::NotAuthorized:
+ errorMessage += ": ";
+ errorMessage += QT_TRANSLATE_NOOP("", "The correct room password is needed");
+ break;
+ case ErrorPayload::RegistrationRequired:
+ errorMessage += ": ";
+ errorMessage += QT_TRANSLATE_NOOP("", "Only members may enter");
+ break;
+ case ErrorPayload::Forbidden:
+ errorMessage += ": ";
+ errorMessage += QT_TRANSLATE_NOOP("", "You are banned from the room");
+ break;
+ case ErrorPayload::ServiceUnavailable:
+ errorMessage += ": ";
+ errorMessage += QT_TRANSLATE_NOOP("", "The room is full");
+ break;
+ case ErrorPayload::ItemNotFound:
+ errorMessage += ": ";
+ errorMessage += QT_TRANSLATE_NOOP("", "The room does not exist");
+ break;
+
+ default: break;
+ }
+ }
+ errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't enter room: %1%.")) % errorMessage);
+ chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
+ parting_ = true;
+ if (!rejoinNick.empty() && renameCounter_ < 10) {
+ renameCounter_++;
+ setNick(rejoinNick);
+ rejoin();
+ }
}
#pragma clang diagnostic pop
void MUCController::handleJoinComplete(const std::string& nick) {
- receivedActivity();
- renameCounter_ = 0;
- joined_ = true;
- std::string joinMessage;
- if (isImpromptu_) {
- joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered chat as %1%.")) % nick);
- } else {
- joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
- }
- setNick(nick);
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::DefaultDirection);
+ receivedActivity();
+ renameCounter_ = 0;
+ joined_ = true;
+ if (isImpromptu_) {
+ lastStartMessage_ = str(format(QT_TRANSLATE_NOOP("", "You have joined the chat as %1%.")) % nick);
+ }
+ else {
+ lastStartMessage_ = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
+ }
+ setNick(nick);
+ chatWindow_->replaceSystemMessage(chatMessageParser_->parseMessageBody(lastStartMessage_, "", true), lastJoinMessageUID_, ChatWindow::UpdateTimestamp);
+ lastJoinMessageUID_ = "";
#ifdef SWIFT_EXPERIMENTAL_HISTORY
- addRecentLogs();
+ addRecentLogs();
#endif
- clearPresenceQueue();
- shouldJoinOnReconnect_ = true;
- setEnabled(true);
- MUCOccupant occupant = muc_->getOccupant(nick);
- setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
- onUserJoined();
-
- if (isImpromptu_) {
- setImpromptuWindowTitle();
- }
+ clearPresenceQueue();
+ shouldJoinOnReconnect_ = true;
+ setEnabled(true);
+ if (isImpromptu_) {
+ setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant);
+ } else {
+ MUCOccupant occupant = muc_->getOccupant(nick);
+ setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
+ }
+ onUserJoined();
}
void MUCController::handleAvatarChanged(const JID& jid) {
- if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) {
- return;
- }
- roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource));
+ if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) {
+ return;
+ }
+ roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource));
}
void MUCController::handleWindowClosed() {
- parting_ = true;
- shouldJoinOnReconnect_ = false;
- muc_->part();
- onUserLeft();
+ parting_ = true;
+ shouldJoinOnReconnect_ = false;
+ muc_->part();
+ onUserLeft();
}
void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
- if (nick_ != occupant.getNick()) {
- completer_->addWord(occupant.getNick());
- }
- receivedActivity();
- JID jid(nickToJID(occupant.getNick()));
- JID realJID;
- if (occupant.getRealJID()) {
- realJID = occupant.getRealJID().get();
- }
- currentOccupants_.insert(occupant.getNick());
- NickJoinPart event(occupant.getNick(), Join);
- appendToJoinParts(joinParts_, event);
- std::string groupName(roleToGroupName(occupant.getRole()));
- roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid));
- roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole()));
- if (joined_) {
- std::string joinString;
- MUCOccupant::Role role = occupant.getRole();
- if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) {
- joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
- }
- else {
- joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
- }
- if (shouldUpdateJoinParts()) {
- updateJoinParts();
- } else {
- addPresenceMessage(joinString);
- }
-
- if (isImpromptu_) {
- setImpromptuWindowTitle();
- onActivity("");
- }
- }
- if (avatarManager_ != NULL) {
- handleAvatarChanged(jid);
- }
+ if (nick_ != occupant.getNick()) {
+ completer_->addWord(occupant.getNick());
+ }
+ receivedActivity();
+ JID jid(nickToJID(occupant.getNick()));
+ JID realJID;
+ if (occupant.getRealJID()) {
+ realJID = occupant.getRealJID().get();
+ }
+ currentOccupants_.insert(occupant.getNick());
+ NickJoinPart event(occupant.getNick(), Join);
+ appendToJoinParts(joinParts_, event);
+ 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(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid));
+ roster_->applyOnItems(SetMUC(jid, role, affiliation));
+ roster_->getGroup(groupName)->setManualSort(roleToSortName(role));
+ if (joined_) {
+ std::string joinString;
+ if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) {
+ joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
+ }
+ else {
+ joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
+ }
+ if (shouldUpdateJoinParts()) {
+ updateJoinParts();
+ } else {
+ addPresenceMessage(joinString);
+ }
+
+ if (isImpromptu_) {
+ onActivity("");
+ }
+ }
+ handleAvatarChanged(jid);
}
void MUCController::addPresenceMessage(const std::string& message) {
- lastWasPresence_ = true;
- chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection);
+ lastWasPresence_ = true;
+ chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection);
}
void MUCController::setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role)
{
- std::vector<ChatWindow::RoomAction> actions;
-
- if (role <= MUCOccupant::Participant) {
- actions.push_back(ChatWindow::ChangeSubject);
- }
- if (affiliation == MUCOccupant::Owner) {
- actions.push_back(ChatWindow::Configure);
- }
- if (affiliation <= MUCOccupant::Admin) {
- actions.push_back(ChatWindow::Affiliations);
- }
- if (affiliation == MUCOccupant::Owner) {
- actions.push_back(ChatWindow::Destroy);
- }
- if (role <= MUCOccupant::Visitor) {
- actions.push_back(ChatWindow::Invite);
- }
- chatWindow_->setAvailableRoomActions(actions);
+ std::vector<ChatWindow::RoomAction> actions;
+
+ if (role <= MUCOccupant::Participant) {
+ actions.push_back(ChatWindow::ChangeSubject);
+ }
+ if (affiliation == MUCOccupant::Owner) {
+ actions.push_back(ChatWindow::Configure);
+ }
+ if (affiliation <= MUCOccupant::Admin) {
+ actions.push_back(ChatWindow::Affiliations);
+ }
+ if (affiliation == MUCOccupant::Owner) {
+ actions.push_back(ChatWindow::Destroy);
+ }
+ if (role <= MUCOccupant::Visitor) {
+ actions.push_back(ChatWindow::Invite);
+ }
+ actions.push_back(ChatWindow::Leave);
+ chatWindow_->setAvailableRoomActions(actions);
}
void MUCController::clearPresenceQueue() {
- lastWasPresence_ = false;
- joinParts_.clear();
+ 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 "";
+ 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";
+ 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);
-}
-
-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 (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);;
- chatWindow_->setSubject(message->getSubject());
- doneGettingHistory_ = true;
- }
-
- if (!doneGettingHistory_ && !message->getPayload<Delay>()) {
- doneGettingHistory_ = true;
- }
-
- if (!doneGettingHistory_) {
- checkDuplicates(message);
- messageEvent->conclude();
- }
-}
-
-void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) {
- boost::shared_ptr<Message> message = messageEvent->getStanza();
- if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>()) {
- eventController_->handleIncomingEvent(messageEvent);
- if (!messageEvent->getConcluded()) {
- highlighter_->handleHighlightAction(highlight);
- }
- }
+ return muc_->getJID().withResource(nick);
+}
+
+bool MUCController::messageTargetsMe(std::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().get_value_or("")), myRegexp);
+}
+
+void MUCController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) {
+ if (messageEvent->getStanza()->getType() == Message::Groupchat) {
+ lastActivity_ = boost::posix_time::microsec_clock::universal_time();
+ }
+ std::shared_ptr<Message> message = messageEvent->getStanza();
+
+ // This avoids clearing join/leave queue for chat state notification messages
+ // which are not readable (e.g. have no body content).
+ if (!(!messageEvent->isReadable() && message->getPayload<ChatState>())) {
+ clearPresenceQueue();
+ }
+
+ 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->getPayload<Body>() && !message->getPayload<Thread>()) {
+ if (!isInitialJoin_) {
+ displaySubjectIfChanged(message->getSubject());
+ }
+ isInitialJoin_ = false;
+ chatWindow_->setSubject(message->getSubject());
+ doneGettingHistory_ = true;
+ subject_ = message->getSubject();
+ }
+
+ if (!doneGettingHistory_ && !message->getPayload<Delay>()) {
+ doneGettingHistory_ = true;
+ }
+
+ if (!doneGettingHistory_) {
+ checkDuplicates(message);
+ messageEvent->conclude();
+ }
+}
+
+void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) {
+ if (from.isBare()) {
+ chatWindow_->addSystemMessage(message, ChatWindow::DefaultDirection);
+ }
+ else {
+ lastMessagesIDs_[from] = {messageID, addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), time)};
+ }
+}
+
+void MUCController::handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& /*idToReplace*/, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) {
+ auto lastMessage = lastMessagesIDs_.find(from);
+ if (lastMessage != lastMessagesIDs_.end()) {
+ replaceMessage(message, lastMessage->second.idInWindow, timeStamp);
+ }
+ else {
+ addMessageHandleIncomingMessage(from, message, messageID, senderIsSelf, label, timeStamp);
+ }
+}
+
+void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) {
+ std::shared_ptr<Message> message = messageEvent->getStanza();
+ if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) {
+ highlighter_->handleSystemNotifications(chatMessage, messageEvent);
+ if (!messageEvent->getNotifications().empty()) {
+ eventController_->handleIncomingEvent(messageEvent);
+ }
+ if (!messageEvent->getConcluded()) {
+ highlighter_->handleSoundNotifications(chatMessage);
+ }
+ }
}
void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) {
- clearPresenceQueue();
- receivedActivity();
- JID jid(nickToJID(nick));
- roster_->removeContactFromGroup(jid, roleToGroupName(oldRole));
- JID realJID;
- if (occupant.getRealJID()) {
- realJID = occupant.getRealJID().get();
- }
- std::string group(roleToGroupName(occupant.getRole()));
- roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
- roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
- if (nick == nick_) {
- setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
- }
+ clearPresenceQueue();
+ receivedActivity();
+ JID jid(nickToJID(nick));
+ roster_->removeContactFromGroup(jid, roleToGroupName(oldRole));
+ JID realJID;
+ if (occupant.getRealJID()) {
+ realJID = occupant.getRealJID().get();
+ }
+ std::string group(roleToGroupName(occupant.getRole()));
+ roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
+ roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
+ roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation()));
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
+ if (nick == nick_) {
+ setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
+ }
}
void MUCController::handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Affiliation& /*oldAffiliation*/)
{
- if (nick == nick_) {
- setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole());
- }
+ if (nick == nick_) {
+ setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole());
+ }
+ JID jid(nickToJID(nick));
+ MUCOccupant occupant = muc_->getOccupant(nick);
+ roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation));
}
std::string MUCController::roleToGroupName(MUCOccupant::Role role) {
- std::string result;
- switch (role) {
- case MUCOccupant::Moderator: result = QT_TRANSLATE_NOOP("", "Moderators"); break;
- case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break;
- case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break;
- case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break;
- }
- return result;
+ std::string result;
+ switch (role) {
+ case MUCOccupant::Moderator: result = QT_TRANSLATE_NOOP("", "Moderators"); break;
+ case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break;
+ case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break;
+ case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break;
+ }
+ return result;
}
void MUCController::setOnline(bool online) {
- ChatControllerBase::setOnline(online);
- if (!online) {
- muc_->part();
- parting_ = true;
- processUserPart();
- } else {
- if (shouldJoinOnReconnect_) {
- renameCounter_ = 0;
- if (isImpromptu_) {
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to enter chat")), ChatWindow::DefaultDirection);
- } else {
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
- }
- if (loginCheckTimer_) {
- loginCheckTimer_->start();
- }
- setNick(desiredNick_);
- rejoin();
- }
- }
+ ChatControllerBase::setOnline(online);
+ if (!online) {
+ muc_->part();
+ parting_ = true;
+ processUserPart();
+ } else {
+ if (shouldJoinOnReconnect_) {
+ renameCounter_ = 0;
+ std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+ if (blockList && blockList->isBlocked(muc_->getJID())) {
+ handleBlockingStateChanged();
+ lastStartMessage_ = QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again");
+ lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(lastStartMessage_ ), ChatWindow::DefaultDirection);
+ }
+ else {
+ if (isImpromptu_) {
+ lastStartMessage_ = QT_TRANSLATE_NOOP("", "Trying to join chat");
+ lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(lastStartMessage_), ChatWindow::DefaultDirection);
+ } else {
+ lastStartMessage_ = str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString());
+ lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(lastStartMessage_), ChatWindow::DefaultDirection);
+ }
+ if (loginCheckTimer_) {
+ loginCheckTimer_->start();
+ }
+ setNick(desiredNick_);
+ rejoin();
+ }
+ }
+ }
}
void MUCController::processUserPart() {
- roster_->removeAll();
- /* handleUserLeft won't throw a part back up unless this is called
- when it doesn't yet know we've left - which only happens on
- disconnect, so call with disconnect here so if the signal does
- bubble back up, it'll be with the right type.*/
- muc_->handleUserLeft(MUC::Disconnect);
- setEnabled(false);
+ roster_->removeAll();
+ /* handleUserLeft won't throw a part back up unless this is called
+ when it doesn't yet know we've left - which only happens on
+ disconnect, so call with disconnect here so if the signal does
+ bubble back up, it'll be with the right type.*/
+ muc_->handleUserLeft(MUC::Disconnect);
+ setEnabled(false);
}
bool MUCController::shouldUpdateJoinParts() {
- return lastWasPresence_;
+ return lastWasPresence_;
}
void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason) {
- NickJoinPart event(occupant.getNick(), Part);
- appendToJoinParts(joinParts_, event);
- currentOccupants_.erase(occupant.getNick());
- completer_->removeWord(occupant.getNick());
- std::string partMessage;
- bool clearAfter = false;
- if (occupant.getNick() != nick_) {
- std::string partType;
- switch (type) {
- case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partType = " (kicked)"; break;
- case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partType = " (banned)"; break;
- case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partType = " (no longer a member)"; break;
- case MUC::LeaveDestroy:
- case MUC::Disconnect:
- case MUC::LeavePart: break;
- }
- partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType);
- }
- else {
- 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::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();
+ NickJoinPart event(occupant.getNick(), Part);
+ appendToJoinParts(joinParts_, event);
+ currentOccupants_.erase(occupant.getNick());
+ completer_->removeWord(occupant.getNick());
+ std::string partMessage;
+ bool clearAfter = false;
+ if (occupant.getNick() != nick_) {
+ std::string partType;
+ switch (type) {
+ case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partType = " (kicked)"; break;
+ case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partType = " (banned)"; break;
+ case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partType = " (no longer a member)"; break;
+ case MUC::LeaveDestroy:
+ case MUC::Disconnect:
+ case MUC::LeavePart: break;
+ }
+ if (isImpromptu_) {
+ partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the chat%2%")) % occupant.getNick() % partType);
+ } else {
+ partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType);
+ }
+ }
+ else if (isImpromptu_) {
+ switch (type) {
+ case MUC::LeaveKick:
+ case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break;
+ case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break;
+ case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "This chat has ended"); break;
+ case MUC::Disconnect:
+ case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the chat");
+ }
+ }
+ else {
+ 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();
+ }
+}
+
+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);
+ auto it = currentOccupants_.find(newNickname);
+ if (it != currentOccupants_.end()) {
+ roster_->removeContact(newJID);
+ }
+
+ 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));
+ handleAvatarChanged(newJID);
+
+ clearPresenceQueue();
+ onUserNicknameChanged(oldNickname, newNickname);
+}
+
+void MUCController::handleOccupantPresenceChange(std::shared_ptr<Presence> presence) {
+ receivedActivity();
+ roster_->applyOnItems(SetPresence(presence, JID::WithResource));
+}
+
+bool MUCController::isIncomingMessageFromMe(std::shared_ptr<Message> message) {
+ JID from = message->getFrom();
+ return nick_ == from.getResource();
+}
+
+std::string MUCController::senderHighlightNameFromMessage(const JID& from) {
+ return from.getResource();
}
std::string MUCController::senderDisplayNameFromMessage(const JID& from) {
- return from.getResource();
+ return from.getResource();
}
-void MUCController::preSendMessageRequest(boost::shared_ptr<Message> message) {
- message->setType(Swift::Message::Groupchat);
+void MUCController::preSendMessageRequest(std::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_);
+boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(std::shared_ptr<Message> message) const {
+ return message->getTimestampFrom(toJID_);
}
void MUCController::updateJoinParts() {
- chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())));
+ 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;
- case Part: type = (type == Join) ? JoinThenPart : Part; break;
- case PartThenJoin: break;
- case JoinThenPart: break;
- }
- (*it).type = type;
- break;
- }
- }
- if (!matched) {
- joinParts.push_back(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;
+ case Part: type = (type == Join) ? JoinThenPart : Part; break;
+ case PartThenJoin: break;
+ case JoinThenPart: break;
+ }
+ (*it).type = type;
+ break;
+ }
+ }
+ if (!matched) {
+ joinParts.push_back(newEvent);
+ }
}
std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart>& joinParts) {
- std::string result;
- for (size_t i = 0; i < joinParts.size(); i++) {
- if (i > 0) {
- if (i < joinParts.size() - 1) {
- result += ", ";
- } else {
- result += QT_TRANSLATE_NOOP("", " and ");
- }
- }
- NickJoinPart event = joinParts[i];
- result += event.nick;
- }
- return result;
+ std::string result;
+ for (size_t i = 0; i < joinParts.size(); i++) {
+ if (i > 0) {
+ if (i < joinParts.size() - 1) {
+ result += ", ";
+ } else {
+ result += QT_TRANSLATE_NOOP("", " and ");
+ }
+ }
+ NickJoinPart event = joinParts[i];
+ result += event.nick;
+ }
+ return result;
}
std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu) {
- std::vector<NickJoinPart> sorted[4];
- std::string eventStrings[4];
- foreach (NickJoinPart event, joinParts) {
- sorted[event.type].push_back(event);
- }
- std::string result;
- std::vector<JoinPart> populatedEvents;
- for (size_t i = 0; i < 4; i++) {
- std::string names = concatenateListOfNames(sorted[i]);
- if (!names.empty()) {
- std::string eventString;
- switch (i) {
- case Join:
- if (sorted[i].size() > 1) {
- eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room"));
- }
- else {
- eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room"));
- }
- break;
- case Part:
- if (sorted[i].size() > 1) {
- eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room"));
- }
- else {
- eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room"));
- }
- 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::vector<NickJoinPart> sorted[4];
+ std::string eventStrings[4];
+ for (const auto& event : joinParts) {
+ sorted[event.type].push_back(event);
+ }
+ std::string result;
+ std::vector<JoinPart> populatedEvents;
+ for (size_t i = 0; i < 4; i++) {
+ std::string names = concatenateListOfNames(sorted[i]);
+ if (!names.empty()) {
+ std::string eventString;
+ switch (i) {
+ case Join:
+ if (sorted[i].size() > 1) {
+ eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room"));
+ }
+ else {
+ eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room"));
+ }
+ break;
+ case Part:
+ if (sorted[i].size() > 1) {
+ eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room"));
+ }
+ else {
+ eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room"));
+ }
+ 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);
+ muc_->changeSubject(subject);
+}
+
+void MUCController::handleBookmarkRequest() {
+ const JID jid = muc_->getJID();
+
+ // Prepare new bookmark for this room.
+ MUCBookmark roomBookmark(jid, jid.toBare().toString());
+ roomBookmark.setPassword(password_);
+ roomBookmark.setNick(nick_);
+
+ // Check for existing bookmark for this room and, if it exists, use it instead.
+ std::vector<MUCBookmark> bookmarks = mucBookmarkManager_->getBookmarks();
+ for (const auto& bookmark : bookmarks) {
+ if (bookmark.getRoom() == jid.toBare()) {
+ roomBookmark = bookmark;
+ break;
+ }
+ }
+
+ chatWindow_->showBookmarkWindow(roomBookmark);
}
void MUCController::handleConfigureRequest(Form::ref form) {
- if (form) {
- muc_->configureRoom(form);
- }
- else {
- muc_->requestConfigurationForm();
- }
+ 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));
+ 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));
+ 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));
- isImpromptuAlreadyConfigured_ = true;
- onImpromptuConfigCompleted();
+ muc_->configureRoom(buildImpromptuRoomConfiguration(form));
+ isImpromptuAlreadyConfigured_ = true;
+ onImpromptuConfigCompleted();
}
void MUCController::handleConfigurationFormReceived(Form::ref form) {
- if (isImpromptu_) {
- if (!isImpromptuAlreadyConfigured_) {
- configureAsImpromptuRoom(form);
- }
- } else {
- chatWindow_->showRoomConfigurationForm(form);
- }
+ if (isImpromptu_) {
+ if (!isImpromptuAlreadyConfigured_) {
+ configureAsImpromptuRoom(form);
+ }
+ } else {
+ chatWindow_->showRoomConfigurationForm(form);
+ }
}
void MUCController::handleConfigurationCancelled() {
- muc_->cancelConfigureRoom();
+ muc_->cancelConfigureRoom();
}
void MUCController::handleDestroyRoomRequest() {
- muc_->destroyRoom();
+ muc_->destroyRoom();
}
void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) {
- boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite));
- eventStream_->send(event);
+ RequestInviteToMUCUIEvent::ImpromptuMode mode = isImpromptu_ ? RequestInviteToMUCUIEvent::Impromptu : RequestInviteToMUCUIEvent::NotImpromptu;
+ eventStream_->send(std::make_shared<RequestInviteToMUCUIEvent>(getToJID(), jidsToInvite, mode));
}
-void MUCController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
- boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
- if (inviteEvent && inviteEvent->getRoom() == muc_->getJID()) {
- foreach (const JID& jid, inviteEvent->getInvites()) {
- muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_);
- }
- }
+void MUCController::handleUIEvent(std::shared_ptr<UIEvent> event) {
+ std::shared_ptr<InviteToMUCUIEvent> inviteEvent = std::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
+ if (inviteEvent && inviteEvent->getOriginator() == muc_->getJID()) {
+ for (const auto& jid : inviteEvent->getInvites()) {
+ muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_);
+ }
+ }
}
void MUCController::handleGetAffiliationsRequest() {
- muc_->requestAffiliationList(MUCOccupant::Owner);
- muc_->requestAffiliationList(MUCOccupant::Admin);
- muc_->requestAffiliationList(MUCOccupant::Member);
- muc_->requestAffiliationList(MUCOccupant::Outcast);
+ muc_->requestAffiliationList(MUCOccupant::Owner);
+ muc_->requestAffiliationList(MUCOccupant::Admin);
+ muc_->requestAffiliationList(MUCOccupant::Member);
+ muc_->requestAffiliationList(MUCOccupant::Outcast);
}
-typedef std::pair<MUCOccupant::Affiliation, JID> AffiliationChangePair;
-
void MUCController::handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes) {
- std::set<JID> addedJIDs;
- foreach (const AffiliationChangePair& change, changes) {
- if (change.first != MUCOccupant::NoAffiliation) {
- addedJIDs.insert(change.second);
- }
- }
- foreach (const AffiliationChangePair& change, changes) {
- if (change.first != MUCOccupant::NoAffiliation || addedJIDs.find(change.second) == addedJIDs.end()) {
- muc_->changeAffiliation(change.second, change.first);
- }
- }
+ std::set<JID> addedJIDs;
+ for (const auto& change : changes) {
+ if (change.first != MUCOccupant::NoAffiliation) {
+ addedJIDs.insert(change.second);
+ }
+ }
+ for (const auto& change : changes) {
+ if (change.first != MUCOccupant::NoAffiliation || addedJIDs.find(change.second) == addedJIDs.end()) {
+ muc_->changeAffiliation(change.second, change.first);
+ }
+ }
+}
+
+void MUCController::handleUnblockUserRequest() {
+ eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, muc_->getJID()));
+}
+
+void MUCController::handleBlockingStateChanged() {
+ std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+ if (blockList->getState() == BlockList::Available) {
+ if (blockList->isBlocked(toJID_)) {
+ if (!blockedContactAlert_) {
+ blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again"));
+ }
+ chatWindow_->setBlockingState(ChatWindow::IsBlocked);
+ } else {
+ if (blockedContactAlert_) {
+ chatWindow_->removeAlert(*blockedContactAlert_);
+ blockedContactAlert_.reset();
+ }
+ chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
+ }
+ }
}
void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) {
- chatWindow_->setAffiliations(affiliation, jids);
+ chatWindow_->setAffiliations(affiliation, jids);
}
void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) {
- // log only incoming messages
- if (isIncoming && historyController_) {
- historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp);
- }
+ // log only incoming messages
+ if (isIncoming && historyController_) {
+ historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp);
+ }
+}
+
+JID MUCController::messageCorrectionJID(const JID& fromJID) {
+ return fromJID;
}
void MUCController::addRecentLogs() {
- if (!historyController_) {
- return;
- }
+ if (!historyController_) {
+ return;
+ }
- joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_);
+ joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_);
- foreach (const HistoryMessage& message, joinContext_) {
- bool senderIsSelf = nick_ == message.getFromJID().getResource();
+ for (const auto& message : joinContext_) {
+ bool senderIsSelf = nick_ == message.getFromJID().getResource();
- // the chatWindow uses utc timestamps
- addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()), HighlightAction());
- }
+ // the chatWindow uses utc timestamps
+ addMessage(chatMessageParser_->parseMessageBody(message.getMessage()), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, std::make_shared<SecurityLabel>(), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()));
+ }
}
-void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) {
- std::string body = newMessage->getBody();
- JID jid = newMessage->getFrom();
- boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp();
+void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) {
+ std::string body = newMessage->getBody().get_value_or("");
+ JID jid = newMessage->getFrom();
+ boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp();
- reverse_foreach (const HistoryMessage& message, joinContext_) {
- boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset());
- if (time && time < messageTime) {
- break;
- }
- if (time && time != messageTime) {
- continue;
- }
- if (message.getFromJID() != jid) {
- continue;
- }
- if (message.getMessage() != body) {
- continue;
- }
+ for (const auto& message : boost::adaptors::reverse(joinContext_)) {
+ boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset());
+ if (time && time < messageTime) {
+ break;
+ }
+ if (time && time != messageTime) {
+ continue;
+ }
+ if (message.getFromJID() != jid) {
+ continue;
+ }
+ if (message.getMessage() != body) {
+ continue;
+ }
- // Mark the message as unreadable
- newMessage->setBody("");
- }
+ // Mark the message as unreadable
+ newMessage->setBody("");
+ }
}
void MUCController::setNick(const std::string& nick) {
- nick_ = nick;
- highlighter_->setNick(nick_);
+ nick_ = nick;
+ chatMessageParser_->setNick(nick);
}
Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) {
- Form::ref result = boost::make_shared<Form>(Form::SubmitType);
- std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"};
- std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4);
- foreach (boost::shared_ptr<FormField> field, roomConfigurationForm->getFields()) {
- boost::shared_ptr<FormField> resultField;
- if (field->getName() == "muc#roomconfig_enablelogging") {
- resultField = boost::make_shared<FormField>(FormField::BooleanType, "0");
- }
- if (field->getName() == "muc#roomconfig_persistentroom") {
- resultField = boost::make_shared<FormField>(FormField::BooleanType, "0");
- }
- if (field->getName() == "muc#roomconfig_publicroom") {
- resultField = boost::make_shared<FormField>(FormField::BooleanType, "0");
- }
- if (field->getName() == "muc#roomconfig_whois") {
- resultField = boost::make_shared<FormField>(FormField::ListSingleType, "anyone");
- }
-
- if (field->getName() == "FORM_TYPE") {
- resultField = boost::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig");
- }
-
- if (resultField) {
- impromptuConfigsMissing.erase(field->getName());
- resultField->setName(field->getName());
- result->addField(resultField);
- }
- }
-
- foreach (const std::string& config, impromptuConfigsMissing) {
- if (config == "muc#roomconfig_publicroom") {
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection);
- } else if (config == "muc#roomconfig_whois") {
- chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection);
- }
- }
-
- return result;
-}
-
-void MUCController::setImpromptuWindowTitle() {
- std::string title;
- typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
- std::map<std::string, MUCOccupant> occupants = muc_->getOccupants();
- if (occupants.size() <= 1) {
- title = QT_TRANSLATE_NOOP("", "Empty Chat");
- } else {
- foreach (StringMUCOccupantPair pair, occupants) {
- if (pair.first != nick_) {
- title += (title.empty() ? "" : ", ") + pair.first;
- }
- }
- }
- chatWindow_->setName(title);
+ Form::ref result = std::make_shared<Form>(Form::SubmitType);
+ std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"};
+ std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4);
+ for (const auto& field : roomConfigurationForm->getFields()) {
+ std::shared_ptr<FormField> resultField;
+ if (field->getName() == "muc#roomconfig_enablelogging") {
+ resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
+ }
+ if (field->getName() == "muc#roomconfig_persistentroom") {
+ resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
+ }
+ if (field->getName() == "muc#roomconfig_publicroom") {
+ resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
+ }
+ if (field->getName() == "muc#roomconfig_whois") {
+ resultField = std::make_shared<FormField>(FormField::ListSingleType, "anyone");
+ }
+
+ if (field->getName() == "FORM_TYPE") {
+ resultField = std::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig");
+ }
+
+ if (resultField) {
+ impromptuConfigsMissing.erase(field->getName());
+ resultField->setName(field->getName());
+ result->addField(resultField);
+ }
+ }
+
+ for (const auto& config : impromptuConfigsMissing) {
+ if (config == "muc#roomconfig_publicroom") {
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection);
+ } else if (config == "muc#roomconfig_whois") {
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection);
+ }
+ }
+
+ return result;
}
void MUCController::handleRoomUnlocked() {
- // Handle buggy MUC implementations where the joined room already exists and is unlocked.
- // Configure the room again in this case.
- if (!isImpromptuAlreadyConfigured_) {
- if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) {
- muc_->requestConfigurationForm();
- } else if (isImpromptu_) {
- onImpromptuConfigCompleted();
- }
- }
+ // Handle buggy MUC implementations where the joined room already exists and is unlocked.
+ // Configure the room again in this case.
+ if (!isImpromptuAlreadyConfigured_) {
+ if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) {
+ muc_->requestConfigurationForm();
+ } else if (isImpromptu_) {
+ onImpromptuConfigCompleted();
+ }
+ }
+}
+
+void MUCController::setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) {
+ ChatControllerBase::setAvailableServerFeatures(info);
+ if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
+ std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+
+ blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&MUCController::handleBlockingStateChanged, this));
+ blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&MUCController::handleBlockingStateChanged, this));
+ blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&MUCController::handleBlockingStateChanged, this));
+
+ handleBlockingStateChanged();
+ }
+}
+
+void MUCController::handleMUCBookmarkAdded(const MUCBookmark& bookmark) {
+ if (bookmark.getRoom() == muc_->getJID()) {
+ updateChatWindowBookmarkStatus(bookmark);
+ }
+}
+
+void MUCController::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) {
+ if (bookmark.getRoom() == muc_->getJID()) {
+ updateChatWindowBookmarkStatus(boost::optional<MUCBookmark>());
+ }
+}
+
+void MUCController::updateChatWindowBookmarkStatus(const boost::optional<MUCBookmark>& bookmark) {
+ assert(chatWindow_);
+ if (bookmark) {
+ if (bookmark->getAutojoin()) {
+ chatWindow_->setBookmarkState(ChatWindow::RoomAutoJoined);
+ }
+ else {
+ chatWindow_->setBookmarkState(ChatWindow::RoomBookmarked);
+ }
+ }
+ else {
+ chatWindow_->setBookmarkState(ChatWindow::RoomNotBookmarked);
+ }
+}
+
+void MUCController::displaySubjectIfChanged(const std::string& subject) {
+ if (subject_ != subject) {
+ if (!subject.empty()) {
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % subject)), ChatWindow::DefaultDirection);
+ }
+ else {
+ chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject has been removed")))), ChatWindow::DefaultDirection);
+ }
+ subject_ = subject;
+ }
+}
+
+void MUCController::addChatSystemMessage() {
+ lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(lastStartMessage_), ChatWindow::DefaultDirection);
+}
+
+void MUCController::setChatWindowTitle(const std::string& title) {
+ chatWindowTitle_ = title;
+ chatWindow_->setName(chatWindowTitle_);
+}
+
+void MUCController::requestSecurityMarking() {
+ auto discoInfoRequest = GetDiscoInfoRequest::create(muc_->getJID(), iqRouter_);
+ discoInfoRequest->onResponse.connect(
+ [this](std::shared_ptr<DiscoInfo> discoInfoRef, ErrorPayload::ref errorPayloadRef) {
+ if (!discoInfoRef || errorPayloadRef) {
+ return;
+ }
+ const std::vector<Form::ref>& extensionsList = discoInfoRef->getExtensions();
+ if (extensionsList.empty()) {
+ return;
+ }
+ // Get the correct form if it exists
+ Form::ref roomInfoForm;
+ for (const auto& form : extensionsList) {
+ if (form->getFormType() == "http://jabber.org/protocol/muc#roominfo") {
+ roomInfoForm = form;
+ break;
+ }
+ }
+ if (!roomInfoForm) {
+ return;
+ }
+ // It exists, now examine the security marking data
+ auto marking = roomInfoForm->getField("x-isode#roominfo_marking");
+ if (!marking) {
+ return;
+ }
+ // Now we know the marking is valid
+ auto markingValue = marking->getTextSingleValue();
+ if (markingValue == "") {
+ setMUCSecurityMarkingDefault();
+ return;
+ }
+ auto markingForegroundColor = roomInfoForm->getField("x-isode#roominfo_marking_fg_color");
+ auto markingBackgroundColor = roomInfoForm->getField("x-isode#roominfo_marking_bg_color");
+ std::string markingForegroundColorValue = "Black";
+ std::string markingBackgroundColorValue = "White";
+ if (markingForegroundColor && markingForegroundColor->getTextSingleValue() != "") {
+ markingForegroundColorValue = markingForegroundColor->getTextSingleValue();
+ }
+ if (markingBackgroundColor && markingBackgroundColor->getTextSingleValue() != "") {
+ markingBackgroundColorValue = markingBackgroundColor->getTextSingleValue();
+ }
+ setMUCSecurityMarking(markingValue, markingForegroundColorValue, markingBackgroundColorValue);
+ }
+ );
+ discoInfoRequest->send();
+}
+
+void MUCController::setMUCSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue) {
+ roomSecurityMarking_ = markingValue;
+ chatWindow_->setChatSecurityMarking(markingValue, markingForegroundColorValue, markingBackgroundColorValue);
+}
+
+void MUCController::setMUCSecurityMarkingDefault() {
+ roomSecurityMarking_ = "";
+ chatWindow_->removeChatSecurityMarking();
}
}
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 9283438..bd1148f 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -1,151 +1,195 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
+#include <map>
+#include <memory>
#include <set>
#include <string>
-#include <map>
-#include <boost/shared_ptr.hpp>
-#include <boost/signals/connection.hpp>
+#include <boost/signals2.hpp>
+#include <boost/signals2/connection.hpp>
-#include <Swiften/Base/boost_bsignals.h>
-#include <Swiften/Network/Timer.h>
-#include <Swiften/Elements/Message.h>
#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/Elements/Message.h>
#include <Swiften/JID/JID.h>
#include <Swiften/MUC/MUC.h>
-#include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/Network/Timer.h>
#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;
-
- 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, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
- ~MUCController();
- boost::signal<void ()> onUserLeft;
- boost::signal<void ()> onUserJoined;
- boost::signal<void ()> onImpromptuConfigCompleted;
- 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);
- bool isJoined();
- const std::string& getNick();
- 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 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&);
- 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);
- void setNick(const std::string& nick);
- void setImpromptuWindowTitle();
- void handleRoomUnlocked();
- void configureAsImpromptuRoom(Form::ref form);
- Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm);
-
- private:
- MUC::ref muc_;
- UIEventStream* events_;
- std::string nick_;
- std::string desiredNick_;
- Roster* roster_;
- TabComplete* completer_;
- bool parting_;
- bool joined_;
- bool lastWasPresence_;
- bool shouldJoinOnReconnect_;
- bool doneGettingHistory_;
- boost::bsignals::scoped_connection avatarChangedConnection_;
- boost::shared_ptr<Timer> loginCheckTimer_;
- std::set<std::string> currentOccupants_;
- std::vector<NickJoinPart> joinParts_;
- boost::posix_time::ptime lastActivity_;
- boost::optional<std::string> password_;
- XMPPRoster* xmppRoster_;
- std::vector<HistoryMessage> joinContext_;
- size_t renameCounter_;
- bool isImpromptu_;
- bool isImpromptuAlreadyConfigured_;
- };
-}
+ 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;
+ class ClientBlockListManager;
+ class MUCBookmarkManager;
+ class MUCBookmark;
+
+ 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, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* xmppRoster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager, SettingsProvider* settings, Chattables& chattables);
+ virtual ~MUCController() override;
+ boost::signals2::signal<void ()> onUserLeft;
+ boost::signals2::signal<void ()> onUserJoined;
+ boost::signals2::signal<void ()> onImpromptuConfigCompleted;
+ boost::signals2::signal<void (const std::string&, const std::string& )> onUserNicknameChanged;
+ virtual void setOnline(bool online) override;
+ virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) override;
+ 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;
+ void setChatWindowTitle(const std::string& title);
+ protected:
+ virtual void preSendMessageRequest(std::shared_ptr<Message> message) override;
+ virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) override;
+ virtual std::string senderHighlightNameFromMessage(const JID& from) override;
+ virtual std::string senderDisplayNameFromMessage(const JID& from) override;
+ virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const override;
+ virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) override;
+ virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) override;
+ virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) override;
+ virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) override;
+ virtual void cancelReplaces() override;
+ virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) override;
+ virtual JID messageCorrectionJID(const JID& fromJID) override;
+
+ 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(std::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(std::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(std::shared_ptr<Message> message);
+ void updateJoinParts();
+ bool shouldUpdateJoinParts();
+ virtual void dayTicked() override { clearPresenceQueue(); }
+ void processUserPart();
+ virtual void handleBareJIDCapsChanged(const JID& jid) override;
+ 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(std::shared_ptr<UIEvent> event);
+ void addRecentLogs();
+ void checkDuplicates(std::shared_ptr<Message> newMessage);
+ void setNick(const std::string& nick);
+ void handleRoomUnlocked();
+ void configureAsImpromptuRoom(Form::ref form);
+ Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm);
+
+ void handleUnblockUserRequest();
+ void handleBlockingStateChanged();
+
+ void handleMUCBookmarkAdded(const MUCBookmark& bookmark);
+ void handleMUCBookmarkRemoved(const MUCBookmark& bookmark);
+ void updateChatWindowBookmarkStatus(const boost::optional<MUCBookmark>& bookmark);
+
+ void displaySubjectIfChanged(const std::string& sucject);
+ void addChatSystemMessage();
+
+ void requestSecurityMarking();
+ void setMUCSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue);
+ void setMUCSecurityMarkingDefault();
+
+ private:
+ MUC::ref muc_;
+ std::string nick_;
+ std::string desiredNick_;
+ TabComplete* completer_;
+ bool parting_;
+ bool joined_;
+ bool shouldJoinOnReconnect_;
+ bool doneGettingHistory_;
+ boost::signals2::scoped_connection avatarChangedConnection_;
+ std::shared_ptr<Timer> loginCheckTimer_;
+ std::set<std::string> currentOccupants_;
+ std::vector<NickJoinPart> joinParts_;
+ boost::posix_time::ptime lastActivity_;
+ boost::optional<std::string> password_;
+ XMPPRoster* xmppRoster_;
+ std::unique_ptr<Roster> roster_;
+ std::vector<HistoryMessage> joinContext_;
+ size_t renameCounter_;
+ bool isImpromptu_;
+ bool isImpromptuAlreadyConfigured_;
+ RosterVCardProvider* rosterVCardProvider_;
+ std::string lastJoinMessageUID_;
+ std::string lastStartMessage_;
+
+ ClientBlockListManager* clientBlockListManager_;
+ boost::signals2::scoped_connection blockingOnStateChangedConnection_;
+ boost::signals2::scoped_connection blockingOnItemAddedConnection_;
+ boost::signals2::scoped_connection blockingOnItemRemovedConnection_;
+
+ boost::optional<ChatWindow::AlertID> blockedContactAlert_;
+
+ MUCBookmarkManager* mucBookmarkManager_;
+ boost::signals2::scoped_connection mucBookmarkManagerBookmarkAddedConnection_;
+ boost::signals2::scoped_connection mucBookmarkManagerBookmarkRemovedConnection_;
+
+ std::string subject_;
+ bool isInitialJoin_;
+ std::string chatWindowTitle_;
+ };
+}
diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp
index f0f8a37..0b54d25 100644
--- a/Swift/Controllers/Chat/MUCSearchController.cpp
+++ b/Swift/Controllers/Chat/MUCSearchController.cpp
@@ -1,183 +1,183 @@
/*
- * Copyright (c) 2010-2011 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2019 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
-#include "Swift/Controllers/Chat/MUCSearchController.h"
+#include <Swift/Controllers/Chat/MUCSearchController.h>
#include <iostream>
+#include <memory>
#include <boost/bind.hpp>
-#include <boost/shared_ptr.hpp>
-#include <Swiften/Base/foreach.h>
-#include <Swiften/Disco/GetDiscoItemsRequest.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Disco/DiscoServiceWalker.h>
+#include <Swiften/Disco/GetDiscoItemsRequest.h>
+
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
-#include <Swiften/Disco/DiscoServiceWalker.h>
-#include <Swiften/Client/NickResolver.h>
namespace Swift {
static const std::string SEARCHED_SERVICES = "searchedServices";
-MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) {
- itemsInProgress_ = 0;
- loadSavedServices();
+MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(nullptr), walker_(nullptr) {
+ itemsInProgress_ = 0;
+ loadSavedServices();
}
MUCSearchController::~MUCSearchController() {
- delete walker_;
- delete window_;
+ delete walker_;
+ delete window_;
}
void MUCSearchController::openSearchWindow() {
- if (!window_) {
- window_ = factory_->createMUCSearchWindow();
- window_->onSearchService.connect(boost::bind(&MUCSearchController::handleSearchService, this, _1));
- window_->onFinished.connect(boost::bind(&MUCSearchController::handleMUCSearchFinished, this, _1));
- window_->addSavedServices(savedServices_);
- }
- handleSearchService(JID(jid_.getDomain()));
- window_->show();
+ if (!window_) {
+ window_ = factory_->createMUCSearchWindow();
+ window_->onSearchService.connect(boost::bind(&MUCSearchController::handleSearchService, this, _1));
+ window_->onFinished.connect(boost::bind(&MUCSearchController::handleMUCSearchFinished, this, _1));
+ window_->addSavedServices(savedServices_);
+ }
+ handleSearchService(JID(jid_.getDomain()));
+ window_->show();
}
void MUCSearchController::loadSavedServices() {
- savedServices_.clear();
- foreach (std::string stringItem, String::split(settings_->getStringSetting(SEARCHED_SERVICES), '\n')) {
- savedServices_.push_back(JID(stringItem));
- }
+ savedServices_.clear();
+ for (auto&& stringItem : String::split(settings_->getStringSetting(SEARCHED_SERVICES), '\n')) {
+ savedServices_.push_back(JID(stringItem));
+ }
}
void MUCSearchController::addToSavedServices(const JID& jid) {
- savedServices_.erase(std::remove(savedServices_.begin(), savedServices_.end(), jid), savedServices_.end());
- savedServices_.push_front(jid);
-
- std::string collapsed;
- int i = 0;
- foreach (JID jidItem, savedServices_) {
- if (i >= 15) {
- break;
- }
- if (!collapsed.empty()) {
- collapsed += "\n";
- }
- collapsed += jidItem.toString();
- ++i;
- }
- settings_->storeString(SEARCHED_SERVICES, collapsed);
- window_->addSavedServices(savedServices_);
+ savedServices_.erase(std::remove(savedServices_.begin(), savedServices_.end(), jid), savedServices_.end());
+ savedServices_.push_front(jid);
+
+ std::string collapsed;
+ int i = 0;
+ for (auto&& jidItem : savedServices_) {
+ if (i >= 15) {
+ break;
+ }
+ if (!collapsed.empty()) {
+ collapsed += "\n";
+ }
+ collapsed += jidItem.toString();
+ ++i;
+ }
+ settings_->storeString(SEARCHED_SERVICES, collapsed);
+ window_->addSavedServices(savedServices_);
}
void MUCSearchController::handleSearchService(const JID& jid) {
- if (!jid.isValid()) {
- //Set Window to say error this isn't valid
- return;
- }
- addToSavedServices(jid);
-
- services_.clear();
- serviceDetails_.clear();
-
- window_->setSearchInProgress(true);
- refreshView();
-
- if (walker_) {
- walker_->endWalk();
- walker_->onServiceFound.disconnect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2));
- walker_->onWalkComplete.disconnect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this));
- delete walker_;
- }
-
- SWIFT_LOG(debug) << "Starting walking MUC services" << std::endl;
- itemsInProgress_ = 0;
- walker_ = new DiscoServiceWalker(jid, iqRouter_);
- walker_->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2));
- walker_->onWalkComplete.connect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this));
- walker_->beginWalk();
+ if (!jid.isValid()) {
+ //Set Window to say error this isn't valid
+ return;
+ }
+ addToSavedServices(jid);
+
+ services_.clear();
+ serviceDetails_.clear();
+
+ window_->setSearchInProgress(true);
+ refreshView();
+
+ if (walker_) {
+ walker_->endWalk();
+ walker_->onServiceFound.disconnect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2));
+ walker_->onWalkComplete.disconnect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this));
+ delete walker_;
+ }
+
+ SWIFT_LOG(debug) << "Starting walking MUC services";
+ itemsInProgress_ = 0;
+ walker_ = new DiscoServiceWalker(jid, iqRouter_);
+ walker_->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2));
+ walker_->onWalkComplete.connect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this));
+ walker_->beginWalk();
}
-void MUCSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) {
- bool isMUC = false;
- std::string name;
- foreach (DiscoInfo::Identity identity, info->getIdentities()) {
- if ((identity.getCategory() == "directory"
- && identity.getType() == "chatroom")
- || (identity.getCategory() == "conference"
- && identity.getType() == "text")) {
- isMUC = true;
- name = identity.getName();
- }
- }
- if (isMUC) {
- SWIFT_LOG(debug) << "MUC Service found: " << jid << std::endl;
- services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end());
- services_.push_back(jid);
- serviceDetails_[jid].setName(name);
- serviceDetails_[jid].setJID(jid);
- serviceDetails_[jid].setComplete(false);
- itemsInProgress_++;
- SWIFT_LOG(debug) << "Requesting items of " << jid << " (" << itemsInProgress_ << " item requests in progress)" << std::endl;
- GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_);
- discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid));
- discoItemsRequest->send();
- }
- else {
- removeService(jid);
- }
- refreshView();
+void MUCSearchController::handleDiscoServiceFound(const JID& jid, std::shared_ptr<DiscoInfo> info) {
+ bool isMUC = false;
+ std::string name;
+ for (auto&& identity : info->getIdentities()) {
+ if ((identity.getCategory() == "directory"
+ && identity.getType() == "chatroom")
+ || (identity.getCategory() == "conference"
+ && identity.getType() == "text")) {
+ isMUC = true;
+ name = identity.getName();
+ }
+ }
+ if (isMUC) {
+ SWIFT_LOG(debug) << "MUC Service found: " << jid;
+ services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end());
+ services_.push_back(jid);
+ serviceDetails_[jid].setName(name);
+ serviceDetails_[jid].setJID(jid);
+ serviceDetails_[jid].setComplete(false);
+ itemsInProgress_++;
+ SWIFT_LOG(debug) << "Requesting items of " << jid << " (" << itemsInProgress_ << " item requests in progress)";
+ GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_);
+ discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid));
+ discoItemsRequest->send();
+ }
+ else {
+ removeService(jid);
+ }
+ refreshView();
}
void MUCSearchController::handleDiscoWalkFinished() {
- SWIFT_LOG(debug) << "MUC Walk finished" << std::endl;
- updateInProgressness();
+ SWIFT_LOG(debug) << "MUC Walk finished";
+ updateInProgressness();
}
void MUCSearchController::removeService(const JID& jid) {
- serviceDetails_.erase(jid);
- services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end());
- refreshView();
+ serviceDetails_.erase(jid);
+ services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end());
+ refreshView();
}
-void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) {
- itemsInProgress_--;
- SWIFT_LOG(debug) << "Items received for " << jid << " (" << itemsInProgress_ << " item requests in progress)" << std::endl;
- updateInProgressness();
- if (error) {
- handleDiscoError(jid, error);
- return;
- }
- serviceDetails_[jid].clearRooms();
- foreach (DiscoItems::Item item, items->getItems()) {
- serviceDetails_[jid].addRoom(MUCService::MUCRoom(item.getJID().getNode(), item.getName(), -1));
- }
- serviceDetails_[jid].setComplete(true);
- refreshView();
+void MUCSearchController::handleRoomsItemsResponse(std::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) {
+ itemsInProgress_--;
+ SWIFT_LOG(debug) << "Items received for " << jid << " (" << itemsInProgress_ << " item requests in progress)";
+ updateInProgressness();
+ if (error) {
+ handleDiscoError(jid, error);
+ return;
+ }
+ serviceDetails_[jid].clearRooms();
+ for (auto&& item : items->getItems()) {
+ serviceDetails_[jid].addRoom(MUCService::MUCRoom(item.getJID().getNode(), item.getName(), -1));
+ }
+ serviceDetails_[jid].setComplete(true);
+ refreshView();
}
void MUCSearchController::handleDiscoError(const JID& jid, ErrorPayload::ref error) {
- serviceDetails_[jid].setComplete(true);
- serviceDetails_[jid].setError(error->getText());
+ serviceDetails_[jid].setComplete(true);
+ serviceDetails_[jid].setError(error->getText());
}
void MUCSearchController::refreshView() {
- window_->clearList();
- foreach (JID jid, services_) {
- window_->addService(serviceDetails_[jid]);
- }
+ window_->clearList();
+ for (auto&& jid : services_) {
+ window_->addService(serviceDetails_[jid]);
+ }
}
void MUCSearchController::updateInProgressness() {
- window_->setSearchInProgress((walker_ && walker_->isActive()) || itemsInProgress_ > 0);
+ window_->setSearchInProgress((walker_ && walker_->isActive()) || itemsInProgress_ > 0);
}
void MUCSearchController::handleMUCSearchFinished(const boost::optional<JID>& result) {
- if (result) {
- onMUCSelected(*result);
- }
+ if (result) {
+ onMUCSelected(*result);
+ }
}
}
diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h
index f90e4a7..f853bcd 100644
--- a/Swift/Controllers/Chat/MUCSearchController.h
+++ b/Swift/Controllers/Chat/MUCSearchController.h
@@ -1,124 +1,124 @@
/*
- * Copyright (c) 2010-2011 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2016 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
-#include <vector>
#include <map>
+#include <memory>
+#include <string>
+#include <vector>
-#include <boost/shared_ptr.hpp>
-#include "Swiften/Base/boost_bsignals.h"
+#include <boost/signals2.hpp>
-#include <string>
-#include "Swiften/JID/JID.h"
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/DiscoItems.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/JID/JID.h>
-#include "Swift/Controllers/UIEvents/UIEvent.h"
-#include "Swift/Controllers/ProfileSettingsProvider.h"
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/Elements/DiscoItems.h"
-#include "Swiften/Elements/ErrorPayload.h"
+#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swift/Controllers/UIEvents/UIEvent.h>
namespace Swift {
- class UIEventStream;
- class MUCSearchWindow;
- class MUCSearchWindowFactory;
- class IQRouter;
- class DiscoServiceWalker;
- class NickResolver;
-
- class MUCService {
- public:
- class MUCRoom {
- public:
- MUCRoom(const std::string& node, const std::string& name, int occupants) : node_(node), name_(name), occupants_(occupants) {}
- std::string getNode() {return node_;}
- std::string getName() {return name_;}
- int getOccupantCount() {return occupants_;}
- private:
- std::string node_;
- std::string name_;
- int occupants_;
- };
-
- MUCService() {error_ = false; complete_ = false;}
-
- void setComplete(bool complete) {
- complete_ = complete;
- }
-
- void setName(const std::string& name) {
- name_ = name;
- }
-
- void setJID(const JID& jid) {
- jid_ = jid;
- }
-
- bool getComplete() const {
- return complete_;
- }
-
- JID getJID() const {
- return jid_;
- }
-
- std::string getName() const {
- return name_;
- }
-
- void setError(const std::string& errorText) {error_ = true; errorText_ = errorText;}
-
- void clearRooms() {rooms_.clear();}
-
- void addRoom(const MUCRoom& room) {rooms_.push_back(room);}
-
- std::vector<MUCRoom> getRooms() const {return rooms_;}
- private:
- std::string name_;
- JID jid_;
- std::vector<MUCRoom> rooms_;
- bool complete_;
- bool error_;
- std::string errorText_;
- };
-
- class MUCSearchController {
- public:
- MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings);
- ~MUCSearchController();
-
- void openSearchWindow();
-
- public:
- boost::signal<void (const JID&)> onMUCSelected;
-
- private:
- void handleSearchService(const JID& jid);
- void handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid);
- void handleDiscoError(const JID& jid, ErrorPayload::ref error);
- void handleDiscoServiceFound(const JID&, boost::shared_ptr<DiscoInfo>);
- void handleDiscoWalkFinished();
- void handleMUCSearchFinished(const boost::optional<JID>& result);
- void removeService(const JID& jid);
- void refreshView();
- void loadSavedServices();
- void addToSavedServices(const JID& jid);
- void updateInProgressness();
-
- private:
- JID jid_;
- MUCSearchWindowFactory* factory_;
- IQRouter* iqRouter_;
- ProfileSettingsProvider* settings_;
- MUCSearchWindow* window_;
- DiscoServiceWalker* walker_;
- std::list<JID> services_;
- std::list<JID> savedServices_;
- std::map<JID, MUCService> serviceDetails_;
- std::vector<DiscoServiceWalker*> walksInProgress_;
- int itemsInProgress_;
- };
+ class UIEventStream;
+ class MUCSearchWindow;
+ class MUCSearchWindowFactory;
+ class IQRouter;
+ class DiscoServiceWalker;
+ class NickResolver;
+
+ class MUCService {
+ public:
+ class MUCRoom {
+ public:
+ MUCRoom(const std::string& node, const std::string& name, int occupants) : node_(node), name_(name), occupants_(occupants) {}
+ std::string getNode() {return node_;}
+ std::string getName() {return name_;}
+ int getOccupantCount() {return occupants_;}
+ private:
+ std::string node_;
+ std::string name_;
+ int occupants_;
+ };
+
+ MUCService() {error_ = false; complete_ = false;}
+
+ void setComplete(bool complete) {
+ complete_ = complete;
+ }
+
+ void setName(const std::string& name) {
+ name_ = name;
+ }
+
+ void setJID(const JID& jid) {
+ jid_ = jid;
+ }
+
+ bool getComplete() const {
+ return complete_;
+ }
+
+ JID getJID() const {
+ return jid_;
+ }
+
+ std::string getName() const {
+ return name_;
+ }
+
+ void setError(const std::string& errorText) {error_ = true; errorText_ = errorText;}
+
+ void clearRooms() {rooms_.clear();}
+
+ void addRoom(const MUCRoom& room) {rooms_.push_back(room);}
+
+ std::vector<MUCRoom> getRooms() const {return rooms_;}
+ private:
+ std::string name_;
+ JID jid_;
+ std::vector<MUCRoom> rooms_;
+ bool complete_;
+ bool error_;
+ std::string errorText_;
+ };
+
+ class MUCSearchController {
+ public:
+ MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings);
+ ~MUCSearchController();
+
+ void openSearchWindow();
+
+ public:
+ boost::signals2::signal<void (const JID&)> onMUCSelected;
+
+ private:
+ void handleSearchService(const JID& jid);
+ void handleRoomsItemsResponse(std::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid);
+ void handleDiscoError(const JID& jid, ErrorPayload::ref error);
+ void handleDiscoServiceFound(const JID&, std::shared_ptr<DiscoInfo>);
+ void handleDiscoWalkFinished();
+ void handleMUCSearchFinished(const boost::optional<JID>& result);
+ void removeService(const JID& jid);
+ void refreshView();
+ void loadSavedServices();
+ void addToSavedServices(const JID& jid);
+ void updateInProgressness();
+
+ private:
+ JID jid_;
+ MUCSearchWindowFactory* factory_;
+ IQRouter* iqRouter_;
+ ProfileSettingsProvider* settings_;
+ MUCSearchWindow* window_;
+ DiscoServiceWalker* walker_;
+ std::list<JID> services_;
+ std::list<JID> savedServices_;
+ std::map<JID, MUCService> serviceDetails_;
+ std::vector<DiscoServiceWalker*> walksInProgress_;
+ int itemsInProgress_;
+ };
}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp
new file mode 100644
index 0000000..e010656
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <memory>
+#include <string>
+
+#include <gtest/gtest.h>
+#include <hippomocks.h>
+
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Disco/DummyEntityCapsProvider.h>
+#include <Swiften/Network/DummyTimerFactory.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Roster/XMPPRoster.h>
+#include <Swiften/Roster/XMPPRosterImpl.h>
+#include <Swiften/VCards/VCardManager.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
+
+#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
+#include <Swift/Controllers/Chat/ChatController.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Chat/Chattables.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UnitTest/MockChatWindow.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+
+using namespace Swift;
+
+/**
+ * Most of the ChatController tests are in ChatsManagerTest.
+ * New tests related with ChatController should be added here,
+ * and old tests should be migrated when possible.
+ */
+
+class ExtendedChatController : public ChatController {
+public:
+ ExtendedChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, SettingsProvider* settings, Chattables& chattables) :
+ ChatController(self, stanzaChannel, iqRouter, chatWindowFactory, contact, nickResolver, presenceOracle, avatarManager, isInMUC, useDelayForLatency, eventStream, timerFactory, eventController, entityCapsProvider, userWantsReceipts, historyController, mucRegistry, highlightManager, clientBlockListManager, chatMessageParser, autoAcceptMUCInviteDecider, settings, chattables) {
+ }
+
+ std::map<std::shared_ptr<Stanza>, std::string> getUnackedStanzas() { return unackedStanzas_; }
+ std::map<std::string, std::shared_ptr<Stanza>> getFailedStanzas() { return failedStanzas_; }
+};
+
+class ChatControllerTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ self_ = JID("alice@wonderland.lit");
+ other_ = JID("whiterabbit@wonderland.lit");
+ stanzaChannel_ = new DummyStanzaChannel();
+ iqChannel_ = new DummyIQChannel();
+ iqRouter_ = new IQRouter(iqChannel_);
+ eventController_ = new EventController();
+ xmppRoster_ = new XMPPRosterImpl();
+ vCardManager_ = new VCardManager(self_, iqRouter_, vCardMemoryStorage_);
+ mucRegistry_ = new MUCRegistry();
+ nickResolver_ = new NickResolver(self_, xmppRoster_, vCardManager_, mucRegistry_);
+ presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
+ avatarManager_ = new NullAvatarManager();
+ uiEventStream_ = new UIEventStream();
+ timerFactory_ = new DummyTimerFactory();
+ entityCapsProvider_ = new DummyEntityCapsProvider();
+ settings_ = new DummySettingsProvider();
+ highlightManager_ = new HighlightManager(settings_);
+ highlightManager_->resetToDefaultConfiguration();
+ clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+ autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(self_.getDomain(), xmppRoster_, settings_);
+ chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat);
+ mocks_ = new MockRepository();
+ window_ = new MockChatWindow();
+ chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(other_, uiEventStream_).Return(window_);
+ chattables_ = std::make_unique<Chattables>();
+
+ controller_ = new ExtendedChatController(self_, stanzaChannel_, iqRouter_, chatWindowFactory_, other_, nickResolver_, presenceOracle_, avatarManager_, false, false, uiEventStream_, timerFactory_, eventController_, entityCapsProvider_, false, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, nullptr, settings_, *chattables_);
+ }
+ virtual void TearDown() {
+ delete controller_;
+ chattables_.reset();
+ chatMessageParser_.reset();
+ delete autoAcceptMUCInviteDecider_;
+ delete clientBlockListManager_;
+ delete highlightManager_;
+ delete settings_;
+ delete entityCapsProvider_;
+ delete timerFactory_;
+ delete uiEventStream_;
+ delete avatarManager_;
+ delete presenceOracle_;
+ delete nickResolver_;
+ delete mucRegistry_;
+ delete vCardManager_;
+ delete xmppRoster_;
+ delete eventController_;
+ delete iqRouter_;
+ delete iqChannel_;
+ delete stanzaChannel_;
+ }
+
+ JID self_, other_;
+ AvatarManager* avatarManager_ = nullptr;
+ ExtendedChatController* controller_ = nullptr;
+ ChatWindowFactory* chatWindowFactory_;
+ ClientBlockListManager* clientBlockListManager_;
+ EventController* eventController_ = nullptr;
+ EntityCapsProvider* entityCapsProvider_ = nullptr;
+ IQChannel* iqChannel_ = nullptr;
+ IQRouter* iqRouter_ = nullptr;
+ MockRepository* mocks_;
+ MockChatWindow* window_;
+ MUCRegistry* mucRegistry_ = nullptr;
+ NickResolver* nickResolver_ = nullptr;
+ PresenceOracle* presenceOracle_ = nullptr;
+ DummyStanzaChannel* stanzaChannel_ = nullptr;
+ TimerFactory* timerFactory_;
+ XMPPRosterImpl* xmppRoster_ = nullptr;
+ UIEventStream* uiEventStream_;
+ VCardManager* vCardManager_ = nullptr;
+ VCardMemoryStorage* vCardMemoryStorage_ = nullptr;
+ DummySettingsProvider* settings_;
+ HighlightManager* highlightManager_;
+ std::shared_ptr<ChatMessageParser> chatMessageParser_;
+ AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
+ std::unique_ptr<Chattables> chattables_;
+
+};
+
+TEST_F(ChatControllerTest, testResendMessage) {
+ std::string msgBody("TestMsg");
+ stanzaChannel_->setStreamManagementEnabled(true);
+ window_->onSendMessageRequest(msgBody, false);
+ {
+ auto failedStanzas = controller_->getFailedStanzas();
+ auto unackedStanzas = controller_->getUnackedStanzas();
+ ASSERT_EQ(failedStanzas.size(), 0);
+ ASSERT_EQ(unackedStanzas.size(), 1);
+ }
+ //Disconnecting to fail the stanza
+ controller_->setOnline(false);
+ controller_->setOnline(true);
+ {
+ auto failedStanzas = controller_->getFailedStanzas();
+ auto unackedStanzas = controller_->getUnackedStanzas();
+ ASSERT_EQ(failedStanzas.size(), 1);
+ ASSERT_EQ(unackedStanzas.size(), 0);
+ }
+ window_->onResendMessageRequest("id");
+ {
+ auto failedStanzas = controller_->getFailedStanzas();
+ auto unackedStanzas = controller_->getUnackedStanzas();
+ ASSERT_EQ(failedStanzas.size(), 0);
+ ASSERT_EQ(unackedStanzas.size(), 1);
+ }
+}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp
new file mode 100644
index 0000000..9561e2b
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/version.hpp>
+
+#include <gtest/gtest.h>
+
+#include <Swiften/Base/ByteArray.h>
+#include <Swiften/StringCodecs/Base64.h>
+
+#include <Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+
+using namespace Swift;
+
+class ChatListWindowChatTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {}
+ virtual void TearDown() {}
+
+ void testOptionalPasswordValue(const boost::optional<std::string>& value1, const std::string& value2) {
+ auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION();
+ if (archiveLibraryVersion != Swift::BoostArchiveSkipVersion) {
+ EXPECT_EQ(value1.get_value_or(""), value2);
+ }
+ else {
+ EXPECT_EQ(value1.get_value_or(""), "");
+ }
+ }
+
+ std::string chatsSerialise(const std::vector<ChatListWindow::Chat>& chats) {
+ std::stringstream serializeStream;
+ boost::archive::text_oarchive oa(serializeStream);
+ oa & chats;
+ std::string serializedStr = Base64::encode(createByteArray(serializeStream.str()));
+ return serializedStr;
+ }
+
+ std::vector<ChatListWindow::Chat> chatsDeserialise(const std::string& b64chats) {
+ ByteArray debase64 = Base64::decode(b64chats);
+ std::vector<ChatListWindow::Chat> recentChats;
+ std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size()));
+ try {
+ boost::archive::text_iarchive ia(deserializeStream);
+ ia >> recentChats;
+ }
+ catch (const boost::archive::archive_exception& e) {
+ EXPECT_TRUE(false) << "Archive Version:" << boost::archive::BOOST_ARCHIVE_VERSION() << " " << e.what() << std::endl;
+ recentChats.clear();
+ return recentChats;
+ }
+ recentChats.erase(std::remove(recentChats.begin(), recentChats.end(), ChatListWindow::Chat()), recentChats.end());
+ return recentChats;
+ }
+};
+
+TEST_F(ChatListWindowChatTest, testNormalSerialization) {
+ ChatListWindow::Chat chat1("swift@rooms.swift.im", "swift@rooms.swift.im", "Some text 0", 0, StatusShow::None, "", false, false, "Nick Name");
+ ChatListWindow::Chat chat2("testuser1@domain.com", "swift@rooms2.swift.im", "Some text 1", 0, StatusShow::None, "", false, false, "Nick Name", std::string("pass"));
+ ChatListWindow::Chat chat3("testuser2@domain.com", "room 2", "Some text 2", 0, StatusShow::None, "", true, false, "Nick Name");
+ ChatListWindow::Chat chat4("testuser3@domain.com", "room 3", "Some text 2", 0, StatusShow::None, "", true, false, "Nick Name", std::string("pass"));
+
+ std::map<std::string, JID> impromptuJIDs;
+ impromptuJIDs["testuser1@domain.com"] = "testuser1@domain.com";
+ impromptuJIDs["testuser2@domain.com"] = "testuser2@domain.com";
+ std::map<JID, std::string> inviteesNames;
+ inviteesNames["user1@domain.com"] = "user1@domain.com";
+
+ chat3.impromptuJIDs = impromptuJIDs;
+ chat3.inviteesNames = inviteesNames;
+ chat4.impromptuJIDs = impromptuJIDs;
+ chat4.inviteesNames = inviteesNames;
+
+ std::vector<ChatListWindow::Chat> chats;
+ chats.push_back(chat1);
+ chats.push_back(chat2);
+ chats.push_back(chat3);
+ chats.push_back(chat4);
+
+ auto base64 = chatsSerialise(chats);
+ ASSERT_TRUE(base64.size() > 0);
+ auto restoredChats = chatsDeserialise(base64);
+ ASSERT_EQ(restoredChats.size(), 4);
+
+ EXPECT_FALSE(restoredChats[0].isMUC);
+ EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].activity, "Some text 0");
+ EXPECT_EQ(restoredChats[0].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0);
+ testOptionalPasswordValue(restoredChats[0].password, "");
+ EXPECT_EQ(restoredChats[0].inviteesNames.size(), 0);
+
+ EXPECT_FALSE(restoredChats[1].isMUC);
+ EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im");
+ EXPECT_EQ(restoredChats[1].activity, "Some text 1");
+ EXPECT_EQ(restoredChats[1].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0);
+ testOptionalPasswordValue(restoredChats[1].password, "pass");
+ EXPECT_EQ(restoredChats[1].inviteesNames.size(), 0);
+
+ EXPECT_TRUE(restoredChats[2].isMUC);
+ EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[2].chatName, "room 2");
+ EXPECT_EQ(restoredChats[2].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[2].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ testOptionalPasswordValue(restoredChats[2].password, "");
+ ASSERT_EQ(restoredChats[2].inviteesNames.size(), 1);
+ EXPECT_EQ(restoredChats[2].inviteesNames["user1@domain.com"], "user1@domain.com");
+
+ EXPECT_TRUE(restoredChats[3].isMUC);
+ EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com");
+ EXPECT_EQ(restoredChats[3].chatName, "room 3");
+ EXPECT_EQ(restoredChats[3].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[3].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ testOptionalPasswordValue(restoredChats[3].password, "pass");
+ ASSERT_EQ(restoredChats[3].inviteesNames.size(), 1);
+ EXPECT_EQ(restoredChats[3].inviteesNames["user1@domain.com"], "user1@domain.com");
+}
+
+TEST_F(ChatListWindowChatTest, testVersionsSerialization) {
+ auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION();
+ /*
+ The following strings are base64 serialised vectors with these Swift::ChatListWindow::Chat elements:
+
+ Chat1: Jid = "swift@rooms.swift.im", ChatName = "swift@rooms.swift.im", Activity = "Some text 0", isMUC = false, nick="Nick Name"
+ Chat2: Jid = "testuser1@domain.com", ChatName = "swift@rooms2.swift.im", Activity = "Some text 1", isMUC = false, nick="Nick Name", password = "pass"
+ Chat3: Jid = "testuser2@domain.com", ChatName = "room2", Activity = "Some text 2", isMUC = true, nick="Nick Name", impromptuJIDs, inviteesNames
+ Chat4: Jid = "testuser3@domain.com", ChatName = "room3", Activity = "Some text 2", isMUC = true, nick="Nick Name", impromptuJIDs, password = "pass", inviteesNames
+
+ impromptuJIDs = {("testuser1@domain.com","testuser1@domain.com"), ("testuser2@domain.com", "testuser2@domain.com")}
+ inviteesNames = {("user1@domain.com","user1@domain.com")}
+ */
+ std::string serializedChats_BoostArchiveV10_ClassVersion_0 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAwIDAgMCAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIxIHN3aWZ0QHJvb21zMi5zd2lmdC5pbSAxMSBTb21lIHRleHQgMSAwIDkgTmljayBOYW1lIDAgMCAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSA2IHJvb20gMiAxMSBTb21lIHRleHQgMiAxIDkgTmljayBOYW1lIDIgMCAwIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIzQGRvbWFpbi5jb20gNiByb29tIDMgMTEgU29tZSB0ZXh0IDIgMSA5IE5pY2sgTmFtZSAyIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20=";
+ {
+ auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_0);
+ if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) {
+ ASSERT_EQ(restoredChats.size(), 0);
+ }
+ else {
+ ASSERT_EQ(restoredChats.size(), 4);
+
+ EXPECT_FALSE(restoredChats[0].isMUC);
+ EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].activity, "Some text 0");
+ EXPECT_EQ(restoredChats[0].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0);
+
+ EXPECT_FALSE(restoredChats[1].isMUC);
+ EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im");
+ EXPECT_EQ(restoredChats[1].activity, "Some text 1");
+ EXPECT_EQ(restoredChats[1].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0);
+
+ EXPECT_TRUE(restoredChats[2].isMUC);
+ EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[2].chatName, "room 2");
+ EXPECT_EQ(restoredChats[2].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[2].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+
+ EXPECT_TRUE(restoredChats[3].isMUC);
+ EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com");
+ EXPECT_EQ(restoredChats[3].chatName, "room 3");
+ EXPECT_EQ(restoredChats[3].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[3].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ }
+ }
+
+ std::string serializedChats_BoostArchiveV10_ClassVersion_1 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAxIDAgMSAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDAgMCAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIxIHN3aWZ0QHJvb21zMi5zd2lmdC5pbSAxMSBTb21lIHRleHQgMSAwIDkgTmljayBOYW1lIDAgMCAxIDAgNCBwYXNzIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDYgcm9vbSAyIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDAgMCAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAwIDIwIHRlc3R1c2VyM0Bkb21haW4uY29tIDYgcm9vbSAzIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDEgMCA0IHBhc3M=";
+ {
+ auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_1);
+ if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) {
+ ASSERT_EQ(restoredChats.size(), 0);
+ }
+ else {
+ ASSERT_EQ(restoredChats.size(), 4);
+
+ EXPECT_FALSE(restoredChats[0].isMUC);
+ EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].activity, "Some text 0");
+ EXPECT_EQ(restoredChats[0].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0);
+ EXPECT_EQ(restoredChats[0].password.get_value_or(""), "");
+
+ EXPECT_FALSE(restoredChats[1].isMUC);
+ EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im");
+ EXPECT_EQ(restoredChats[1].activity, "Some text 1");
+ EXPECT_EQ(restoredChats[1].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0);
+ EXPECT_EQ(restoredChats[1].password.get_value_or(""), "pass");
+
+ EXPECT_TRUE(restoredChats[2].isMUC);
+ EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[2].chatName, "room 2");
+ EXPECT_EQ(restoredChats[2].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[2].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[2].password.get_value_or(""), "");
+
+ EXPECT_TRUE(restoredChats[3].isMUC);
+ EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com");
+ EXPECT_EQ(restoredChats[3].chatName, "room 3");
+ EXPECT_EQ(restoredChats[3].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[3].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[3].password.get_value_or(""), "pass");
+ }
+ }
+
+ std::string serializedChats_BoostArchiveV10_ClassVersion_2 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAyIDAgMiAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjEgc3dpZnRAcm9vbXMyLnN3aWZ0LmltIDExIFNvbWUgdGV4dCAxIDAgOSBOaWNrIE5hbWUgMCAwIDEgMCA0IHBhc3MgMCAwIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDYgcm9vbSAyIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDAgMCAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAwIDEgMCAwIDAgMTYgdXNlcjFAZG9tYWluLmNvbSAxNiB1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyM0Bkb21haW4uY29tIDYgcm9vbSAzIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDEgMCA0IHBhc3MgMSAwIDE2IHVzZXIxQGRvbWFpbi5jb20gMTYgdXNlcjFAZG9tYWluLmNvbQ==";
+ {
+ auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_2);
+ if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) {
+ ASSERT_EQ(restoredChats.size(), 0);
+ }
+ else {
+ ASSERT_EQ(restoredChats.size(), 4);
+
+ EXPECT_FALSE(restoredChats[0].isMUC);
+ EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im");
+ EXPECT_EQ(restoredChats[0].activity, "Some text 0");
+ EXPECT_EQ(restoredChats[0].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0);
+ EXPECT_EQ(restoredChats[0].password.get_value_or(""), "");
+ EXPECT_EQ(restoredChats[0].inviteesNames.size(), 0);
+
+ EXPECT_FALSE(restoredChats[1].isMUC);
+ EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im");
+ EXPECT_EQ(restoredChats[1].activity, "Some text 1");
+ EXPECT_EQ(restoredChats[1].nick, "Nick Name");
+ EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0);
+ EXPECT_EQ(restoredChats[1].password.get_value_or(""), "pass");
+ EXPECT_EQ(restoredChats[1].inviteesNames.size(), 0);
+
+ EXPECT_TRUE(restoredChats[2].isMUC);
+ EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[2].chatName, "room 2");
+ EXPECT_EQ(restoredChats[2].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[2].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[2].password.get_value_or(""), "");
+ ASSERT_EQ(restoredChats[2].inviteesNames.size(), 1);
+ EXPECT_EQ(restoredChats[2].inviteesNames["user1@domain.com"], "user1@domain.com");
+
+ EXPECT_TRUE(restoredChats[3].isMUC);
+ EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com");
+ EXPECT_EQ(restoredChats[3].chatName, "room 3");
+ EXPECT_EQ(restoredChats[3].activity, "Some text 2");
+ EXPECT_EQ(restoredChats[3].nick, "Nick Name");
+ ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2);
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com");
+ EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com");
+ EXPECT_EQ(restoredChats[3].password.get_value_or(""), "pass");
+ ASSERT_EQ(restoredChats[3].inviteesNames.size(), 1);
+ EXPECT_EQ(restoredChats[3].inviteesNames["user1@domain.com"], "user1@domain.com");
+ }
+ }
+}
+
diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
index 44d7834..163a38a 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
@@ -1,74 +1,292 @@
/*
- * Copyright (c) 2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2013-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
-#include <cppunit/extensions/HelperMacros.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include <hippomocks.h>
+#include <gtest/gtest.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
using namespace Swift;
-class ChatMessageParserTest : public CppUnit::TestFixture {
- CPPUNIT_TEST_SUITE(ChatMessageParserTest);
- CPPUNIT_TEST(testFullBody);
- CPPUNIT_TEST_SUITE_END();
-
-public:
- void setUp() {
- smile1_ = ":)";
- smile1Path_ = "/blah/smile1.png";
- smile2_ = ":(";
- smile2Path_ = "/blah/smile2.jpg";
- emoticons_[smile1_] = smile1Path_;
- emoticons_[smile2_] = smile2Path_;
- }
-
- void tearDown() {
- emoticons_.clear();
- }
-
- void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
- boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]);
- CPPUNIT_ASSERT_EQUAL(text, part->text);
- }
-
- void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
- boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]);
- CPPUNIT_ASSERT_EQUAL(text, part->alternativeText);
- CPPUNIT_ASSERT_EQUAL(path, part->imagePath);
- }
-
- void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
- boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]);
- CPPUNIT_ASSERT_EQUAL(text, part->target);
- }
-
- void testFullBody() {
- ChatMessageParser testling(emoticons_);
- ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom");
- assertEmoticon(result, 0, smile1_, smile1Path_);
- assertText(result, 1, " shiny ");
- assertEmoticon(result, 2, smile2_, smile2Path_);
- assertText(result, 3, " ");
- assertEmoticon(result, 4, smile1_, smile1Path_);
- assertText(result, 5, " ");
- assertURL(result, 6, "http://wonderland.lit/blah");
- assertText(result, 7, " ");
- assertURL(result, 8, "http://denmark.lit");
- assertText(result, 9, " boom boom");
- }
-
-private:
- std::map<std::string, std::string> emoticons_;
- std::string smile1_;
- std::string smile1Path_;
- std::string smile2_;
- std::string smile2Path_;
+// Common test state
+class ChatMessageParserTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ smile1_ = ":)";
+ smile1Path_ = "/blah/smile1.png";
+ smile2_ = ":(";
+ smile2Path_ = "/blah/smile2.jpg";
+ emoticons_[smile1_] = smile1Path_;
+ emoticons_[smile2_] = smile2Path_;
+ }
+
+ virtual void TearDown() {
+ emoticons_.clear();
+ }
+
+ static void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ ASSERT_LT(index, result.getParts().size());
+ std::shared_ptr<ChatWindow::ChatTextMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]);
+ ASSERT_EQ(text, part->text);
+ }
+
+ static void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
+ ASSERT_LT(index, result.getParts().size());
+ std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]);
+ ASSERT_NE(nullptr, part);
+ ASSERT_EQ(text, part->alternativeText);
+ ASSERT_EQ(path, part->imagePath);
+ }
+
+ static void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ ASSERT_LT(index, result.getParts().size());
+ std::shared_ptr<ChatWindow::ChatURIMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]);
+ ASSERT_EQ(text, part->target);
+ }
+
+ void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const HighlightAction& action) {
+ ASSERT_LT(index, result.getParts().size());
+ std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]);
+ ASSERT_EQ(std::string(text), part->text);
+ ASSERT_EQ(action, part->action);
+ }
+
+ static const std::shared_ptr<HighlightConfiguration> highlightConfigFromKeyword(const std::string& keyword, bool matchCase) {
+ std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>();
+ HighlightConfiguration::KeywordHightlight keywordHighlight;
+ keywordHighlight.keyword = keyword;
+ keywordHighlight.matchCaseSensitive = matchCase;
+ keywordHighlight.action.setFrontColor(std::string("#121212"));
+ config->keywordHighlights.push_back(keywordHighlight);
+ return config;
+ }
+
+ static const std::shared_ptr<HighlightConfiguration> mergeHighlightConfig(const std::shared_ptr<HighlightConfiguration>& configA, const std::shared_ptr<HighlightConfiguration>& configB) {
+ std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>();
+ config->keywordHighlights.insert(config->keywordHighlights.end(), configA->keywordHighlights.begin(), configA->keywordHighlights.end());
+ config->keywordHighlights.insert(config->keywordHighlights.end(), configB->keywordHighlights.begin(), configB->keywordHighlights.end());
+ return config;
+ }
+
+ std::map<std::string, std::string> emoticons_;
+ std::string smile1_;
+ std::string smile1Path_;
+ std::string smile2_;
+ std::string smile2Path_;
};
-CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest);
+TEST_F(ChatMessageParserTest, testNoHighlightingWithEmtpyConfiguration) {
+ const std::string no_special_message = "a message with no special content";
+ ChatMessageParser testling(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(no_special_message);
+ assertText(result, 0, no_special_message);
+}
+
+TEST_F(ChatMessageParserTest, testSimpleHighlightAndEmojiAndUrlParsing) {
+ auto highlightConfig = highlightConfigFromKeyword("trigger", false);
+ auto testling = ChatMessageParser(emoticons_, highlightConfig);
+ auto result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom");
+ assertEmoticon(result, 0, smile1_, smile1Path_);
+ assertText(result, 1, " shiny ");
+ assertEmoticon(result, 2, smile2_, smile2Path_);
+ assertText(result, 3, " ");
+ assertHighlight(result, 4, "trigger", highlightConfig->keywordHighlights[0].action);
+ assertText(result, 5, " ");
+ assertEmoticon(result, 6, smile1_, smile1Path_);
+ assertText(result, 7, " ");
+ assertURL(result, 8, "http://wonderland.lit/blah");
+ assertText(result, 9, " ");
+ assertURL(result, 10, "http://denmark.lit");
+ assertText(result, 11, " boom boom");
+}
+
+TEST_F(ChatMessageParserTest, testNoKeywordHighlightAsPartOfLongerWords) {
+ auto testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false));
+ auto result = testling.parseMessageBody("testtriggermessage");
+ assertText(result, 0, "testtriggermessage");
+}
+
+TEST_F(ChatMessageParserTest, testCaseInsensitiveKeyordHighlight) {
+ auto config = highlightConfigFromKeyword("trigger", true);
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("TrIgGeR");
+ assertText(result, 0, "TrIgGeR");
+
+ testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false));
+ result = testling.parseMessageBody("TrIgGeR");
+ assertHighlight(result, 0, "TrIgGeR", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testMultipleKeywordHighlights) {
+ auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false));
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("zero one two three");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "one", config->keywordHighlights[0].action);
+ assertText(result, 2, " two ");
+ assertHighlight(result, 3, "three", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testMultipleCaseInsensitiveKeywordHighlights) {
+ auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false));
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action);
+ assertText(result, 2, " two ");
+ assertHighlight(result, 3, "tHrEe", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testMultipleCaseSensitiveKeywordHighlights) {
+ auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", true));
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action);
+ assertText(result, 2, " two tHrEe");
+
+ config = mergeHighlightConfig(highlightConfigFromKeyword("one", true), highlightConfigFromKeyword("three", false));
+ testling = ChatMessageParser(emoticons_, config);
+ result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero oNe two ");
+ assertHighlight(result, 1, "tHrEe", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testOneEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(" :) ");
+ assertText(result, 0, " ");
+ assertEmoticon(result, 1, smile1_, smile1Path_);
+ assertText(result, 2, " ");
+}
+
+TEST_F(ChatMessageParserTest, testBareEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(":)");
+ assertEmoticon(result, 0, smile1_, smile1Path_);
+}
+
+TEST_F(ChatMessageParserTest, testHiddenEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody("b:)a");
+ assertText(result, 0, "b:)a");
+}
+
+TEST_F(ChatMessageParserTest, testEndlineEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody("Lazy:)");
+ assertText(result, 0, "Lazy");
+ assertEmoticon(result, 1, smile1_, smile1Path_);
+}
+
+TEST_F(ChatMessageParserTest, testBoundedEmoticons) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(":)Lazy:(");
+ assertEmoticon(result, 0, smile1_, smile1Path_);
+ assertText(result, 1, "Lazy");
+ assertEmoticon(result, 2, smile2_, smile2Path_);
+}
+
+TEST_F(ChatMessageParserTest, testEmoticonParenthesis) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody("(Like this :))");
+ assertText(result, 0, "(Like this ");
+ assertEmoticon(result, 1, smile1_, smile1Path_);
+ assertText(result, 2, ")");
+}
+
+TEST_F(ChatMessageParserTest, testSenderAndKeywordHighlighting) {
+ auto config = std::make_shared<HighlightConfiguration>();
+ auto contactHighlight = HighlightConfiguration::ContactHighlight();
+ contactHighlight.action.setFrontColor(std::string("#f0f0f0"));
+ contactHighlight.action.setBackColor(std::string("#0f0f0f"));
+ contactHighlight.name = "Romeo";
+ config->contactHighlights.push_back(contactHighlight);
+ auto keywordHighlight = HighlightConfiguration::KeywordHightlight();
+ keywordHighlight.action.setFrontColor(std::string("#abcdef"));
+ keywordHighlight.action.setBackColor(std::string("#fedcba"));
+ keywordHighlight.keyword = "XMPP";
+ config->keywordHighlights.push_back(keywordHighlight);
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo");
+ assertText(result, 0, "Heard any news about ");
+ assertHighlight(result, 1, "xmpp", keywordHighlight.action);
+ assertText(result, 2, " recently?");
+ ASSERT_EQ(contactHighlight.action, result.getHighlightActionSender());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testKeywordWithEmptyActionIsIgnored) {
+ auto config = std::make_shared<HighlightConfiguration>();
+ auto keywordHighlight = HighlightConfiguration::KeywordHightlight();
+ keywordHighlight.keyword = "XMPP";
+ config->keywordHighlights.push_back(keywordHighlight);
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo");
+ assertText(result, 0, "Heard any news about xmpp recently?");
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testMeMessageAndOwnMention) {
+ auto config = std::make_shared<HighlightConfiguration>();
+ config->ownMentionAction.setFrontColor(std::string("#f0f0f0"));
+ config->ownMentionAction.setBackColor(std::string("#0f0f0f"));
+ config->ownMentionAction.setSoundFilePath(std::string("someSoundFile.wav"));
+ auto ownMentionActionForPart = config->ownMentionAction;
+ ownMentionActionForPart.setSoundFilePath(boost::optional<std::string>());
+ auto testling = ChatMessageParser(emoticons_, config);
+ testling.setNick("Juliet");
+ auto result = testling.parseMessageBody("/me wonders when Juliet is coming?", "Romeo");
+ assertText(result, 0, "wonders when ");
+ assertHighlight(result, 1, "Juliet", ownMentionActionForPart);
+ assertText(result, 2, " is coming?");
+ ASSERT_EQ(true, result.isMeCommand());
+ ASSERT_EQ(config->ownMentionAction, result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testSoundAndNotificationOnDirectMessage) {
+ auto defaultConfiguration = std::make_shared<HighlightConfiguration>();
+ defaultConfiguration->playSoundOnIncomingDirectMessages = true;
+ defaultConfiguration->showNotificationOnIncomingDirectMessages = true;
+ defaultConfiguration->ownMentionAction.setFrontColor(std::string("black"));
+ defaultConfiguration->ownMentionAction.setBackColor(std::string("yellow"));
+ defaultConfiguration->ownMentionAction.setSoundFilePath(std::string(""));
+ defaultConfiguration->ownMentionAction.setSystemNotificationEnabled(true);
+
+ auto testling = ChatMessageParser(emoticons_, defaultConfiguration, ChatMessageParser::Mode::Chat);
+ auto result = testling.parseMessageBody("I wonder when Juliet is coming?", "Romeo");
+
+ ASSERT_EQ(std::string(""), result.getHighlightActionDirectMessage().getSoundFilePath().get_value_or(std::string("somethingElse")));
+ ASSERT_EQ(true, result.getHighlightActionDirectMessage().isSystemNotificationEnabled());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testWithDefaultConfiguration) {
+ DummySettingsProvider settings;
+ HighlightManager manager(&settings);
+ manager.resetToDefaultConfiguration();
+ auto testling = ChatMessageParser(emoticons_, manager.getConfiguration(), ChatMessageParser::Mode::GroupChat);
+ testling.setNick("Juliet");
+ auto result = testling.parseMessageBody("Hello, how is it going?", "Romeo");
+ assertText(result, 0, "Hello, how is it going?");
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionSender());
+ result = testling.parseMessageBody("Juliet, seen the new interface design?", "Romeo");
+ auto mentionKeywordAction = manager.getConfiguration()->ownMentionAction;
+ mentionKeywordAction.setSoundFilePath(boost::optional<std::string>());
+ mentionKeywordAction.setSystemNotificationEnabled(false);
+ assertHighlight(result, 0, "Juliet", mentionKeywordAction);
+ assertText(result, 1, ", seen the new interface design?");
+ ASSERT_EQ(manager.getConfiguration()->ownMentionAction, result.getHighlightActionOwnMention());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionSender());
+}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index f5a3003..954dd2f 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -1,495 +1,1904 @@
/*
- * Copyright (c) 2010-2012 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
+#include <map>
+#include <set>
+
+#include <boost/bind.hpp>
+
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <hippomocks.h>
-#include <boost/bind.hpp>
+#include <Swiften/Avatars/AvatarMemoryStorage.h>
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Base/Log.h>
+#include <Swiften/Base/LogSerializers.h>
+#include <Swiften/Client/Client.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Crypto/CryptoProvider.h>
+#include <Swiften/Crypto/PlatformCryptoProvider.h>
+#include <Swiften/Disco/DummyEntityCapsProvider.h>
+#include <Swiften/Elements/CarbonsReceived.h>
+#include <Swiften/Elements/CarbonsSent.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/Elements/Forwarded.h>
+#include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/Elements/PrivateStorage.h>
+#include <Swiften/Elements/Storage.h>
+#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
+#include <Swiften/Jingle/JingleSessionManager.h>
+#include <Swiften/MUC/MUCManager.h>
+#include <Swiften/MUC/UnitTest/MockMUC.h>
+#include <Swiften/MUC/MUCBookmarkManager.h>
+#include <Swiften/Network/DummyTimerFactory.h>
+#include <Swiften/Presence/DirectedPresenceSender.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Presence/StanzaChannelPresenceSender.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Roster/XMPPRosterImpl.h>
+#include <Swiften/VCards/VCardManager.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+#include <Swift/Controllers/Chat/ChatController.h>
#include <Swift/Controllers/Chat/ChatsManager.h>
-
+#include <Swift/Controllers/Chat/Chattables.h>
+#include <Swift/Controllers/Chat/MUCController.h>
#include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/EventNotifier.h>
+#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
+#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/Settings/DummySettingsProvider.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
-#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
-#include <Swiften/Client/Client.h>
-#include <Swiften/Disco/EntityCapsManager.h>
-#include <Swiften/Disco/CapsProvider.h>
-#include <Swiften/MUC/MUCManager.h>
-#include <Swift/Controllers/Chat/ChatController.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
-#include <Swift/Controllers/Chat/MUCController.h>
-#include <Swiften/Presence/StanzaChannelPresenceSender.h>
-#include <Swiften/Avatars/NullAvatarManager.h>
-#include <Swiften/Avatars/AvatarMemoryStorage.h>
-#include <Swiften/VCards/VCardManager.h>
-#include <Swiften/VCards/VCardMemoryStorage.h>
-#include <Swiften/Client/NickResolver.h>
-#include <Swiften/Presence/DirectedPresenceSender.h>
-#include <Swiften/Roster/XMPPRosterImpl.h>
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
#include <Swift/Controllers/UnitTest/MockChatWindow.h>
-#include <Swiften/Client/DummyStanzaChannel.h>
-#include <Swiften/Queries/DummyIQChannel.h>
-#include <Swiften/Presence/PresenceOracle.h>
-#include <Swiften/Jingle/JingleSessionManager.h>
-#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
-#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
-#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
-#include <Swift/Controllers/UIEvents/UIEventStream.h>
-#include <Swift/Controllers/ProfileSettingsProvider.h>
-#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
-#include <Swiften/Elements/DeliveryReceiptRequest.h>
-#include <Swiften/Elements/DeliveryReceipt.h>
-#include <Swiften/Base/Algorithm.h>
-#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/WhiteboardManager.h>
-#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
-#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+
+#include <SwifTools/Notifier/Notifier.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
using namespace Swift;
-class DummyCapsProvider : public CapsProvider {
- DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());}
+class DummyNotifier : public Notifier {
+ public:
+ virtual void showMessage(
+ Type type,
+ const std::string& subject,
+ const std::string& description,
+ const boost::filesystem::path& picture,
+ boost::function<void()> callback) {
+ notifications.push_back({type, subject, description, picture, callback});
+ }
+
+ /** Remove any pending callbacks. */
+ virtual void purgeCallbacks() {
+
+ }
+
+ public:
+ struct Notification {
+ Type type;
+ std::string subject;
+ std::string description;
+ boost::filesystem::path picture;
+ boost::function<void()> callback;
+ };
+
+ public:
+ std::vector<Notification> notifications;
+};
+
+class ExtendedChatsManager : public ChatsManager {
+public:
+ ExtendedChatsManager(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, VCardManager* vcardManager, Chattables& chattables) :
+ ChatsManager(jid, stanzaChannel, iqRouter, eventController, chatWindowFactory, joinMUCWindowFactory, nickResolver, presenceOracle, presenceSender, uiEventStream, chatListWindowFactory, useDelayForLatency, timerFactory, mucRegistry, entityCapsProvider, mucManager, mucSearchWindowFactory, profileSettings, ftOverview, roster, eagleMode, settings, historyController_, whiteboardManager, highlightManager, clientBlockListManager, emoticons, vcardManager, chattables) {
+ }
+ MUCBookmarkManager* getBookmarkManager() {
+ return mucBookmarkManager_;
+ }
};
+
class ChatsManagerTest : public CppUnit::TestFixture {
- CPPUNIT_TEST_SUITE(ChatsManagerTest);
- CPPUNIT_TEST(testFirstOpenWindowIncoming);
- CPPUNIT_TEST(testSecondOpenWindowIncoming);
- CPPUNIT_TEST(testFirstOpenWindowOutgoing);
- CPPUNIT_TEST(testFirstOpenWindowBareToFull);
- CPPUNIT_TEST(testSecondWindow);
- CPPUNIT_TEST(testUnbindRebind);
- CPPUNIT_TEST(testNoDuplicateUnbind);
- CPPUNIT_TEST(testThreeMUCWindows);
- CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster);
- CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster);
- CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth);
- CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom);
- CPPUNIT_TEST_SUITE_END();
-
+ CPPUNIT_TEST_SUITE(ChatsManagerTest);
+ CPPUNIT_TEST(testFirstOpenWindowIncoming);
+ CPPUNIT_TEST(testSecondOpenWindowIncoming);
+ CPPUNIT_TEST(testFirstOpenWindowOutgoing);
+ CPPUNIT_TEST(testFirstOpenWindowBareToFull);
+ CPPUNIT_TEST(testSecondWindow);
+ CPPUNIT_TEST(testUnbindRebind);
+ CPPUNIT_TEST(testNoDuplicateUnbind);
+ CPPUNIT_TEST(testThreeMUCWindows);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom);
+ CPPUNIT_TEST(testChatControllerFullJIDBindingOnMessageAndNotReceipt);
+ CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive);
+ CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect);
+ CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite);
+ CPPUNIT_TEST(testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications);
+
+ // MUC PM Tests
+ CPPUNIT_TEST(testChatControllerPMPresenceHandling);
+ CPPUNIT_TEST(testChatControllerMucPmUnavailableErrorHandling);
+
+ // Highlighting tests
+ CPPUNIT_TEST(testChatControllerHighlightingNotificationTesting);
+ CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds);
+ CPPUNIT_TEST(testChatControllerHighlightingNotificationKeyword);
+
+ CPPUNIT_TEST(testChatControllerMeMessageHandling);
+ CPPUNIT_TEST(testRestartingMUCComponentCrash);
+ CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC);
+
+ // Carbons tests
+ CPPUNIT_TEST(testCarbonsForwardedIncomingMessageToSecondResource);
+ CPPUNIT_TEST(testCarbonsForwardedOutgoingMessageFromSecondResource);
+ CPPUNIT_TEST(testCarbonsForwardedIncomingDuplicates);
+
+ // Message correction tests
+ CPPUNIT_TEST(testChatControllerMessageCorrectionCorrectReplaceID);
+ CPPUNIT_TEST(testChatControllerMessageCorrectionIncorrectReplaceID);
+ CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceBySameResource);
+ CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceByOtherResource);
+ CPPUNIT_TEST(testMUCControllerMessageCorrectionNoIDMatchRequired);
+
+ // Chat window title tests
+ CPPUNIT_TEST(testImpromptuChatTitle);
+ CPPUNIT_TEST(testImpromptuChatWindowTitle);
+ CPPUNIT_TEST(testStandardMUCChatWindowTitle);
+
+ // Bookmark tests
+ CPPUNIT_TEST(testReceivingBookmarksWithDomainJID);
+ CPPUNIT_TEST(testReceivingBookmarksWithBareJID);
+ CPPUNIT_TEST(testReceivingBookmarksWithFullJID);
+ CPPUNIT_TEST(testAutoJoinBookmarksAndChattables);
+ CPPUNIT_TEST(testJoinNoAutojoinBookmark);
+ CPPUNIT_TEST(testJoinAndBookmarkMUC);
+ CPPUNIT_TEST(testReceivingNoBookmarks);
+ CPPUNIT_TEST(testReceivingNullBookmarks);
+ CPPUNIT_TEST(testReceivingBookmarksError);
+
+ CPPUNIT_TEST_SUITE_END();
+
public:
- void setUp() {
- mocks_ = new MockRepository();
- jid_ = JID("test@test.com/resource");
- stanzaChannel_ = new DummyStanzaChannel();
- iqChannel_ = new DummyIQChannel();
- iqRouter_ = new IQRouter(iqChannel_);
- capsProvider_ = new DummyCapsProvider();
- eventController_ = new EventController();
- chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
- joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>();
- xmppRoster_ = new XMPPRosterImpl();
- mucRegistry_ = new MUCRegistry();
- nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, NULL, mucRegistry_);
- presenceOracle_ = new PresenceOracle(stanzaChannel_);
- serverDiscoInfo_ = boost::make_shared<DiscoInfo>();
- presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
- directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
- mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_);
- uiEventStream_ = new UIEventStream();
- entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_);
- chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
- mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
- settings_ = new DummySettingsProvider();
- profileSettings_ = new ProfileSettingsProvider("a", settings_);
- chatListWindow_ = new MockChatListWindow();
- ftManager_ = new DummyFileTransferManager();
- ftOverview_ = new FileTransferOverview(ftManager_);
- avatarManager_ = new NullAvatarManager();
- wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_);
- wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_);
- highlightManager_ = new HighlightManager(settings_);
-
- mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
- clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
- manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL);
-
- manager_->setAvatarManager(avatarManager_);
- }
-
- void tearDown() {
- delete highlightManager_;
- //delete chatListWindowFactory
- delete profileSettings_;
- delete avatarManager_;
- delete manager_;
- delete clientBlockListManager_;
- delete ftOverview_;
- delete ftManager_;
- delete wbSessionManager_;
- delete wbManager_;
- delete directedPresenceSender_;
- delete presenceSender_;
- delete presenceOracle_;
- delete nickResolver_;
- delete mucRegistry_;
- delete stanzaChannel_;
- delete eventController_;
- delete iqRouter_;
- delete iqChannel_;
- delete uiEventStream_;
- delete mucManager_;
- delete xmppRoster_;
- delete entityCapsManager_;
- delete capsProvider_;
- delete chatListWindow_;
- delete mocks_;
- delete settings_;
- }
-
- void testFirstOpenWindowIncoming() {
- JID messageJID("testling@test.com/resource1");
-
- MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
-
- boost::shared_ptr<Message> message(new Message());
- message->setFrom(messageJID);
- std::string body("This is a legible message. >HEH@)oeueu");
- message->setBody(body);
- manager_->handleIncomingMessage(message);
- CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_);
- }
-
- void testSecondOpenWindowIncoming() {
- JID messageJID1("testling@test.com/resource1");
-
- MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1);
-
- boost::shared_ptr<Message> message1(new Message());
- message1->setFrom(messageJID1);
- std::string body1("This is a legible message. >HEH@)oeueu");
- message1->setBody(body1);
- manager_->handleIncomingMessage(message1);
- CPPUNIT_ASSERT_EQUAL(body1, window1->lastMessageBody_);
-
- JID messageJID2("testling@test.com/resource2");
-
- //MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- //mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2, uiEventStream_).Return(window2);
-
- boost::shared_ptr<Message> message2(new Message());
- message2->setFrom(messageJID2);
- std::string body2("This is a legible message. .cmaulm.chul");
- message2->setBody(body2);
- manager_->handleIncomingMessage(message2);
- CPPUNIT_ASSERT_EQUAL(body2, window1->lastMessageBody_);
- }
-
- void testFirstOpenWindowOutgoing() {
- std::string messageJIDString("testling@test.com");
-
- ChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString), uiEventStream_).Return(window);
-
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString))));
- }
-
-
- void testFirstOpenWindowBareToFull() {
- std::string bareJIDString("testling@test.com");
- std::string fullJIDString("testling@test.com/resource1");
-
- MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window);
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(bareJIDString))));
-
- boost::shared_ptr<Message> message(new Message());
- message->setFrom(JID(fullJIDString));
- std::string body("This is a legible message. mjuga3089gm8G(*>M)@*(");
- message->setBody(body);
- manager_->handleIncomingMessage(message);
- CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_);
- }
-
- void testSecondWindow() {
- std::string messageJIDString1("testling1@test.com");
- ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1);
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1))));
-
- std::string messageJIDString2("testling2@test.com");
- ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2);
-
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString2))));
- }
-
- /** Complete cycle.
- Create unbound window.
- Bind it.
- Unbind it.
- Rebind it.
- */
- void testUnbindRebind() {
- std::string bareJIDString("testling@test.com");
- std::string fullJIDString1("testling@test.com/resource1");
- std::string fullJIDString2("testling@test.com/resource2");
-
- MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window);
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(bareJIDString))));
-
- boost::shared_ptr<Message> message1(new Message());
- message1->setFrom(JID(fullJIDString1));
- std::string messageBody1("This is a legible message.");
- message1->setBody(messageBody1);
- manager_->handleIncomingMessage(message1);
- CPPUNIT_ASSERT_EQUAL(messageBody1, window->lastMessageBody_);
-
- boost::shared_ptr<Presence> jid1Online(new Presence());
- jid1Online->setFrom(JID(fullJIDString1));
- boost::shared_ptr<Presence> jid1Offline(new Presence());
- jid1Offline->setFrom(JID(fullJIDString1));
- jid1Offline->setType(Presence::Unavailable);
- presenceOracle_->onPresenceChange(jid1Offline);
-
- boost::shared_ptr<Message> message2(new Message());
- message2->setFrom(JID(fullJIDString2));
- std::string messageBody2("This is another legible message.");
- message2->setBody(messageBody2);
- manager_->handleIncomingMessage(message2);
- CPPUNIT_ASSERT_EQUAL(messageBody2, window->lastMessageBody_);
- }
-
- /**
- * Test that MUC PMs get opened in the right windows
- */
- void testThreeMUCWindows() {
- JID muc("testling@test.com");
- ChatWindow* mucWindow = new MockChatWindow();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc, uiEventStream_).Return(mucWindow);
- uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(muc, std::string("nick")));
-
-
- std::string messageJIDString1("testling@test.com/1");
- ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1);
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1))));
-
- std::string messageJIDString2("testling@test.com/2");
- ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2);
-
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString2))));
-
- std::string messageJIDString3("testling@test.com/3");
- ChatWindow* window3 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString3), uiEventStream_).Return(window3);
-
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString3))));
-
- /* Refetch an earlier window */
- /* We do not expect a new window to be created */
- uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1))));
-
- }
-
- /**
- Test that a second window isn't unbound where there's already an unbound one.
- Bind 1
- Bind 2
- Unbind 1
- Unbind 2 (but it doesn't)
- Sent to bound 2
- Rebind 1
- */
- void testNoDuplicateUnbind() {
- JID messageJID1("testling@test.com/resource1");
-
- MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1);
-
- boost::shared_ptr<Message> message1(new Message());
- message1->setFrom(messageJID1);
- message1->setBody("This is a legible message1.");
- manager_->handleIncomingMessage(message1);
-
- JID messageJID2("testling@test.com/resource2");
-
- //MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- //mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2, uiEventStream_).Return(window2);
-
- boost::shared_ptr<Message> message2(new Message());
- message2->setFrom(messageJID2);
- message2->setBody("This is a legible message2.");
- manager_->handleIncomingMessage(message2);
-
- boost::shared_ptr<Presence> jid1Online(new Presence());
- jid1Online->setFrom(JID(messageJID1));
- boost::shared_ptr<Presence> jid1Offline(new Presence());
- jid1Offline->setFrom(JID(messageJID1));
- jid1Offline->setType(Presence::Unavailable);
- presenceOracle_->onPresenceChange(jid1Offline);
-
- boost::shared_ptr<Presence> jid2Online(new Presence());
- jid2Online->setFrom(JID(messageJID2));
- boost::shared_ptr<Presence> jid2Offline(new Presence());
- jid2Offline->setFrom(JID(messageJID2));
- jid2Offline->setType(Presence::Unavailable);
- presenceOracle_->onPresenceChange(jid2Offline);
-
- JID messageJID3("testling@test.com/resource3");
-
- boost::shared_ptr<Message> message3(new Message());
- message3->setFrom(messageJID3);
- std::string body3("This is a legible message3.");
- message3->setBody(body3);
- manager_->handleIncomingMessage(message3);
- CPPUNIT_ASSERT_EQUAL(body3, window1->lastMessageBody_);
-
- boost::shared_ptr<Message> message2b(new Message());
- message2b->setFrom(messageJID2);
- std::string body2b("This is a legible message2b.");
- message2b->setBody(body2b);
- manager_->handleIncomingMessage(message2b);
- CPPUNIT_ASSERT_EQUAL(body2b, window1->lastMessageBody_);
- }
-
- /**
- * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster.
- */
- void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() {
- JID messageJID("testling@test.com/resource1");
- xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
-
- MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
- settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
-
- boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
- manager_->handleIncomingMessage(message);
- Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
- CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size());
- CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
-
- xmppRoster_->removeContact(messageJID);
-
- message->setID("2");
- manager_->handleIncomingMessage(message);
- CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size());
- }
-
- /**
- * Test that ChatController sends receipts after the contact has been added to the roster.
- */
- void testChatControllerPresenceAccessUpdatedOnAddToRoster() {
- JID messageJID("testling@test.com/resource1");
-
- MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
- settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
-
- boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
- manager_->handleIncomingMessage(message);
-
- CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->sentStanzas.size());
-
- xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
- message->setID("2");
- manager_->handleIncomingMessage(message);
-
- CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size());
- Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
- CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
- }
-
- /**
- * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both.
- */
- void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() {
- testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both);
- }
-
- /**
- * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From.
- */
- void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() {
- testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From);
- }
-
- void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) {
- JID messageJID("testling@test.com/resource1");
- xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from);
-
- MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
- settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
-
- boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
- manager_->handleIncomingMessage(message);
-
- CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->sentStanzas.size());
-
- xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to);
- message->setID("2");
- manager_->handleIncomingMessage(message);
-
- CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size());
- Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
- CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
- }
+ void setUp() {
+ mocks_ = new MockRepository();
+ notifier_ = std::make_unique<DummyNotifier>();
+ jid_ = JID("test@test.com/resource");
+ stanzaChannel_ = new DummyStanzaChannel();
+ iqRouter_ = new IQRouter(stanzaChannel_);
+ eventController_ = new EventController();
+ chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
+ joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>();
+ xmppRoster_ = new XMPPRosterImpl();
+ mucRegistry_ = new MUCRegistry();
+ nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_);
+ presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
+ serverDiscoInfo_ = std::make_shared<DiscoInfo>();
+ presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
+ directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
+ mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_);
+ uiEventStream_ = new UIEventStream();
+ entityCapsProvider_ = new DummyEntityCapsProvider();
+ chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
+ mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
+ settings_ = new DummySettingsProvider();
+ profileSettings_ = new ProfileSettingsProvider("a", settings_);
+ chatListWindow_ = new MockChatListWindow();
+ ftManager_ = new DummyFileTransferManager();
+ ftOverview_ = new FileTransferOverview(ftManager_);
+ avatarManager_ = new NullAvatarManager();
+ eventNotifier_ = new EventNotifier(eventController_, notifier_.get(), avatarManager_, nickResolver_);
+ wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_);
+ wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_);
+ highlightManager_ = new HighlightManager(settings_);
+ highlightManager_->resetToDefaultConfiguration();
+ handledHighlightActions_ = 0;
+ soundsPlayed_.clear();
+ highlightManager_->onHighlight.connect(boost::bind(&ChatsManagerTest::handleHighlightAction, this, _1));
+
+ crypto_ = PlatformCryptoProvider::create();
+ vcardStorage_ = new VCardMemoryStorage(crypto_);
+ vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_);
+ mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
+ clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+ timerFactory_ = new DummyTimerFactory();
+ chattables_ = std::make_unique<Chattables>();
+ manager_ = new ExtendedChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, timerFactory_, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, nullptr, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_, *chattables_);
+
+ manager_->setAvatarManager(avatarManager_);
+ }
+
+ void tearDown() {
+ delete highlightManager_;
+ delete profileSettings_;
+ delete eventNotifier_;
+ delete avatarManager_;
+ delete manager_;
+ delete timerFactory_;
+ delete clientBlockListManager_;
+ delete vcardManager_;
+ delete vcardStorage_;
+ delete crypto_;
+ delete ftOverview_;
+ delete ftManager_;
+ delete wbSessionManager_;
+ delete wbManager_;
+ delete directedPresenceSender_;
+ delete presenceSender_;
+ delete presenceOracle_;
+ delete nickResolver_;
+ delete mucRegistry_;
+ delete iqRouter_;
+ delete stanzaChannel_;
+ delete eventController_;
+ delete uiEventStream_;
+ delete mucManager_;
+ delete xmppRoster_;
+ delete entityCapsProvider_;
+ delete chatListWindow_;
+ delete mocks_;
+ delete settings_;
+ }
+
+ void testFirstOpenWindowIncoming() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This is a legible message. >HEH@)oeueu");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ }
+
+ void testSecondOpenWindowIncoming() {
+ JID messageJID1("testling@test.com/resource1");
+
+ MockChatWindow* window1 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1);
+
+ std::shared_ptr<Message> message1(new Message());
+ message1->setFrom(messageJID1);
+ std::string body1("This is a legible message. >HEH@)oeueu");
+ message1->setBody(body1);
+ manager_->handleIncomingMessage(message1);
+ CPPUNIT_ASSERT_EQUAL(body1, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_));
+
+ JID messageJID2("testling@test.com/resource2");
+
+ std::shared_ptr<Message> message2(new Message());
+ message2->setFrom(messageJID2);
+ std::string body2("This is a legible message. .cmaulm.chul");
+ message2->setBody(body2);
+ manager_->handleIncomingMessage(message2);
+ CPPUNIT_ASSERT_EQUAL(body2, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_));
+ }
+
+ void testFirstOpenWindowOutgoing() {
+ std::string messageJIDString("testling@test.com");
+
+ ChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString), uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString)));
+ }
+
+
+ void testFirstOpenWindowBareToFull() {
+ std::string bareJIDString("testling@test.com");
+ std::string fullJIDString("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window);
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(bareJIDString)));
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(JID(fullJIDString));
+ std::string body("This is a legible message. mjuga3089gm8G(*>M)@*(");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ }
+
+ void testSecondWindow() {
+ std::string messageJIDString1("testling1@test.com");
+ ChatWindow* window1 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1);
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1)));
+
+ std::string messageJIDString2("testling2@test.com");
+ ChatWindow* window2 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString2)));
+ }
+
+ /** Complete cycle.
+ Create unbound window.
+ Bind it.
+ Unbind it.
+ Rebind it.
+ */
+ void testUnbindRebind() {
+ std::string bareJIDString("testling@test.com");
+ std::string fullJIDString1("testling@test.com/resource1");
+ std::string fullJIDString2("testling@test.com/resource2");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window);
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(bareJIDString)));
+
+ std::shared_ptr<Message> message1(new Message());
+ message1->setFrom(JID(fullJIDString1));
+ std::string messageBody1("This is a legible message.");
+ message1->setBody(messageBody1);
+ manager_->handleIncomingMessage(message1);
+ CPPUNIT_ASSERT_EQUAL(messageBody1, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ std::shared_ptr<Presence> jid1Online(new Presence());
+ jid1Online->setFrom(JID(fullJIDString1));
+ std::shared_ptr<Presence> jid1Offline(new Presence());
+ jid1Offline->setFrom(JID(fullJIDString1));
+ jid1Offline->setType(Presence::Unavailable);
+ presenceOracle_->onPresenceChange(jid1Offline);
+
+ std::shared_ptr<Message> message2(new Message());
+ message2->setFrom(JID(fullJIDString2));
+ std::string messageBody2("This is another legible message.");
+ message2->setBody(messageBody2);
+ manager_->handleIncomingMessage(message2);
+ CPPUNIT_ASSERT_EQUAL(messageBody2, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ }
+
+ /**
+ * Test that MUC PMs get opened in the right windows
+ */
+ void testThreeMUCWindows() {
+ JID muc("testling@test.com");
+ ChatWindow* mucWindow = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc, uiEventStream_).Return(mucWindow);
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(muc, std::string("nick")));
+
+
+ std::string messageJIDString1("testling@test.com/1");
+ ChatWindow* window1 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1);
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1)));
+
+ std::string messageJIDString2("testling@test.com/2");
+ ChatWindow* window2 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString2)));
+
+ std::string messageJIDString3("testling@test.com/3");
+ ChatWindow* window3 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString3), uiEventStream_).Return(window3);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString3)));
+
+ /* Refetch an earlier window */
+ /* We do not expect a new window to be created */
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1)));
+
+ }
+
+ /**
+ Test that a second window isn't unbound where there's already an unbound one.
+ Bind 1
+ Bind 2
+ Unbind 1
+ Unbind 2 (but it doesn't)
+ Sent to bound 2
+ Rebind 1
+ */
+ void testNoDuplicateUnbind() {
+ JID messageJID1("testling@test.com/resource1");
+
+ MockChatWindow* window1 = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1);
+
+ std::shared_ptr<Message> message1(new Message());
+ message1->setFrom(messageJID1);
+ message1->setBody("This is a legible message1.");
+ manager_->handleIncomingMessage(message1);
+
+ JID messageJID2("testling@test.com/resource2");
+
+ std::shared_ptr<Message> message2(new Message());
+ message2->setFrom(messageJID2);
+ message2->setBody("This is a legible message2.");
+ manager_->handleIncomingMessage(message2);
+
+ std::shared_ptr<Presence> jid1Online(new Presence());
+ jid1Online->setFrom(JID(messageJID1));
+ std::shared_ptr<Presence> jid1Offline(new Presence());
+ jid1Offline->setFrom(JID(messageJID1));
+ jid1Offline->setType(Presence::Unavailable);
+ presenceOracle_->onPresenceChange(jid1Offline);
+
+ std::shared_ptr<Presence> jid2Online(new Presence());
+ jid2Online->setFrom(JID(messageJID2));
+ std::shared_ptr<Presence> jid2Offline(new Presence());
+ jid2Offline->setFrom(JID(messageJID2));
+ jid2Offline->setType(Presence::Unavailable);
+ presenceOracle_->onPresenceChange(jid2Offline);
+
+ JID messageJID3("testling@test.com/resource3");
+
+ std::shared_ptr<Message> message3(new Message());
+ message3->setFrom(messageJID3);
+ std::string body3("This is a legible message3.");
+ message3->setBody(body3);
+ manager_->handleIncomingMessage(message3);
+ CPPUNIT_ASSERT_EQUAL(body3, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_));
+
+ std::shared_ptr<Message> message2b(new Message());
+ message2b->setFrom(messageJID2);
+ std::string body2b("This is a legible message2b.");
+ message2b->setBody(body2b);
+ manager_->handleIncomingMessage(message2b);
+ CPPUNIT_ASSERT_EQUAL(body2b, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_));
+ }
+
+ /**
+ * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster.
+ */
+ void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() {
+ JID messageJID("testling@test.com/resource1");
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+ settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
+
+ std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+ manager_->handleIncomingMessage(message);
+ Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1);
+ CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>());
+ CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr);
+
+ xmppRoster_->removeContact(messageJID);
+
+ message->setID("2");
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>());
+ }
+
+ /**
+ * Test that ChatController sends receipts after the contact has been added to the roster.
+ */
+ void testChatControllerPresenceAccessUpdatedOnAddToRoster() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+ settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
+
+ std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>());
+
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
+ message->setID("2");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>());
+ Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1);
+ CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr);
+ }
+
+ /**
+ * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both.
+ */
+ void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() {
+ testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both);
+ }
+
+ /**
+ * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From.
+ */
+ void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() {
+ testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From);
+ }
+
+ void testChatControllerFullJIDBindingOnMessageAndNotReceipt() {
+ JID ownJID("test@test.com/resource");
+ JID sender("foo@test.com");
+ std::vector<JID> senderResource;
+ senderResource.push_back(sender.withResource("resourceA"));
+ senderResource.push_back(sender.withResource("resourceB"));
+
+ // We support delivery receipts.
+ settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
+
+ // Open chat window to a sender.
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender));
+
+ for (const auto& senderJID : senderResource) {
+ // The sender supports delivery receipts.
+ DiscoInfo::ref disco = std::make_shared<DiscoInfo>();
+ disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
+ entityCapsProvider_->caps[senderJID] = disco;
+
+ // The sender is online.
+ Presence::ref senderPresence = std::make_shared<Presence>();
+ senderPresence->setFrom(senderJID);
+ senderPresence->setTo(ownJID);
+ stanzaChannel_->onPresenceReceived(senderPresence);
+
+ entityCapsProvider_->onCapsChanged(senderJID);
+ }
+
+ // Send first message.
+ window->onSendMessageRequest("hello there", false);
+
+ // A bare message is send because no resources is bound.
+ CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>());
+
+ // Two resources respond with message receipts.
+ for (const auto& senderJID : senderResource) {
+ Message::ref receiptReply = std::make_shared<Message>();
+ receiptReply->setFrom(senderJID);
+ receiptReply->setTo(ownJID);
+
+ std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>();
+ receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID());
+ receiptReply->addPayload(receipt);
+ manager_->handleIncomingMessage(receiptReply);
+ }
+
+ // Send second message.
+ window->onSendMessageRequest("how are you?", false);
+
+ // A bare message is send because no resources is bound.
+ CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>());
+
+ // Two resources respond with message receipts.
+ for (const auto& senderJID : senderResource) {
+ Message::ref receiptReply = std::make_shared<Message>();
+ receiptReply->setFrom(senderJID);
+ receiptReply->setTo(ownJID);
+
+ std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>();
+ receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID());
+ receiptReply->addPayload(receipt);
+ manager_->handleIncomingMessage(receiptReply);
+ }
+
+ // Reply with a message including a body text.
+ Message::ref reply = std::make_shared<Message>();
+ reply->setFrom(senderResource[0]);
+ reply->setTo(ownJID);
+ reply->setBody("fine.");
+ manager_->handleIncomingMessage(reply);
+
+ // Send third message.
+ window->onSendMessageRequest("great to hear.", false);
+
+ // The chat session is bound to the full JID of the first resource.
+ CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(2)->getPayload<DeliveryReceiptRequest>());
+
+ // Receive random receipt from second sender resource.
+ reply = std::make_shared<Message>();
+ reply->setFrom(senderResource[1]);
+ reply->setTo(ownJID);
+
+ std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>();
+ receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(2)->getID());
+ reply->addPayload(receipt);
+ manager_->handleIncomingMessage(reply);
+
+ // Send forth message.
+ window->onSendMessageRequest("what else is new?", false);
+
+ // The chat session is bound to the full JID of the first resource.
+ CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>());
+
+ // Reply with a message including a body text from second resource.
+ reply = std::make_shared<Message>();
+ reply->setFrom(senderResource[1]);
+ reply->setTo(ownJID);
+ reply->setBody("nothing.");
+ manager_->handleIncomingMessage(reply);
+
+ // Send fifth message.
+ window->onSendMessageRequest("okay", false);
+
+ // The chat session is now bound to the full JID of the second resource.
+ CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(5)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(5)->getPayload<DeliveryReceiptRequest>());
+ }
+
+ void testChatControllerFullJIDBindingOnTypingAndNotActive() {
+ JID ownJID("test@test.com/resource");
+ JID sender("foo@test.com");
+ std::vector<JID> senderResource;
+ senderResource.push_back(sender.withResource("resourceA"));
+ senderResource.push_back(sender.withResource("resourceB"));
+
+ // We support delivery receipts.
+ settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
+
+ // Open chat window to a sender.
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender));
+
+ for (const auto& senderJID : senderResource) {
+ // The sender supports delivery receipts.
+ DiscoInfo::ref disco = std::make_shared<DiscoInfo>();
+ disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
+ entityCapsProvider_->caps[senderJID] = disco;
+
+ // The sender is online.
+ Presence::ref senderPresence = std::make_shared<Presence>();
+ senderPresence->setFrom(senderJID);
+ senderPresence->setTo(ownJID);
+ stanzaChannel_->onPresenceReceived(senderPresence);
+
+ entityCapsProvider_->onCapsChanged(senderJID);
+ }
+
+ // Send first message.
+ window->onSendMessageRequest("hello there", false);
+
+ // A bare message is send because no resources is bound.
+ CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>());
+
+ // Two resources respond with message receipts.
+ for (const auto& senderJID : senderResource) {
+ Message::ref reply = std::make_shared<Message>();
+ reply->setFrom(senderJID);
+ reply->setTo(ownJID);
+
+ std::shared_ptr<ChatState> csn = std::make_shared<ChatState>();
+ csn->setChatState(ChatState::Active);
+ reply->addPayload(csn);
+ manager_->handleIncomingMessage(reply);
+ }
+
+ // Send second message.
+ window->onSendMessageRequest("how are you?", false);
+
+ // A bare message is send because no resources is bound.
+ CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>());
+
+ // Two resources respond with message receipts.
+ for (const auto& senderJID : senderResource) {
+ Message::ref receiptReply = std::make_shared<Message>();
+ receiptReply->setFrom(senderJID);
+ receiptReply->setTo(ownJID);
+
+ std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>();
+ receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID());
+ receiptReply->addPayload(receipt);
+ manager_->handleIncomingMessage(receiptReply);
+ }
+
+ // Reply with a message including a CSN.
+ Message::ref reply = std::make_shared<Message>();
+ reply->setFrom(senderResource[0]);
+ reply->setTo(ownJID);
+
+ std::shared_ptr<ChatState> csn = std::make_shared<ChatState>();
+ csn->setChatState(ChatState::Composing);
+ reply->addPayload(csn);
+ manager_->handleIncomingMessage(reply);
+
+ // Send third message.
+ window->onSendMessageRequest("great to hear.", false);
+
+ // The chat session is now bound to the full JID of the first resource due to its recent composing message.
+ CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>());
+
+ // Reply with a message including a CSN from the other resource.
+ reply = std::make_shared<Message>();
+ reply->setFrom(senderResource[1]);
+ reply->setTo(ownJID);
+
+ csn = std::make_shared<ChatState>();
+ csn->setChatState(ChatState::Composing);
+ reply->addPayload(csn);
+ manager_->handleIncomingMessage(reply);
+
+ // Send third message.
+ window->onSendMessageRequest("ping.", false);
+
+ // The chat session is now bound to the full JID of the second resource due to its recent composing message.
+ CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(4)->getTo());
+ CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(4)->getPayload<DeliveryReceiptRequest>());
+ }
+
+ void testChatControllerPMPresenceHandling() {
+ JID participantA = JID("test@rooms.test.com/participantA");
+ JID participantB = JID("test@rooms.test.com/participantB");
+
+ mucRegistry_->addMUC("test@rooms.test.com");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(participantA)));
+
+ Presence::ref presence = Presence::create();
+ presence->setFrom(participantA);
+ presence->setShow(StatusShow::Online);
+ stanzaChannel_->onPresenceReceived(presence);
+ CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
+
+ presence = Presence::create();
+ presence->setFrom(participantB);
+ presence->setShow(StatusShow::Away);
+ stanzaChannel_->onPresenceReceived(presence);
+
+ presence = Presence::create();
+ presence->setFrom(participantA);
+ presence->setShow(StatusShow::None);
+ presence->setType(Presence::Unavailable);
+ stanzaChannel_->onPresenceReceived(presence);
+ CPPUNIT_ASSERT_EQUAL(std::string("participantA has gone offline."), MockChatWindow::bodyFromMessage(window->lastReplacedLastMessage_));
+ }
+
+ void testChatControllerMucPmUnavailableErrorHandling() {
+ auto mucJID = JID("test@rooms.test.com");
+ auto participantA = mucJID.withResource("participantA");
+ auto participantB = mucJID.withResource("participantB");
+
+ auto mucWindow = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(mucWindow);
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, participantB.getResource()));
+ CPPUNIT_ASSERT_EQUAL(true, mucWindow->mucType_.is_initialized());
+
+ auto window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window);
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(participantA));
+ CPPUNIT_ASSERT_EQUAL(false, window->mucType_.is_initialized());
+
+ Presence::ref presence = Presence::create();
+ presence->setFrom(participantA);
+ presence->setShow(StatusShow::Online);
+ stanzaChannel_->onPresenceReceived(presence);
+ CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
+
+ // send message to participantA
+ auto messageBody = std::string("message body to send");
+ window->onSendMessageRequest(messageBody, false);
+ auto sendMessageStanza = stanzaChannel_->getStanzaAtIndex<Message>(3);
+ CPPUNIT_ASSERT_EQUAL(messageBody, *sendMessageStanza->getBody());
+
+ // receive reply with error
+ auto messageErrorReply = std::make_shared<Message>();
+ messageErrorReply->setID(stanzaChannel_->getNewIQID());
+ messageErrorReply->setType(Message::Error);
+ messageErrorReply->setFrom(participantA);
+ messageErrorReply->setTo(jid_);
+ messageErrorReply->addPayload(std::make_shared<ErrorPayload>(ErrorPayload::ItemNotFound, ErrorPayload::Cancel, "Recipient not in room"));
+
+ auto lastMUCWindowErrorMessageBeforeError = MockChatWindow::bodyFromMessage(mucWindow->lastAddedErrorMessage_);
+ manager_->handleIncomingMessage(messageErrorReply);
+
+ // assert that error is not routed to MUC window
+ CPPUNIT_ASSERT_EQUAL(lastMUCWindowErrorMessageBeforeError, MockChatWindow::bodyFromMessage(mucWindow->lastAddedErrorMessage_));
+ // assert that error is routed to PM
+ CPPUNIT_ASSERT_EQUAL(std::string("This user could not be found in the room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_));
+ }
+
+ void testLocalMUCServiceDiscoveryResetOnDisconnect() {
+ JID ownJID("test@test.com/resource");
+ JID sender("foo@test.com");
+
+ manager_->setOnline(true);
+
+ // Open chat window to a sender.
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender));
+
+ CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_);
+
+ std::shared_ptr<IQ> infoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]);
+ std::shared_ptr<IQ> infoResponse = IQ::createResult(infoRequest->getFrom(), infoRequest->getTo(), infoRequest->getID());
+
+ DiscoInfo info;
+ info.addIdentity(DiscoInfo::Identity("Shakespearean Chat Service", "conference", "text"));
+ info.addFeature("http://jabber.org/protocol/muc");
+ infoResponse->addPayload(std::make_shared<DiscoInfo>(info));
+ stanzaChannel_->onIQReceived(infoResponse);
+
+ CPPUNIT_ASSERT_EQUAL(true, window->impromptuMUCSupported_);
+ manager_->setOnline(false);
+ CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_);
+ }
+
+ void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) {
+ JID messageJID("testling@test.com/resource1");
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from);
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+ settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
+
+ std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>());
+
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to);
+ message->setID("2");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>());
+ Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1);
+ CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr);
+ }
+
+ void testChatControllerHighlightingNotificationTesting() {
+ HighlightConfiguration::KeywordHightlight keywordRuleA;
+ keywordRuleA.keyword = "Romeo";
+ keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow"));
+ keywordRuleA.action.setSoundFilePath(boost::optional<std::string>(""));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA);
+
+ HighlightConfiguration::KeywordHightlight keywordRuleB;
+ keywordRuleB.keyword = "Juliet";
+ keywordRuleB.action.setFrontColor(boost::optional<std::string>("green"));
+ keywordRuleB.action.setSoundFilePath(boost::optional<std::string>("/tmp/someotherfile.wav"));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB);
+
+ JID messageJID = JID("testling@test.com");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This message should cause two sounds: Juliet and Romeo.");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(2, handledHighlightActions_);
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), notifier_->notifications.size());
+ }
+
+ void testChatControllerHighlightingNotificationDeduplicateSounds() {
+ auto keywordRuleA = HighlightConfiguration::KeywordHightlight();
+ keywordRuleA.keyword = "Romeo";
+ keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow"));
+ keywordRuleA.action.setSoundFilePath(boost::optional<std::string>(""));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA);
+
+ auto keywordRuleB = HighlightConfiguration::KeywordHightlight();
+ keywordRuleB.keyword = "Juliet";
+ keywordRuleB.action.setFrontColor(boost::optional<std::string>("green"));
+ keywordRuleB.action.setSoundFilePath(boost::optional<std::string>(""));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB);
+
+ JID messageJID = JID("testling@test.com");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This message should cause one sound, because both actions have the same sound: Juliet and Romeo.");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(1, handledHighlightActions_);
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), notifier_->notifications.size());
+ }
+
+ void testChatControllerHighlightingNotificationKeyword() {
+ auto keywordRuleA = HighlightConfiguration::KeywordHightlight();
+ keywordRuleA.keyword = "Swift";
+ keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow"));
+ keywordRuleA.action.setSoundFilePath(boost::optional<std::string>(""));
+ keywordRuleA.action.setSystemNotificationEnabled(true);
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA);
+
+ JID messageJID = JID("testling@test.com");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("Let's see if the Swift highlight kicks off a system notification.");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), notifier_->notifications.size());
+ }
+
+ void testChatControllerMeMessageHandling() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("/me is feeling delighted.");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(std::string("is feeling delighted."), window->bodyFromMessage(window->lastAddedAction_));
+ }
+
+ void testRestartingMUCComponentCrash() {
+ JID mucJID = JID("teaparty@rooms.wonderland.lit");
+ JID self = JID("girl@wonderland.lit/rabbithole");
+ std::string nick = "aLiCe";
+
+ MockChatWindow* window;
+
+ auto genRemoteMUCPresence = [=]() {
+ auto presence = Presence::create();
+ presence->setFrom(mucJID.withResource(nick));
+ presence->setTo(self);
+ return presence;
+ };
+
+ // User rejoins.
+ window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
+
+ // Join room
+ {
+ auto joinRoomEvent = std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nick);
+ uiEventStream_->send(joinRoomEvent);
+ }
+
+ {
+ auto firstPresence = genRemoteMUCPresence();
+ firstPresence->setType(Presence::Unavailable);
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addItem(MUCItem(MUCOccupant::Owner, MUCOccupant::NoRole));
+ firstPresence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(firstPresence);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("Couldn't enter room: Unable to enter this room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_));
+
+ {
+ auto presence = genRemoteMUCPresence();
+ presence->setType(Presence::Unavailable);
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addStatusCode(303);
+ auto item = MUCItem(MUCOccupant::Owner, self, MUCOccupant::Moderator);
+ item.nick = nick;
+ userPayload->addItem(item);
+ userPayload->addStatusCode(110);
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+ }
+
+ void testChatControllerMeMessageHandlingInMUC() {
+ JID mucJID("mucroom@rooms.test.com");
+ std::string nickname = "toodles";
+
+ //highlightManager_->resetToDefaultConfiguration();
+
+ // add highlight rule for 'foo'
+ HighlightConfiguration::KeywordHightlight keywordHighlight;
+ keywordHighlight.keyword = "foo";
+ keywordHighlight.action.setBackColor(boost::optional<std::string>("green"));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordHighlight);
+
+ HighlightConfiguration::KeywordHightlight keywordHighlightNotification;
+ keywordHighlightNotification.keyword = "Swift";
+ keywordHighlightNotification.action.setBackColor(boost::optional<std::string>("green"));
+ keywordHighlightNotification.action.setSystemNotificationEnabled(true);
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordHighlightNotification);
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname));
+
+ auto genRemoteMUCPresence = [=]() {
+ auto presence = Presence::create();
+ presence->setFrom(mucJID.withResource(nickname));
+ presence->setTo(jid_);
+ return presence;
+ };
+
+ {
+ auto presence = genRemoteMUCPresence();
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addStatusCode(110);
+ userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator));
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+
+ {
+ auto presence = genRemoteMUCPresence();
+ presence->setFrom(mucJID.withResource("someDifferentNickname"));
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addItem(MUCItem(MUCOccupant::Member, JID("foo@bar.com"), MUCOccupant::Moderator));
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+
+ window->onSendMessageRequest("/me sends a test message with foo", false);
+
+ window->resetLastMessages();
+ {
+ Message::ref mucMirrored = std::make_shared<Message>();
+ mucMirrored->setFrom(mucJID.withResource(nickname));
+ mucMirrored->setTo(jid_);
+ mucMirrored->setType(Message::Groupchat);
+ mucMirrored->setBody("/me sends a test message with foo");
+ manager_->handleIncomingMessage(mucMirrored);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("sends a test message with foo"), window->bodyFromMessage(window->lastAddedAction_));
+
+ window->resetLastMessages();
+ {
+ Message::ref mucMirrored = std::make_shared<Message>();
+ mucMirrored->setFrom(mucJID.withResource("someDifferentNickname"));
+ mucMirrored->setTo(jid_);
+ mucMirrored->setType(Message::Groupchat);
+ mucMirrored->setBody("/me says hello with a test message with foo and foo");
+ manager_->handleIncomingMessage(mucMirrored);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("says hello with a test message with foo and foo"), window->bodyFromMessage(window->lastAddedAction_));
+
+ window->resetLastMessages();
+ {
+ Message::ref keywordMessage = std::make_shared<Message>();
+ keywordMessage->setFrom(mucJID.withResource("someOtherDifferentNickname"));
+ keywordMessage->setTo(jid_);
+ keywordMessage->setType(Message::Groupchat);
+ keywordMessage->setBody("Let's mention Swift and see what happens.");
+ manager_->handleIncomingMessage(keywordMessage);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("Let's mention Swift and see what happens."), window->bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), notifier_->notifications.size());
+ }
+
+ void testPresenceChangeDoesNotReplaceMUCInvite() {
+ JID messageJID("testling@test.com/resource1");
+
+ auto generateIncomingPresence = [=](Presence::Type type) {
+ auto presence = std::make_shared<Presence>();
+ presence->setType(type);
+ presence->setFrom(messageJID);
+ presence->setTo(jid_);
+ return presence;
+ };
+
+ stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available));
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This is a legible message. >HEH@)oeueu");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ auto incomingMUCInvite = std::make_shared<Message>();
+ incomingMUCInvite->setFrom(messageJID);
+
+ auto invitePayload = std::make_shared<MUCInvitationPayload>();
+ invitePayload->setJID("room@muc.service.com");
+ incomingMUCInvite->addPayload(invitePayload);
+
+ stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable));
+ stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available));
+
+ window->resetLastMessages();
+
+ manager_->handleIncomingMessage(incomingMUCInvite);
+ CPPUNIT_ASSERT_EQUAL(JID("room@muc.service.com"), window->lastMUCInvitationJID_);
+
+ stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable));
+ CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedLastMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string("testling@test.com has gone offline."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
+ }
+
+ void testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications() {
+ JID mucJID("mucroom@rooms.test.com");
+ std::string nickname = "toodles";
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname));
+
+ auto genRemoteMUCPresence = [=]() {
+ auto presence = Presence::create();
+ presence->setFrom(mucJID.withResource(nickname));
+ presence->setTo(jid_);
+ return presence;
+ };
+
+ {
+ auto presence = genRemoteMUCPresence();
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addStatusCode(110);
+ userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator));
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+
+ {
+ auto presence = genRemoteMUCPresence();
+ presence->setFrom(mucJID.withResource("someDifferentNickname"));
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname has entered the room."), window->bodyFromMessage(window->lastAddedPresence_));
+ CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastReplacedLastMessage_));
+ window->resetLastMessages();
+
+ {
+ auto presence = genRemoteMUCPresence();
+ presence->setFrom(mucJID.withResource("Romeo"));
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastAddedPresence_));
+ CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname and Romeo have entered the room"), window->bodyFromMessage(window->lastReplacedLastMessage_));
+ window->resetLastMessages();
+
+ {
+ auto message = std::make_shared<Message>();
+ message->setFrom(mucJID.withResource("Romeo"));
+ message->setTo(mucJID);
+ message->setType(Message::Groupchat);
+ message->addPayload(std::make_shared<ChatState>(ChatState::Composing));
+ manager_->handleIncomingMessage(message);
+ }
+
+ {
+ auto presence = genRemoteMUCPresence();
+ presence->setFrom(mucJID.withResource("Juliet"));
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastAddedPresence_));
+ CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname, Romeo and Juliet have entered the room"), window->bodyFromMessage(window->lastReplacedLastMessage_));
+ }
+
+ template <typename CarbonsType>
+ Message::ref createCarbonsMessage(std::shared_ptr<CarbonsType> carbons, std::shared_ptr<Message> forwardedMessage) {
+ auto messageWrapper = std::make_shared<Message>();
+ messageWrapper->setFrom(jid_.toBare());
+ messageWrapper->setTo(jid_);
+ messageWrapper->setType(Message::Chat);
+
+ messageWrapper->addPayload(carbons);
+ auto forwarded = std::make_shared<Forwarded>();
+ carbons->setForwarded(forwarded);
+ forwarded->setStanza(forwardedMessage);
+ return messageWrapper;
+ }
+
+ void testCarbonsForwardedIncomingMessageToSecondResource() {
+ JID messageJID("testling@test.com/resource1");
+ JID jid2 = jid_.toBare().withResource("someOtherResource");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This is a legible message. >HEH@)oeueu");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ // incoming carbons message from another resource
+ {
+ auto originalMessage = std::make_shared<Message>();
+ originalMessage->setFrom(messageJID);
+ originalMessage->setTo(jid2);
+ originalMessage->setType(Message::Chat);
+ std::string forwardedBody = "Some further text.";
+ originalMessage->setBody(forwardedBody);
+
+ auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
+
+ manager_->handleIncomingMessage(messageWrapper);
+
+ CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_);
+
+ auto recentChats = manager_->getRecentChats();
+ CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1));
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, originalMessage->getFrom().toBare());
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some further text."));
+ }
+ }
+
+ void testCarbonsForwardedOutgoingMessageFromSecondResource() {
+ JID messageJID("testling@test.com/resource1");
+ JID jid2 = jid_.toBare().withResource("someOtherResource");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This is a legible message. >HEH@)oeueu");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ // incoming carbons message from another resource
+ {
+ auto originalMessage = std::make_shared<Message>();
+ originalMessage->setFrom(jid2);
+ originalMessage->setTo(messageJID);
+ originalMessage->setType(Message::Chat);
+ originalMessage->setID("abcdefg123456");
+ std::string forwardedBody = "Some text my other resource sent.";
+ originalMessage->setBody(forwardedBody);
+ originalMessage->addPayload(std::make_shared<DeliveryReceiptRequest>());
+
+ auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsSent>(), originalMessage);
+
+ manager_->handleIncomingMessage(messageWrapper);
+
+ CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(true, window->lastAddedMessageSenderIsSelf_);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), window->receiptChanges_.size());
+ CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptRequested, window->receiptChanges_[0].second);
+
+ auto recentChats = manager_->getRecentChats();
+ CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1));
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, originalMessage->getTo().toBare());
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some text my other resource sent."));
+ }
+
+ // incoming carbons message for the received delivery receipt to the other resource
+ {
+ auto originalMessage = std::make_shared<Message>();
+ originalMessage->setFrom(messageJID);
+ originalMessage->setTo(jid2);
+ originalMessage->setType(Message::Chat);
+ originalMessage->addPayload(std::make_shared<DeliveryReceipt>("abcdefg123456"));
+
+ auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
+
+ manager_->handleIncomingMessage(messageWrapper);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), window->receiptChanges_.size());
+ CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptReceived, window->receiptChanges_[1].second);
+
+ //Delivery receipt should not change the latest recent entry. Checking for the original message.
+ auto recentChats = manager_->getRecentChats();
+ CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1));
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, messageJID.toBare());
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some text my other resource sent."));
+ }
+ }
+
+ void testCarbonsForwardedIncomingDuplicates() {
+ JID messageJID("testling@test.com/resource1");
+ JID jid2 = jid_.toBare().withResource("someOtherResource");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ std::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ std::string body("This is a legible message. >HEH@)oeueu");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ // incoming carbons message from another resource and duplicate of it
+ {
+ auto originalMessage = std::make_shared<Message>();
+ originalMessage->setFrom(messageJID);
+ originalMessage->setTo(jid2);
+ originalMessage->setID("BDD82F0B-2523-48BF-B8CA-17B23A314BC2");
+ originalMessage->setType(Message::Chat);
+ std::string forwardedBody = "Some further text.";
+ originalMessage->setBody(forwardedBody);
+
+ auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
+
+ manager_->handleIncomingMessage(messageWrapper);
+
+ CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_);
+ window->resetLastMessages();
+
+ messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
+ manager_->handleIncomingMessage(messageWrapper);
+ CPPUNIT_ASSERT_EQUAL(std::string(), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_);
+
+ auto recentChats = manager_->getRecentChats();
+ CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1));
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, originalMessage->getFrom().toBare());
+ CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some further text."));
+ }
+ }
+
+ /**
+ * This test case ensures correct handling of the ideal case where the replace
+ * message refers to a message with a known ID. This results in the last
+ * message being replaced.
+ */
+ void testChatControllerMessageCorrectionCorrectReplaceID() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ auto message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text before edit");
+ message->setID("someID");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text after edit");
+ message->addPayload(std::make_shared<Replace>("someID"));
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ }
+
+ /**
+ * This test case ensures correct handling of the case where the replace
+ * message refers to a message with a unknown ID. The replace message should
+ * be treated like a non-repalce message in this case, with no replacement
+ * occuring.
+ */
+ void testChatControllerMessageCorrectionIncorrectReplaceID() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ auto message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text before edit");
+ message->setID("someID");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text after failed edit");
+ message->addPayload(std::make_shared<Replace>("wrongID"));
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text after failed edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ }
+
+ void testChatControllerMessageCorrectionReplaceBySameResource() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ auto message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text before edit");
+ message->setID("someID");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text after edit");
+ message->addPayload(std::make_shared<Replace>("someID"));
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ }
+
+ void testChatControllerMessageCorrectionReplaceByOtherResource() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ auto message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text before edit");
+ message->setID("someID");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ message = std::make_shared<Message>();
+ message->setFrom(messageJID.toBare().withResource("resource2"));
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text after edit");
+ message->addPayload(std::make_shared<Replace>("someID"));
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ }
+
+ void testMUCControllerMessageCorrectionNoIDMatchRequired() {
+ JID mucJID("SomeMUCRoom@test.com");
+ manager_->setOnline(true);
+
+ // Open chat window to a sender.
+ MockChatWindow* window = new MockChatWindow();
+
+ std::vector<JID> jids;
+ jids.emplace_back("foo@test.com");
+ jids.emplace_back("bar@test.com");
+
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
+
+ auto nickname = std::string("SomeNickName");
+ // Join room
+ {
+ auto joinRoomEvent = std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname);
+ uiEventStream_->send(joinRoomEvent);
+ }
+
+ auto genRemoteMUCPresence = [=]() {
+ auto presence = Presence::create();
+ presence->setFrom(mucJID.withResource(nickname));
+ presence->setTo(jid_);
+ return presence;
+ };
+
+ {
+ auto presence = genRemoteMUCPresence();
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addStatusCode(110);
+ userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator));
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+
+ {
+ auto presence = genRemoteMUCPresence();
+ presence->setFrom(mucJID.withResource("someDifferentNickname"));
+ auto userPayload = std::make_shared<MUCUserPayload>();
+ userPayload->addItem(MUCItem(MUCOccupant::Member, JID("foo@bar.com"), MUCOccupant::Moderator));
+ presence->addPayload(userPayload);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+
+ {
+ Message::ref mucMirrored = std::make_shared<Message>();
+ mucMirrored->setFrom(mucJID.withResource(nickname));
+ mucMirrored->setTo(jid_);
+ mucMirrored->setType(Message::Groupchat);
+ mucMirrored->setID("fooBlaID_1");
+ mucMirrored->setBody("Some misssssspelled message.");
+ manager_->handleIncomingMessage(mucMirrored);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("Some misssssspelled message."), window->bodyFromMessage(window->lastAddedMessage_));
+
+ // Replace message with non-matching ID
+ {
+ Message::ref mucMirrored = std::make_shared<Message>();
+ mucMirrored->setFrom(mucJID.withResource(nickname));
+ mucMirrored->setTo(jid_);
+ mucMirrored->setType(Message::Groupchat);
+ mucMirrored->setID("fooBlaID_3");
+ mucMirrored->setBody("Some correctly spelled message.");
+ mucMirrored->addPayload(std::make_shared<Replace>("fooBlaID_2"));
+ manager_->handleIncomingMessage(mucMirrored);
+ }
+ CPPUNIT_ASSERT_EQUAL(std::string("Some correctly spelled message."), window->bodyFromMessage(window->lastReplacedMessage_));
+ }
+
+ void impromptuChatSetup(MockChatWindow* window) {
+ stanzaChannel_->uniqueIDs_ = true;
+ JID mucJID("795B7BBE-9099-4A0D-81BA-C816F78E275C@test.com");
+ manager_->setOnline(true);
+
+ std::shared_ptr<IQ> infoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]);
+ CPPUNIT_ASSERT(infoRequest);
+
+ std::shared_ptr<IQ> infoResponse = IQ::createResult(infoRequest->getFrom(), infoRequest->getTo(), infoRequest->getID());
+
+ DiscoInfo info;
+ info.addIdentity(DiscoInfo::Identity("Shakespearean Chat Service", "conference", "text"));
+ info.addFeature("http://jabber.org/protocol/muc");
+ infoResponse->addPayload(std::make_shared<DiscoInfo>(info));
+ stanzaChannel_->onIQReceived(infoResponse);
+
+ std::vector<JID> jids;
+ jids.emplace_back("foo@test.com");
+ jids.emplace_back("bar@test.com");
+
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
+ uiEventStream_->send(std::make_shared<CreateImpromptuMUCUIEvent>(jids, mucJID, ""));
+ CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle());
+
+ // Check the MUC security marking request
+ auto mucInfoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[2]);
+ CPPUNIT_ASSERT(mucInfoRequest);
+
+ auto mucJoinPresence = std::dynamic_pointer_cast<Presence>(stanzaChannel_->sentStanzas[3]);
+ CPPUNIT_ASSERT(mucJoinPresence);
+
+ // MUC presence reply
+ auto mucResponse = Presence::create();
+ mucResponse->setTo(jid_);
+ mucResponse->setFrom(mucJoinPresence->getTo());
+ mucResponse->addPayload([]() {
+ auto mucUser = std::make_shared<MUCUserPayload>();
+ mucUser->addItem(MUCItem(MUCOccupant::Member, MUCOccupant::Participant));
+ mucUser->addStatusCode(MUCUserPayload::StatusCode(110));
+ mucUser->addStatusCode(MUCUserPayload::StatusCode(210));
+ return mucUser;
+ }());
+ stanzaChannel_->onPresenceReceived(mucResponse);
+
+ // Before people join the impromptu room, the title is based on names coming from Roster
+ CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle());
+
+ auto mucParticipantJoined = [&](const JID& jid) {
+ auto participantJoinedPresence = Presence::create();
+ participantJoinedPresence->setTo(jid_);
+ participantJoinedPresence->setFrom(mucJID.withResource(jid.toString()));
+ auto mucUser = std::make_shared<MUCUserPayload>();
+ mucUser->addItem(MUCItem(MUCOccupant::Member, MUCOccupant::Participant));
+ participantJoinedPresence->addPayload(mucUser);
+ return participantJoinedPresence;
+ };
+
+ for (const auto& participantJID : jids) {
+ stanzaChannel_->onPresenceReceived(mucParticipantJoined(participantJID));
+ }
+ }
+
+ void testImpromptuChatTitle() {
+ // Open a new chat window to a sender.
+ MockChatWindow* window = new MockChatWindow();
+ impromptuChatSetup(window);
+ // After people joined, the title is the list of participant nicknames or names coming from Roster (if nicknames are unavailable)
+ CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle());
+ }
+
+ void testImpromptuChatWindowTitle() {
+ // Open a new chat window to a sender.
+ MockChatWindow* window = new MockChatWindow();
+ impromptuChatSetup(window);
+ // After people joined, the title of chat window is combined of participant nicknames or names coming from Roster (if nicknames are unavailable)
+ CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), window->name_);
+ }
+
+ void testStandardMUCChatWindowTitle() {
+ JID mucJID("mucroom@rooms.test.com");
+ std::string nickname = "toodles";
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
+
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname));
+ CPPUNIT_ASSERT_EQUAL(std::string("mucroom"), window->name_);
+ }
+
+ std::shared_ptr<Storage> createBookmarkStorageWithJID(std::shared_ptr<IQ> bookmarkRequest, const JID& jid, const bool autojoin) {
+ CPPUNIT_ASSERT(bookmarkRequest);
+ CPPUNIT_ASSERT_EQUAL(IQ::Get, bookmarkRequest->getType());
+
+ auto privateStorage = bookmarkRequest->getPayload<PrivateStorage>();
+ CPPUNIT_ASSERT(privateStorage);
+
+ auto storage = std::dynamic_pointer_cast<Storage>(privateStorage->getPayload());
+ CPPUNIT_ASSERT(storage);
+
+ auto roomsStorage = std::make_shared<Storage>();
+ if (jid.isValid()) {
+ auto room = Storage::Room();
+ room.jid = jid;
+ room.autoJoin = autojoin;
+ roomsStorage->addRoom(room);
+ }
+ return roomsStorage;
+ }
+
+ void testReceivingBookmarksWithDomainJID() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>(createBookmarkStorageWithJID(bookmarkRequest, "montague.lit", true))
+ );
+ stanzaChannel_->onIQReceived(response);
+ }
+
+ void testReceivingBookmarksWithBareJID() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID("example@montague.lit"), uiEventStream_).Return(window);
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>(createBookmarkStorageWithJID(bookmarkRequest, "example@montague.lit", true))
+ );
+ stanzaChannel_->onIQReceived(response);
+ }
+
+ void testReceivingNoBookmarks() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>()
+ );
+ stanzaChannel_->onIQReceived(response);
+ }
+
+ void testReceivingNullBookmarks() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ nullptr
+ );
+ stanzaChannel_->onIQReceived(response);
+ }
+
+ void testReceivingBookmarksError() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto response = IQ::createError(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ ErrorPayload::Condition::ServiceUnavailable,
+ ErrorPayload::Type::Cancel,
+ nullptr
+ );
+ stanzaChannel_->onIQReceived(response);
+ }
+
+ void testReceivingBookmarksWithFullJID() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>(createBookmarkStorageWithJID(bookmarkRequest, "example@montague.lit/someresource", true))
+ );
+ stanzaChannel_->onIQReceived(response);
+ }
+
+ void testAutoJoinBookmarksAndChattables() {
+
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto roomsStorage = createBookmarkStorageWithJID(bookmarkRequest, "autojoin@bookmark.lit", true);
+ auto room = Storage::Room();
+ room.jid = "noAutojoin@bookmark.lit";
+ roomsStorage->addRoom(room);
+
+ //Only autojoin@bookmark.lit window should open.
+ MockChatWindow* autojoinBookmarkWindow = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID("autojoin@bookmark.lit"), uiEventStream_).Return(autojoinBookmarkWindow);
+
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>(roomsStorage)
+ );
+ stanzaChannel_->onIQReceived(response);
+ //Both bookmarks should be added to the chattables.
+ CPPUNIT_ASSERT_EQUAL(size_t(2), chattables_->get().size());
+ auto autoJoinState = chattables_->getState("autojoin@bookmark.lit");
+ CPPUNIT_ASSERT(autoJoinState.type == Chattables::State::Type::Room);
+ CPPUNIT_ASSERT_EQUAL(autoJoinState.status, StatusShow::Online);
+ auto noAutoJoinState = chattables_->getState("noAutojoin@bookmark.lit");
+ CPPUNIT_ASSERT(noAutoJoinState.type == Chattables::State::Type::Room);
+ CPPUNIT_ASSERT_EQUAL(noAutoJoinState.status, StatusShow::None);
+ }
+
+ void testJoinNoAutojoinBookmark() {
+
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto roomsStorage = createBookmarkStorageWithJID(bookmarkRequest, "example@montague.lit", false);
+
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>(roomsStorage)
+ );
+ stanzaChannel_->onIQReceived(response);
+
+ //Join previous bookmarked room, expecting no increase in chattables and change of autojoin in bookmark to true
+ MockChatWindow* newExampleChatWindow = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID("example@montague.lit"), uiEventStream_).Return(newExampleChatWindow);
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>("example@montague.lit", boost::optional<std::string>(), boost::optional<std::string>()));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), chattables_->get().size());
+ auto state = chattables_->getState("example@montague.lit");
+ CPPUNIT_ASSERT(state.type == Chattables::State::Type::Room);
+ CPPUNIT_ASSERT_EQUAL(state.status, StatusShow::Online);
+
+ auto bookmarks = manager_->getBookmarkManager()->getBookmarks();
+ CPPUNIT_ASSERT_EQUAL(bookmarks.size(), size_t(1));
+ CPPUNIT_ASSERT(bookmarks[0].getRoom() == JID("example@montague.lit"));
+ CPPUNIT_ASSERT(bookmarks[0].getAutojoin());
+ }
+
+ void testJoinAndBookmarkMUC() {
+ auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+ auto roomsStorage = createBookmarkStorageWithJID(bookmarkRequest, "", true);
+ auto response = IQ::createResult(
+ bookmarkRequest->getFrom(),
+ bookmarkRequest->getTo(),
+ bookmarkRequest->getID(),
+ std::make_shared<PrivateStorage>(roomsStorage)
+ );
+ stanzaChannel_->onIQReceived(response);
+
+ //Join non-bookmarked room expecting for the room to get bookmarked with autojoin to true
+ MockChatWindow* exampleChatWindow = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With("example@montague.lit", uiEventStream_).Return(exampleChatWindow);
+ uiEventStream_->send(std::make_shared<JoinMUCUIEvent>("example@montague.lit", boost::optional<std::string>(), boost::optional<std::string>()));
+ {
+ CPPUNIT_ASSERT_EQUAL(size_t(1), chattables_->get().size());
+ auto state = chattables_->getState("example@montague.lit");
+ CPPUNIT_ASSERT(state.type == Chattables::State::Type::Room);
+ CPPUNIT_ASSERT_EQUAL(state.status, StatusShow::Online);
+
+ auto bookmarks = manager_->getBookmarkManager()->getBookmarks();
+ CPPUNIT_ASSERT_EQUAL(bookmarks.size(), size_t(1));
+ CPPUNIT_ASSERT(bookmarks[0].getRoom() == JID("example@montague.lit"));
+ CPPUNIT_ASSERT(bookmarks[0].getAutojoin());
+
+ }
+ //Exiting room that is bookmarked, expecting chattable to stay but bookmark autojoin change to false.
+ exampleChatWindow->onClosed();
+ {
+ CPPUNIT_ASSERT_EQUAL(size_t(1), chattables_->get().size());
+ auto bookmarks = manager_->getBookmarkManager()->getBookmarks();
+ CPPUNIT_ASSERT_EQUAL(bookmarks.size(), size_t(1));
+ CPPUNIT_ASSERT(bookmarks[0].getRoom() == JID("example@montague.lit"));
+ CPPUNIT_ASSERT(!bookmarks[0].getAutojoin());
+ }
+ }
private:
- boost::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
- boost::shared_ptr<Message> message = boost::make_shared<Message>();
- message->setFrom(from);
- message->setID(id);
- message->setBody("This will cause the window to open");
- message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
- return message;
- }
-
- size_t st(int i) {
- return static_cast<size_t>(i);
- }
+ std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
+ std::shared_ptr<Message> message = std::make_shared<Message>();
+ message->setFrom(from);
+ message->setID(id);
+ message->setBody("This will cause the window to open");
+ message->addPayload(std::make_shared<DeliveryReceiptRequest>());
+ return message;
+ }
+
+ size_t st(int i) {
+ return static_cast<size_t>(i);
+ }
+
+ void handleHighlightAction(const HighlightAction& action) {
+ handledHighlightActions_++;
+ if (action.getSoundFilePath()) {
+ soundsPlayed_.insert(action.getSoundFilePath().get_value_or(""));
+ }
+ }
private:
- JID jid_;
- ChatsManager* manager_;
- DummyStanzaChannel* stanzaChannel_;
- IQChannel* iqChannel_;
- IQRouter* iqRouter_;
- EventController* eventController_;
- ChatWindowFactory* chatWindowFactory_;
- JoinMUCWindowFactory* joinMUCWindowFactory_;
- NickResolver* nickResolver_;
- PresenceOracle* presenceOracle_;
- AvatarManager* avatarManager_;
- boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
- XMPPRosterImpl* xmppRoster_;
- PresenceSender* presenceSender_;
- MockRepository* mocks_;
- UIEventStream* uiEventStream_;
- ChatListWindowFactory* chatListWindowFactory_;
- WhiteboardWindowFactory* whiteboardWindowFactory_;
- MUCSearchWindowFactory* mucSearchWindowFactory_;
- MUCRegistry* mucRegistry_;
- DirectedPresenceSender* directedPresenceSender_;
- EntityCapsManager* entityCapsManager_;
- CapsProvider* capsProvider_;
- MUCManager* mucManager_;
- DummySettingsProvider* settings_;
- ProfileSettingsProvider* profileSettings_;
- ChatListWindow* chatListWindow_;
- FileTransferOverview* ftOverview_;
- FileTransferManager* ftManager_;
- WhiteboardSessionManager* wbSessionManager_;
- WhiteboardManager* wbManager_;
- HighlightManager* highlightManager_;
- ClientBlockListManager* clientBlockListManager_;
- std::map<std::string, std::string> emoticons_;
+ JID jid_;
+ std::unique_ptr<DummyNotifier> notifier_;
+ ExtendedChatsManager* manager_;
+ DummyStanzaChannel* stanzaChannel_;
+ IQRouter* iqRouter_;
+ EventController* eventController_;
+ ChatWindowFactory* chatWindowFactory_;
+ JoinMUCWindowFactory* joinMUCWindowFactory_;
+ NickResolver* nickResolver_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ EventNotifier* eventNotifier_;
+ std::shared_ptr<DiscoInfo> serverDiscoInfo_;
+ XMPPRosterImpl* xmppRoster_;
+ PresenceSender* presenceSender_;
+ MockRepository* mocks_;
+ UIEventStream* uiEventStream_;
+ ChatListWindowFactory* chatListWindowFactory_;
+ WhiteboardWindowFactory* whiteboardWindowFactory_;
+ MUCSearchWindowFactory* mucSearchWindowFactory_;
+ MUCRegistry* mucRegistry_;
+ DirectedPresenceSender* directedPresenceSender_;
+ DummyEntityCapsProvider* entityCapsProvider_;
+ MUCManager* mucManager_;
+ DummySettingsProvider* settings_;
+ ProfileSettingsProvider* profileSettings_;
+ ChatListWindow* chatListWindow_;
+ FileTransferOverview* ftOverview_;
+ FileTransferManager* ftManager_;
+ WhiteboardSessionManager* wbSessionManager_;
+ WhiteboardManager* wbManager_;
+ HighlightManager* highlightManager_;
+ ClientBlockListManager* clientBlockListManager_;
+ VCardManager* vcardManager_;
+ CryptoProvider* crypto_;
+ VCardStorage* vcardStorage_;
+ std::map<std::string, std::string> emoticons_;
+ int handledHighlightActions_;
+ std::set<std::string> soundsPlayed_;
+ DummyTimerFactory* timerFactory_;
+ std::unique_ptr<Chattables> chattables_;
+
};
CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
-
diff --git a/Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp b/Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp
new file mode 100644
index 0000000..f30e3fd
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <gtest/gtest.h>
+#include <hippomocks.h>
+
+#include <Swift/Controllers/Chat/Chattables.h>
+
+// Clang wrongly things that tests for 0 are using 0 as null.
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
+
+using namespace Swift;
+
+class ChattablesTest : public ::testing::Test {
+ protected:
+ void SetUp() {
+ beginListSize_ = 0;
+ endListSize_ = 0;
+ callsToBegin_ = 0;
+ callsToEnd_ = 0;
+ }
+
+ void TearDown() {
+ }
+
+ void updateBeginListSize(int listSize);
+ void updateEndListSize();
+
+ Chattables chattables_;
+ int beginListSize_;
+ int endListSize_;
+ int callsToBegin_;
+ int callsToEnd_;
+};
+
+void ChattablesTest::updateBeginListSize(int listSize) {
+ beginListSize_ = listSize;
+ callsToBegin_++;
+}
+
+void ChattablesTest::updateEndListSize() {
+ endListSize_ = chattables_.get().size();
+ callsToEnd_++;
+}
+
+TEST_F(ChattablesTest, testAddJID) {
+ chattables_.onBeginAdd.connect([this](int listSize){ updateBeginListSize(listSize); });
+ chattables_.onAdded.connect([this](){ updateEndListSize(); });
+ JID jid("user@swift.jid");
+
+ chattables_.addJID(jid, Chattables::State::Type::Person);
+ ASSERT_EQ(0, beginListSize_);
+ ASSERT_EQ(1, endListSize_);
+ ASSERT_EQ(1, callsToBegin_);
+ ASSERT_EQ(1, callsToEnd_);
+
+ ASSERT_EQ(jid, chattables_.get()[0]);
+ Chattables::State state = chattables_.getState(jid);
+ ASSERT_EQ(jid, state.jid);
+ ASSERT_EQ(Chattables::State::Type::Person, state.type);
+}
+
+TEST_F(ChattablesTest, testAddMultipleJIDs) {
+ chattables_.onBeginAdd.connect([this](int listSize){ updateBeginListSize(listSize); });
+ chattables_.onAdded.connect([this](){ updateEndListSize(); });
+ JID jid0("user0@swift.jid");
+ JID jid1("user1@swift.jid");
+ JID jid2("user2@swift.jid");
+
+ chattables_.addJID(jid0, Chattables::State::Type::Person);
+ ASSERT_EQ(0, beginListSize_);
+ ASSERT_EQ(1, endListSize_);
+ ASSERT_EQ(1, callsToBegin_);
+ ASSERT_EQ(1, callsToEnd_);
+ chattables_.addJID(jid1, Chattables::State::Type::Person);
+ ASSERT_EQ(1, beginListSize_);
+ ASSERT_EQ(2, endListSize_);
+ ASSERT_EQ(2, callsToBegin_);
+ ASSERT_EQ(2, callsToEnd_);
+ chattables_.addJID(jid2, Chattables::State::Type::Person);
+ ASSERT_EQ(2, beginListSize_);
+ ASSERT_EQ(3, endListSize_);
+ ASSERT_EQ(3, callsToBegin_);
+ ASSERT_EQ(3, callsToEnd_);
+}
+
+TEST_F(ChattablesTest, testAddRoomJID) {
+ chattables_.onBeginAdd.connect([this](int listSize){ updateBeginListSize(listSize); });
+ chattables_.onAdded.connect([this](){ updateEndListSize(); });
+ JID jid("room@swift.jid");
+
+ chattables_.addJID(jid, Chattables::State::Type::Room);
+ ASSERT_EQ(0, beginListSize_);
+ ASSERT_EQ(1, endListSize_);
+ ASSERT_EQ(1, callsToBegin_);
+ ASSERT_EQ(1, callsToEnd_);
+
+ ASSERT_EQ(jid, chattables_.get()[0]);
+ Chattables::State state = chattables_.getState(jid);
+ ASSERT_EQ(jid, state.jid);
+ ASSERT_EQ(Chattables::State::Type::Room, state.type);
+}
+
+TEST_F(ChattablesTest, testSetState) {
+ JID jid("user@swift.jid");
+ chattables_.addJID(jid, Chattables::State::Type::Person);
+ ASSERT_EQ(1, chattables_.get().size());
+ ASSERT_EQ(jid, chattables_.get()[0]);
+ ASSERT_EQ(Chattables::State::Type::Person, chattables_.getState(jid).type);
+ ASSERT_EQ(StatusShow::None, chattables_.getState(jid).status);
+
+ JID returnedJID;
+ int returnedIndex;
+ int callsToChanged = 0;
+ chattables_.onChanged.connect([&returnedJID, &returnedIndex, &callsToChanged](const JID& jid, int index){
+ returnedJID = jid;
+ returnedIndex = index;
+ callsToChanged++;
+ });
+
+ Chattables::State newState;
+ newState.jid = jid;
+ newState.type = Chattables::State::Type::Room;
+ newState.status = StatusShow::Online;
+ chattables_.setState(jid, newState);
+
+ auto storedState = chattables_.getState(jid);
+
+ ASSERT_EQ(1, chattables_.get().size());
+ ASSERT_EQ(jid, chattables_.get()[0]);
+ ASSERT_EQ(jid, returnedJID);
+ ASSERT_EQ(0, returnedIndex);
+ ASSERT_EQ(1, callsToChanged);
+ ASSERT_EQ(jid, storedState.jid);
+ ASSERT_EQ(Chattables::State::Type::Room, storedState.type);
+ ASSERT_EQ(StatusShow::Online, storedState.status);
+}
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 5ca0687..5339c7b 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -1,371 +1,964 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
-#include <cppunit/extensions/HelperMacros.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
#include <boost/algorithm/string.hpp>
+
+#include <memory>
+
+#include <gtest/gtest.h>
#include <hippomocks.h>
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swiften/Presence/DirectedPresenceSender.h"
-#include "Swiften/Presence/StanzaChannelPresenceSender.h"
-#include "Swiften/Avatars/NullAvatarManager.h"
-#include "Swift/Controllers/Chat/MUCController.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swiften/Roster/XMPPRoster.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UnitTest/MockChatWindow.h"
-#include "Swiften/Client/DummyStanzaChannel.h"
-#include "Swiften/Queries/DummyIQChannel.h"
-#include "Swiften/Presence/PresenceOracle.h"
-#include "Swiften/Network/TimerFactory.h"
-#include "Swiften/Elements/MUCUserPayload.h"
-#include "Swiften/Disco/DummyEntityCapsProvider.h"
-#include <Swiften/VCards/VCardMemoryStorage.h>
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Crypto/CryptoProvider.h>
#include <Swiften/Crypto/PlatformCryptoProvider.h>
+#include <Swiften/Disco/DummyEntityCapsProvider.h>
+#include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/Elements/Thread.h>
+#include <Swiften/MUC/MUCBookmarkManager.h>
+#include <Swiften/MUC/UnitTest/MockMUC.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Presence/DirectedPresenceSender.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Presence/StanzaChannelPresenceSender.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Roster/XMPPRoster.h>
+#include <Swiften/Roster/XMPPRosterImpl.h>
#include <Swiften/VCards/VCardManager.h>
-#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
+
#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Chat/Chattables.h>
+#include <Swift/Controllers/Chat/MUCController.h>
#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
-#include <Swiften/Crypto/CryptoProvider.h>
+#include <Swift/Controllers/UnitTest/MockChatWindow.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+
+// Clang wrongly things that tests for 0 are using 0 as null.
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
using namespace Swift;
-class MUCControllerTest : public CppUnit::TestFixture {
- CPPUNIT_TEST_SUITE(MUCControllerTest);
- CPPUNIT_TEST(testJoinPartStringContructionSimple);
- CPPUNIT_TEST(testJoinPartStringContructionMixed);
- CPPUNIT_TEST(testAppendToJoinParts);
- CPPUNIT_TEST(testAddressedToSelf);
- CPPUNIT_TEST(testNotAddressedToSelf);
- CPPUNIT_TEST(testAddressedToSelfBySelf);
- CPPUNIT_TEST(testMessageWithEmptyLabelItem);
- CPPUNIT_TEST(testMessageWithLabelItem);
- CPPUNIT_TEST(testCorrectMessageWithLabelItem);
- CPPUNIT_TEST_SUITE_END();
-
-public:
- void setUp() {
- crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
- self_ = JID("girl@wonderland.lit/rabbithole");
- nick_ = "aLiCe";
- mucJID_ = JID("teaparty@rooms.wonderland.lit");
- mocks_ = new MockRepository();
- stanzaChannel_ = new DummyStanzaChannel();
- iqChannel_ = new DummyIQChannel();
- iqRouter_ = new IQRouter(iqChannel_);
- eventController_ = new EventController();
- chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
- userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>();
- presenceOracle_ = new PresenceOracle(stanzaChannel_);
- presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
- directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
- uiEventStream_ = new UIEventStream();
- avatarManager_ = new NullAvatarManager();
- TimerFactory* timerFactory = NULL;
- window_ = new MockChatWindow();
- mucRegistry_ = new MUCRegistry();
- entityCapsProvider_ = new DummyEntityCapsProvider();
- settings_ = new DummySettingsProvider();
- highlightManager_ = new HighlightManager(settings_);
- muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
- mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
- chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>());
- vcardStorage_ = new VCardMemoryStorage(crypto_.get());
- vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
- controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL);
- }
-
- void tearDown() {
- delete vcardManager_;
- delete vcardStorage_;
- delete highlightManager_;
- delete settings_;
- delete entityCapsProvider_;
- delete controller_;
- delete eventController_;
- delete presenceOracle_;
- delete mocks_;
- delete uiEventStream_;
- delete stanzaChannel_;
- delete presenceSender_;
- delete directedPresenceSender_;
- delete iqRouter_;
- delete iqChannel_;
- delete mucRegistry_;
- delete avatarManager_;
- delete chatMessageParser_;
- }
-
- void finishJoin() {
- Presence::ref presence(new Presence());
- presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
- MUCUserPayload::ref status(new MUCUserPayload());
- MUCUserPayload::StatusCode code;
- code.code = 110;
- status->addStatusCode(code);
- presence->addPayload(status);
- stanzaChannel_->onPresenceReceived(presence);
- }
-
- void testAddressedToSelf() {
- finishJoin();
- Message::ref message(new Message());
-
- message = Message::ref(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
- message->setBody("basic " + nick_ + " test.");
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size());
-
- message = Message::ref(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
- message->setBody(nick_ + ": hi there");
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size());
-
- message->setFrom(JID(muc_->getJID().toString() + "/other"));
- message->setBody("Hi there " + nick_);
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size());
-
- message = Message::ref(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/other2"));
- message->setBody("Hi " + boost::to_lower_copy(nick_) + ".");
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
-
- message = Message::ref(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/other3"));
- message->setBody("Hi bert.");
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
-
- message = Message::ref(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/other2"));
- message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie.");
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
- }
-
- void testNotAddressedToSelf() {
- finishJoin();
- Message::ref message(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/other3"));
- message->setBody("Hi there Hatter");
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size());
- }
-
- void testAddressedToSelfBySelf() {
- finishJoin();
- Message::ref message(new Message());
- message->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
- message->setBody("Hi there " + nick_);
- message->setType(Message::Groupchat);
- controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
- CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size());
- }
-
- void testMessageWithEmptyLabelItem() {
- SecurityLabelsCatalog::Item label;
- label.setSelector("Bob");
- window_->label_ = label;
- boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>();
- features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
- controller_->setAvailableServerFeatures(features);
- IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
- SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>();
- labelPayload->addItem(label);
- IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
- iqChannel_->onIQReceived(result);
- std::string messageBody("agamemnon");
- window_->onSendMessageRequest(messageBody, false);
- boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
- Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza);
- CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
- CPPUNIT_ASSERT(window_->labelsEnabled_);
- CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
- CPPUNIT_ASSERT(message);
- CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
- CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>());
- }
-
- void testMessageWithLabelItem() {
- SecurityLabel::ref label = boost::make_shared<SecurityLabel>();
- label->setLabel("a");
- SecurityLabelsCatalog::Item labelItem;
- labelItem.setSelector("Bob");
- labelItem.setLabel(label);
- window_->label_ = labelItem;
- boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>();
- features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
- controller_->setAvailableServerFeatures(features);
- IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
- SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>();
- labelPayload->addItem(labelItem);
- IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
- iqChannel_->onIQReceived(result);
- std::string messageBody("agamemnon");
- window_->onSendMessageRequest(messageBody, false);
- boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
- Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza);
- CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
- CPPUNIT_ASSERT(window_->labelsEnabled_);
- CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
- CPPUNIT_ASSERT(message);
- CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
- CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
- }
-
- void testCorrectMessageWithLabelItem() {
- SecurityLabel::ref label = boost::make_shared<SecurityLabel>();
- label->setLabel("a");
- SecurityLabelsCatalog::Item labelItem;
- labelItem.setSelector("Bob");
- labelItem.setLabel(label);
- SecurityLabel::ref label2 = boost::make_shared<SecurityLabel>();
- label->setLabel("b");
- SecurityLabelsCatalog::Item labelItem2;
- labelItem2.setSelector("Charlie");
- labelItem2.setLabel(label2);
- window_->label_ = labelItem;
- boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>();
- features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
- controller_->setAvailableServerFeatures(features);
- IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
- SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>();
- labelPayload->addItem(labelItem);
- IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
- iqChannel_->onIQReceived(result);
- std::string messageBody("agamemnon");
- window_->onSendMessageRequest(messageBody, false);
- boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
- Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza);
- CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
- CPPUNIT_ASSERT(window_->labelsEnabled_);
- CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
- CPPUNIT_ASSERT(message);
- CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
- CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
- window_->label_ = labelItem2;
- window_->onSendMessageRequest(messageBody, true);
- rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
- message = boost::dynamic_pointer_cast<Message>(rawStanza);
- CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
- CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
- }
-
- void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) {
- CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size());
- for (size_t i = 0; i < expected.size(); i++) {
- CPPUNIT_ASSERT_EQUAL(expected[i].nick, actual[i].nick);
- CPPUNIT_ASSERT_EQUAL(expected[i].type, actual[i].type);
- }
- }
-
- void testAppendToJoinParts() {
- std::vector<NickJoinPart> list;
- std::vector<NickJoinPart> gold;
- MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
- gold.push_back(NickJoinPart("Kev", Join));
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join));
- gold.push_back(NickJoinPart("Remko", Join));
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join));
- gold.push_back(NickJoinPart("Bert", Join));
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part));
- gold[2].type = JoinThenPart;
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part));
- gold[0].type = JoinThenPart;
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part));
- gold[1].type = JoinThenPart;
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
- gold.push_back(NickJoinPart("Ernie", Part));
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join));
- gold[3].type = PartThenJoin;
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
- gold[0].type = Join;
- checkEqual(gold, list);
- MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
- gold[3].type = Part;
- checkEqual(gold, list);
-
- }
-
- void testJoinPartStringContructionSimple() {
- std::vector<NickJoinPart> list;
- list.push_back(NickJoinPart("Kev", Join));
- CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false));
- list.push_back(NickJoinPart("Remko", Part));
- CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
- list.push_back(NickJoinPart("Bert", Join));
- CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
- list.push_back(NickJoinPart("Ernie", Join));
- CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
- }
-
- void testJoinPartStringContructionMixed() {
- std::vector<NickJoinPart> list;
- list.push_back(NickJoinPart("Kev", JoinThenPart));
- CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
- list.push_back(NickJoinPart("Remko", Part));
- CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
- list.push_back(NickJoinPart("Bert", PartThenJoin));
- CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
- list.push_back(NickJoinPart("Ernie", JoinThenPart));
- CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
- }
-
-private:
- JID self_;
- JID mucJID_;
- MUC::ref muc_;
- std::string nick_;
- DummyStanzaChannel* stanzaChannel_;
- DummyIQChannel* iqChannel_;
- IQRouter* iqRouter_;
- EventController* eventController_;
- ChatWindowFactory* chatWindowFactory_;
- UserSearchWindowFactory* userSearchWindowFactory_;
- MUCController* controller_;
-// NickResolver* nickResolver_;
- PresenceOracle* presenceOracle_;
- AvatarManager* avatarManager_;
- StanzaChannelPresenceSender* presenceSender_;
- DirectedPresenceSender* directedPresenceSender_;
- MockRepository* mocks_;
- UIEventStream* uiEventStream_;
- MockChatWindow* window_;
- MUCRegistry* mucRegistry_;
- DummyEntityCapsProvider* entityCapsProvider_;
- DummySettingsProvider* settings_;
- HighlightManager* highlightManager_;
- ChatMessageParser* chatMessageParser_;
- boost::shared_ptr<CryptoProvider> crypto_;
- VCardManager* vcardManager_;
- VCardMemoryStorage* vcardStorage_;
+class MUCControllerTest : public ::testing::Test {
+
+ protected:
+ void SetUp() {
+ crypto_ = std::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
+ self_ = JID("girl@wonderland.lit/rabbithole");
+ nick_ = "aLiCe";
+ mucJID_ = JID("teaparty@rooms.wonderland.lit");
+ mocks_ = new MockRepository();
+ stanzaChannel_ = new DummyStanzaChannel();
+ iqChannel_ = new DummyIQChannel();
+ iqRouter_ = new IQRouter(iqChannel_);
+ eventController_ = new EventController();
+ chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
+ userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>();
+ xmppRoster_ = new XMPPRosterImpl();
+ presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
+ presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
+ directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
+ uiEventStream_ = new UIEventStream();
+ avatarManager_ = new NullAvatarManager();
+ TimerFactory* timerFactory = nullptr;
+ window_ = new MockChatWindow();
+ mucRegistry_ = new MUCRegistry();
+ entityCapsProvider_ = new DummyEntityCapsProvider();
+ settings_ = new DummySettingsProvider();
+ highlightManager_ = new HighlightManager(settings_);
+ highlightManager_->resetToDefaultConfiguration();
+ muc_ = std::make_shared<MockMUC>(mucJID_);
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
+ chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat);
+ vcardStorage_ = new VCardMemoryStorage(crypto_.get());
+ vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
+ nickResolver_ = new NickResolver(self_, xmppRoster_, vcardManager_, mucRegistry_);
+ clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+ mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
+ chattables_ = std::make_unique<Chattables>();
+ controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_, settings_, *chattables_);
+ }
+
+ void TearDown() {
+ delete controller_;
+ delete mucBookmarkManager_;
+ delete clientBlockListManager_;
+ delete nickResolver_;
+ delete vcardManager_;
+ delete vcardStorage_;
+ delete highlightManager_;
+ delete settings_;
+ delete entityCapsProvider_;
+ delete eventController_;
+ delete presenceOracle_;
+ delete xmppRoster_;
+ delete mocks_;
+ delete uiEventStream_;
+ delete stanzaChannel_;
+ delete presenceSender_;
+ delete directedPresenceSender_;
+ delete iqRouter_;
+ delete iqChannel_;
+ delete mucRegistry_;
+ delete avatarManager_;
+ }
+
+ void finishJoin() {
+ Presence::ref presence(new Presence());
+ presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
+ MUCUserPayload::ref status(new MUCUserPayload());
+ MUCUserPayload::StatusCode code;
+ code.code = 110;
+ status->addStatusCode(code);
+ presence->addPayload(status);
+ stanzaChannel_->onPresenceReceived(presence);
+ }
+
+ void joinCompleted() {
+ std::string messageBody("test message");
+ window_->onSendMessageRequest(messageBody, false);
+ std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
+ Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
+ ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
+ ASSERT_TRUE(message);
+ ASSERT_EQ(messageBody, message->getBody().get_value_or(""));
+
+ {
+ Message::ref message = std::make_shared<Message>();
+ message->setType(Message::Groupchat);
+ message->setTo(self_);
+ message->setFrom(mucJID_.withResource("SomeNickname"));
+ message->setID(iqChannel_->getNewIQID());
+ message->setSubject("Initial");
+
+ controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
+ }
+ }
+
+ void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) {
+ ASSERT_EQ(expected.size(), actual.size());
+ for (size_t i = 0; i < expected.size(); i++) {
+ ASSERT_EQ(expected[i].nick, actual[i].nick);
+ ASSERT_EQ(expected[i].type, actual[i].type);
+ }
+ }
+
+ JID jidFromOccupant(const MUCOccupant& occupant) {
+ return JID(mucJID_.toString()+"/"+occupant.getNick());
+ }
+
+ void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) {
+ /* verify that the roster is in sync */
+ GroupRosterItem* group = window_->getRosterModel()->getRoot();
+ for (auto rosterItem : group->getChildren()) {
+ GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem);
+ ASSERT_TRUE(child);
+ for (auto childItem : child->getChildren()) {
+ ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem);
+ ASSERT_TRUE(item);
+ std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource());
+ ASSERT_TRUE(occupant != occupants.end());
+ ASSERT_TRUE(item->getMUCRole() == occupant->second.getRole());
+ ASSERT_TRUE(item->getMUCAffiliation() == occupant->second.getAffiliation());
+ }
+ }
+ }
+
+ void setMUCSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue, const bool includeFormTypeField = true) {
+ auto form = std::make_shared<Form>(Form::Type::ResultType);
+
+ if (includeFormTypeField) {
+ std::shared_ptr<FormField> formTypeField = std::make_shared<FormField>(FormField::Type::HiddenType, "http://jabber.org/protocol/muc#roominfo");
+ formTypeField->setName("FORM_TYPE");
+ form->addField(formTypeField);
+ }
+
+ auto markingField = std::make_shared<FormField>(FormField::Type::TextSingleType, markingValue);
+ auto markingForegroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, markingForegroundColorValue);
+ auto markingBackgroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, markingBackgroundColorValue);
+
+ markingField->setName("x-isode#roominfo_marking");
+ markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color");
+ markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color");
+
+ form->addField(markingField);
+ form->addField(markingForegroundColorField);
+ form->addField(markingBackgroundColorField);
+
+ auto discoInfoRef = std::make_shared<DiscoInfo>();
+ discoInfoRef->addExtension(form);
+
+ auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef);
+ iqChannel_->onIQReceived(infoResponse);
+ }
+
+ Message::ref createTestMessageWithoutSecurityLabel() {
+ auto message = std::make_shared<Message>();
+ message->setType(Message::Type::Groupchat);
+ message->setID("test-id");
+ message->setTo(self_);
+ message->setFrom(mucJID_.withResource("TestNickname"));
+ message->setBody("Do Not Read This Message");
+ return message;
+ }
+
+ JID self_;
+ JID mucJID_;
+ MockMUC::ref muc_;
+ std::string nick_;
+ DummyStanzaChannel* stanzaChannel_;
+ DummyIQChannel* iqChannel_;
+ IQRouter* iqRouter_;
+ EventController* eventController_;
+ ChatWindowFactory* chatWindowFactory_;
+ UserSearchWindowFactory* userSearchWindowFactory_;
+ MUCController* controller_;
+ NickResolver* nickResolver_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ StanzaChannelPresenceSender* presenceSender_;
+ DirectedPresenceSender* directedPresenceSender_;
+ MockRepository* mocks_;
+ UIEventStream* uiEventStream_;
+ MockChatWindow* window_;
+ MUCRegistry* mucRegistry_;
+ DummyEntityCapsProvider* entityCapsProvider_;
+ DummySettingsProvider* settings_;
+ HighlightManager* highlightManager_;
+ std::shared_ptr<ChatMessageParser> chatMessageParser_;
+ std::shared_ptr<CryptoProvider> crypto_;
+ VCardManager* vcardManager_;
+ VCardMemoryStorage* vcardStorage_;
+ ClientBlockListManager* clientBlockListManager_;
+ MUCBookmarkManager* mucBookmarkManager_;
+ XMPPRoster* xmppRoster_;
+ std::unique_ptr<Chattables> chattables_;
};
-CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
+TEST_F(MUCControllerTest, testAddressedToSelf) {
+ finishJoin();
+ Message::ref message(new Message());
+
+ message = Message::ref(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
+ message->setBody("basic " + nick_ + " test.");
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(1, eventController_->getEvents().size());
+
+ message = Message::ref(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
+ message->setBody(nick_ + ": hi there");
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(2, eventController_->getEvents().size());
+
+ message->setFrom(JID(muc_->getJID().toString() + "/other"));
+ message->setBody("Hi there " + nick_);
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(3, eventController_->getEvents().size());
+
+ message = Message::ref(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/other2"));
+ message->setBody("Hi " + boost::to_lower_copy(nick_) + ".");
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+
+ // The last message is ignored because self-mention highlights are matched case
+ // sensitive against the nickname.
+ ASSERT_EQ(3, eventController_->getEvents().size());
+
+ message = Message::ref(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/other3"));
+ message->setBody("Hi bert.");
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(3, eventController_->getEvents().size());
+
+ message = Message::ref(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/other2"));
+ message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie.");
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(3, eventController_->getEvents().size());
+}
+
+TEST_F(MUCControllerTest, testNotAddressedToSelf) {
+ finishJoin();
+ Message::ref message(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/other3"));
+ message->setBody("Hi there Hatter");
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(0, eventController_->getEvents().size());
+}
+
+TEST_F(MUCControllerTest, testAddressedToSelfBySelf) {
+ finishJoin();
+ Message::ref message(new Message());
+ message->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
+ message->setBody("Hi there " + nick_);
+ message->setType(Message::Groupchat);
+ controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
+ ASSERT_EQ(0, eventController_->getEvents().size());
+}
+
+TEST_F(MUCControllerTest, testMessageWithEmptyLabelItem) {
+ SecurityLabelsCatalog::Item label;
+ label.setSelector("Bob");
+ window_->label_ = label;
+ std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>();
+ features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
+ controller_->setAvailableServerFeatures(features);
+ IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
+ SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>();
+ labelPayload->addItem(label);
+ IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
+ iqChannel_->onIQReceived(result);
+ std::string messageBody("agamemnon");
+ window_->onSendMessageRequest(messageBody, false);
+ std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
+ Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
+ ASSERT_EQ(iq->getTo(), result->getFrom());
+ ASSERT_TRUE(window_->labelsEnabled_);
+ ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
+ ASSERT_TRUE(message);
+ ASSERT_EQ(messageBody, message->getBody().get());
+ ASSERT_FALSE(message->getPayload<SecurityLabel>());
+}
+
+TEST_F(MUCControllerTest, testMessageWithLabelItem) {
+ std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>();
+ label->setLabel("a");
+ SecurityLabelsCatalog::Item labelItem;
+ labelItem.setSelector("Bob");
+ labelItem.setLabel(label);
+ window_->label_ = labelItem;
+ std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>();
+ features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
+ controller_->setAvailableServerFeatures(features);
+ IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
+ SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>();
+ labelPayload->addItem(labelItem);
+ IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
+ iqChannel_->onIQReceived(result);
+ std::string messageBody("agamemnon");
+ window_->onSendMessageRequest(messageBody, false);
+ std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
+ Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
+ ASSERT_EQ(iq->getTo(), result->getFrom());
+ ASSERT_TRUE(window_->labelsEnabled_);
+ ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
+ ASSERT_TRUE(message);
+ ASSERT_EQ(messageBody, message->getBody().get());
+ ASSERT_EQ(label, message->getPayload<SecurityLabel>());
+}
+
+TEST_F(MUCControllerTest, testCorrectMessageWithLabelItem) {
+ std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>();
+ label->setLabel("a");
+ SecurityLabelsCatalog::Item labelItem;
+ labelItem.setSelector("Bob");
+ labelItem.setLabel(label);
+ std::shared_ptr<SecurityLabel> label2 = std::make_shared<SecurityLabel>();
+ label->setLabel("b");
+ SecurityLabelsCatalog::Item labelItem2;
+ labelItem2.setSelector("Charlie");
+ labelItem2.setLabel(label2);
+ window_->label_ = labelItem;
+ std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>();
+ features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
+ controller_->setAvailableServerFeatures(features);
+ IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
+ SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>();
+ labelPayload->addItem(labelItem);
+ IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
+ iqChannel_->onIQReceived(result);
+ std::string messageBody("agamemnon");
+ window_->onSendMessageRequest(messageBody, false);
+ std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
+ Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
+ ASSERT_EQ(iq->getTo(), result->getFrom());
+ ASSERT_TRUE(window_->labelsEnabled_);
+ ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
+ ASSERT_TRUE(message);
+ ASSERT_EQ(messageBody, message->getBody().get());
+ ASSERT_EQ(label, message->getPayload<SecurityLabel>());
+ window_->label_ = labelItem2;
+ window_->onSendMessageRequest(messageBody, true);
+ rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
+ message = std::dynamic_pointer_cast<Message>(rawStanza);
+ ASSERT_EQ(messageBody, message->getBody().get());
+ ASSERT_EQ(label, message->getPayload<SecurityLabel>());
+}
+
+TEST_F(MUCControllerTest, testAppendToJoinParts) {
+ std::vector<NickJoinPart> list;
+ std::vector<NickJoinPart> gold;
+ MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
+ gold.push_back(NickJoinPart("Kev", Join));
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join));
+ gold.push_back(NickJoinPart("Remko", Join));
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join));
+ gold.push_back(NickJoinPart("Bert", Join));
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part));
+ gold[2].type = JoinThenPart;
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part));
+ gold[0].type = JoinThenPart;
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part));
+ gold[1].type = JoinThenPart;
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
+ gold.push_back(NickJoinPart("Ernie", Part));
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join));
+ gold[3].type = PartThenJoin;
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
+ gold[0].type = Join;
+ checkEqual(gold, list);
+ MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
+ gold[3].type = Part;
+ checkEqual(gold, list);
+
+}
+
+TEST_F(MUCControllerTest, testJoinPartStringContructionSimple) {
+ std::vector<NickJoinPart> list;
+ list.push_back(NickJoinPart("Kev", Join));
+ ASSERT_EQ(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false));
+ list.push_back(NickJoinPart("Remko", Part));
+ ASSERT_EQ(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
+ list.push_back(NickJoinPart("Bert", Join));
+ ASSERT_EQ(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
+ list.push_back(NickJoinPart("Ernie", Join));
+ ASSERT_EQ(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
+}
+
+TEST_F(MUCControllerTest, testJoinPartStringContructionMixed) {
+ std::vector<NickJoinPart> list;
+ list.push_back(NickJoinPart("Kev", JoinThenPart));
+ ASSERT_EQ(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
+ list.push_back(NickJoinPart("Remko", Part));
+ ASSERT_EQ(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
+ list.push_back(NickJoinPart("Bert", PartThenJoin));
+ ASSERT_EQ(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
+ list.push_back(NickJoinPart("Ernie", JoinThenPart));
+ ASSERT_EQ(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
+}
+
+TEST_F(MUCControllerTest, testRoleAffiliationStates) {
+
+ typedef std::map<std::string, MUCOccupant> occupant_map;
+ occupant_map occupants;
+ occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner)));
+ occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner)));
+ occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner)));
+ occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner)));
+
+ /* populate the MUC with fake users */
+ for (auto&& occupant : occupants) {
+ muc_->insertOccupant(occupant.second);
+ }
+
+ std::vector<MUCOccupant> alterations;
+ alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin));
+ alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member));
+ alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast));
+ alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member));
+ alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner));
+ alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast));
+ alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation));
+ alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation));
+ alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast));
+
+ for (const auto& alteration : alterations) {
+ /* perform an alteration to a user's role and affiliation */
+ occupant_map::iterator occupant = occupants.find(alteration.getNick());
+ ASSERT_TRUE(occupant != occupants.end());
+ const JID jid = jidFromOccupant(occupant->second);
+ /* change the affiliation, leave the role in place */
+ muc_->changeAffiliation(jid, alteration.getAffiliation());
+ occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation());
+ testRoleAffiliationStatesVerify(occupants);
+ /* change the role, leave the affiliation in place */
+ muc_->changeOccupantRole(jid, alteration.getRole());
+ occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation());
+ testRoleAffiliationStatesVerify(occupants);
+ }
+}
+
+TEST_F(MUCControllerTest, testSubjectChangeCorrect) {
+ joinCompleted();
+
+ {
+ Message::ref message = std::make_shared<Message>();
+ message->setType(Message::Groupchat);
+ message->setTo(self_);
+ message->setFrom(mucJID_.withResource("SomeNickname"));
+ message->setID("3FB99C56-7C92-4755-91B0-9C0098BC7AE0");
+ message->setSubject("New Room Subject");
+
+ controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
+ ASSERT_EQ(std::string("The room subject is now: New Room Subject"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
+ }
+}
+
+/*
+ * Test that message stanzas with subject element and non-empty body element do not cause a subject change.
+ */
+TEST_F(MUCControllerTest, testSubjectChangeIncorrectA) {
+ joinCompleted();
+
+ {
+ Message::ref message = std::make_shared<Message>();
+ message->setType(Message::Groupchat);
+ message->setTo(self_);
+ message->setFrom(mucJID_.withResource("SomeNickname"));
+ message->setID(iqChannel_->getNewIQID());
+ message->setSubject("New Room Subject");
+ message->setBody("Some body text that prevents this stanza from being a subject change.");
+
+ controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
+ ASSERT_EQ(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
+ }
+}
+
+/*
+ * Test that message stanzas with subject element and thread element do not cause a subject change.
+ */
+TEST_F(MUCControllerTest, testSubjectChangeIncorrectB) {
+ joinCompleted();
+
+ {
+ Message::ref message = std::make_shared<Message>();
+ message->setType(Message::Groupchat);
+ message->setTo(self_);
+ message->setFrom(mucJID_.withResource("SomeNickname"));
+ message->setID(iqChannel_->getNewIQID());
+ message->setSubject("New Room Subject");
+ message->addPayload(std::make_shared<Thread>("Thread that prevents the subject change."));
+
+ controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
+ ASSERT_EQ(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
+ }
+}
+
+/*
+ * Test that message stanzas with subject element and empty body element do not cause a subject change.
+ */
+TEST_F(MUCControllerTest, testSubjectChangeIncorrectC) {
+ joinCompleted();
+
+ {
+ Message::ref message = std::make_shared<Message>();
+ message->setType(Message::Groupchat);
+ message->setTo(self_);
+ message->setFrom(mucJID_.withResource("SomeNickname"));
+ message->setID(iqChannel_->getNewIQID());
+ message->setSubject("New Room Subject");
+ message->setBody("");
+
+ controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
+ ASSERT_EQ(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
+ }
+}
+
+TEST_F(MUCControllerTest, testHandleOccupantNicknameChanged) {
+ const auto occupantCount = [&](const std::string & nick) {
+ auto roster = window_->getRosterModel();
+ const auto currentOccupantsJIDs = roster->getJIDs();
+ int count = 0;
+ for (auto & p : currentOccupantsJIDs) {
+ if (p.getResource() == nick) {
+ ++count;
+ }
+ }
+ return count;
+ };
+
+ muc_->insertOccupant(MUCOccupant("TestUserOne", MUCOccupant::Participant, MUCOccupant::Owner));
+ muc_->insertOccupant(MUCOccupant("TestUserTwo", MUCOccupant::Participant, MUCOccupant::Owner));
+ muc_->insertOccupant(MUCOccupant("TestUserThree", MUCOccupant::Participant, MUCOccupant::Owner));
+
+ muc_->onOccupantNicknameChanged("TestUserOne", "TestUserTwo");
+
+ ASSERT_EQ(0, occupantCount("TestUserOne"));
+ ASSERT_EQ(1, occupantCount("TestUserTwo"));
+ ASSERT_EQ(1, occupantCount("TestUserThree"));
+}
+
+TEST_F(MUCControllerTest, testHandleOccupantNicknameChangedRoster) {
+ const auto occupantCount = [&](const std::string & nick) {
+ auto roster = window_->getRosterModel();
+ const auto participants = roster->getGroup("Participants");
+ const auto displayedParticipants = participants->getDisplayedChildren();
+ int count = 0;
+ for (auto & p : displayedParticipants) {
+ if (p->getDisplayName() == nick) {
+ ++count;
+ }
+ }
+ return count;
+ };
+
+ muc_->insertOccupant(MUCOccupant("TestUserOne", MUCOccupant::Participant, MUCOccupant::Owner));
+ muc_->insertOccupant(MUCOccupant("TestUserTwo", MUCOccupant::Participant, MUCOccupant::Owner));
+ muc_->insertOccupant(MUCOccupant("TestUserThree", MUCOccupant::Participant, MUCOccupant::Owner));
+ ASSERT_EQ(1, occupantCount("TestUserOne"));
+ ASSERT_EQ(1, occupantCount("TestUserTwo"));
+ ASSERT_EQ(1, occupantCount("TestUserThree"));
+
+ muc_->onOccupantNicknameChanged("TestUserOne", "TestUserTwo");
+
+ ASSERT_EQ(0, occupantCount("TestUserOne"));
+ ASSERT_EQ(1, occupantCount("TestUserTwo"));
+ ASSERT_EQ(1, occupantCount("TestUserThree"));
+}
+
+TEST_F(MUCControllerTest, testHandleChangeSubjectRequest) {
+ std::string testStr("New Subject");
+ ASSERT_EQ(std::string(""), muc_->newSubjectSet_);
+ window_->onChangeSubjectRequest(testStr);
+ ASSERT_EQ(testStr, muc_->newSubjectSet_);
+}
+
+TEST_F(MUCControllerTest, testNonImpromptuMUCWindowTitle) {
+ ASSERT_EQ(muc_->getJID().getNode(), window_->name_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestCompleteMarking) {
+ setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red", true);
+
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), window_->markingValue_);
+ ASSERT_EQ(std::string("Black"), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string("Red"), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestCompleteMarkingWithExtraForm) {
+ auto formTypeField = std::make_shared<FormField>(FormField::Type::HiddenType, "http://jabber.org/protocol/muc#roominfo");
+ auto markingField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Test|Highest Possible Security");
+ auto markingForegroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Black");
+ auto markingBackgroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Red");
+ formTypeField->setName("FORM_TYPE");
+ markingField->setName("x-isode#roominfo_marking");
+ markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color");
+ markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color");
+
+ auto extraForm = std::make_shared<Form>(Form::Type::ResultType);
+ auto form = std::make_shared<Form>(Form::Type::ResultType);
+ form->addField(formTypeField);
+ form->addField(markingField);
+ form->addField(markingForegroundColorField);
+ form->addField(markingBackgroundColorField);
+
+ auto discoInfoRef = std::make_shared<DiscoInfo>();
+ discoInfoRef->addExtension(extraForm);
+ discoInfoRef->addExtension(form);
+
+ auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef);
+ iqChannel_->onIQReceived(infoResponse);
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), window_->markingValue_);
+ ASSERT_EQ(std::string("Black"), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string("Red"), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestNoColorsInMarking) {
+ auto formTypeField = std::make_shared<FormField>(FormField::Type::HiddenType, "http://jabber.org/protocol/muc#roominfo");
+ auto markingField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Test|Highest Possible Security");
+ auto markingForegroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, "");
+ auto markingBackgroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, "");
+ formTypeField->setName("FORM_TYPE");
+ markingField->setName("x-isode#roominfo_marking");
+ markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color");
+ markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color");
+
+ auto form = std::make_shared<Form>(Form::Type::ResultType);
+ form->addField(formTypeField);
+ form->addField(markingField);
+ form->addField(markingForegroundColorField);
+ form->addField(markingBackgroundColorField);
+
+ auto discoInfoRef = std::make_shared<DiscoInfo>();
+ discoInfoRef->addExtension(form);
+
+ auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef);
+ iqChannel_->onIQReceived(infoResponse);
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), window_->markingValue_);
+ ASSERT_EQ(std::string("Black"), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string("White"), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestEmptyMarking) {
+ setMUCSecurityMarking("", "", "", true);
+
+ ASSERT_EQ(std::string(""), window_->markingValue_);
+ ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestWithMarkingNoFormType) {
+ setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red", false);
+
+ ASSERT_EQ(std::string(""), window_->markingValue_);
+ ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestNoMarking) {
+ auto form = std::make_shared<Form>(Form::Type::ResultType);
+
+ auto discoInfoRef = std::make_shared<DiscoInfo>();
+ discoInfoRef->addExtension(form);
+
+ auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef);
+ iqChannel_->onIQReceived(infoResponse);
+ ASSERT_EQ(std::string(""), window_->markingValue_);
+ ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestNoForm) {
+ auto discoInfoRef = std::make_shared<DiscoInfo>();
+
+ auto infoResponse = IQ::createResult( self_, mucJID_, "test-id", discoInfoRef);
+ iqChannel_->onIQReceived(infoResponse);
+ ASSERT_EQ(std::string(""), window_->markingValue_);
+ ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingRequestError) {
+ auto errorPayload = std::make_shared<ErrorPayload>(ErrorPayload::Condition::NotAuthorized, ErrorPayload::Type::Auth);
+
+ auto infoResponse = IQ::createResult( self_, mucJID_, "test-id", errorPayload);
+ iqChannel_->onIQReceived(infoResponse);
+ ASSERT_EQ(std::string(""), window_->markingValue_);
+ ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_);
+ ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_);
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_NoRoomMarkingA) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true);
+ setMUCSecurityMarking("", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("Test|Highest Possible Security");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_NoRoomMarkingB) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true);
+ setMUCSecurityMarking("", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingA) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true);
+ setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("Test|Highest Possible Security");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ // Test the first message matching MUC marking. This message SHOULD have a marking
+
+ auto sentMessageEvent1 = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent1);
+
+ auto storedSecurityLabel1 = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel1 == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel1->getDisplayMarking());
+
+ // Test a consecutive message matching MUC marking. This message SHOULD NOT have a marking
+
+ auto sentMessageEvent2 = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent2);
+
+ auto storedSecurityLabel2 = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel2 == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string(""), storedSecurityLabel2->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingB) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true);
+ setMUCSecurityMarking("Test|Lower Security Marking", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("Test|Highest Possible Security");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingC) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true);
+ setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Unmarked"), storedSecurityLabel->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_NoRoomMarkingA) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false);
+ setMUCSecurityMarking("", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("Test|Highest Possible Security");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_NoRoomMarkingB) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false);
+ setMUCSecurityMarking("", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_WithRoomMarkingA) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false);
+ setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("Test|Highest Possible Security");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ // Test the first message matching MUC marking. This message SHOULD have a marking
+
+ auto sentMessageEvent1 = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent1);
+
+ auto storedSecurityLabel1 = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel1 == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel1->getDisplayMarking());
+
+ // Test a consecutive message matching MUC marking. This message SHOULD ALSO have a marking
+
+ auto sentMessageEvent2 = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent2);
+
+ auto storedSecurityLabel2 = window_->lastAddedMessageSecurityLabel_;
+
+ ASSERT_EQ(false, storedSecurityLabel2 == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel2->getDisplayMarking());
+}
+
+TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_WithRoomMarkingB) {
+ settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false);
+ setMUCSecurityMarking("", "Black", "Red");
+
+ auto messageLabel = std::make_shared<SecurityLabel>();
+ messageLabel->setDisplayMarking("");
+
+ auto sentMessage = createTestMessageWithoutSecurityLabel();
+ sentMessage->addPayload(messageLabel);
+
+ auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage);
+ controller_->handleIncomingMessage(sentMessageEvent);
+
+ auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_;
+ ASSERT_EQ(false, storedSecurityLabel == nullptr);
+ // This is the potentially altered security label that is displayed on the screen
+ ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking());
+}
diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
index 5fa264d..1d980d3 100644
--- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
+++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
@@ -1,27 +1,28 @@
/*
- * Copyright (c) 2011 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2011-2016 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
-#include "Swift/Controllers/UIInterfaces/ChatListWindow.h"
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
namespace Swift {
- class MockChatListWindow : public ChatListWindow {
- public:
- MockChatListWindow() {}
- virtual ~MockChatListWindow() {}
- void addMUCBookmark(const MUCBookmark& /*bookmark*/) {}
- void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {}
- void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {}
- void removeWhiteboardSession(const JID& /*jid*/) {}
- void setBookmarksEnabled(bool /*enabled*/) {}
- void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {}
- void setUnreadCount(int /*unread*/) {}
- void clearBookmarks() {}
- };
+ class MockChatListWindow : public ChatListWindow {
+ public:
+ MockChatListWindow() {}
+ virtual ~MockChatListWindow() {}
+ void addMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+ void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+ void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {}
+ void removeWhiteboardSession(const JID& /*jid*/) {}
+ void setBookmarksEnabled(bool /*enabled*/) {}
+ void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {}
+ void setUnreadCount(size_t /*unread*/) {}
+ void clearBookmarks() {}
+ void setOnline(bool /*isOnline*/) {}
+ };
}
diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index 3c7eb67..30c3c77 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -1,286 +1,393 @@
/*
- * Copyright (c) 2010 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <memory>
+
#include <boost/bind.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/smart_ptr/make_shared.hpp>
-#include <Swiften/Base/foreach.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Base/String.h>
+#include <Swiften/Disco/DiscoServiceWalker.h>
#include <Swiften/Disco/GetDiscoInfoRequest.h>
#include <Swiften/Disco/GetDiscoItemsRequest.h>
-#include <Swiften/Disco/DiscoServiceWalker.h>
-#include <Swiften/VCards/VCardManager.h>
#include <Swiften/Presence/PresenceOracle.h>
-#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/VCards/VCardManager.h>
+
#include <Swift/Controllers/ContactEditController.h>
-#include <Swift/Controllers/UIEvents/UIEventStream.h>
-#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
+#include <Swift/Controllers/ContactSuggester.h>
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swift/Controllers/Roster/RosterController.h>
#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/UserSearchWindow.h>
#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
-#include <Swift/Controllers/Roster/RosterController.h>
-#include <Swift/Controllers/ContactSuggester.h>
namespace Swift {
-UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController), contactSuggester_(contactSuggester), avatarManager_(avatarManager), presenceOracle_(presenceOracle) {
- uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1));
- vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2));
- avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1));
- presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1));
- window_ = NULL;
- discoWalker_ = NULL;
+
+static const std::string SEARCHED_DIRECTORIES = "searchedDirectories";
+
+UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle, ProfileSettingsProvider* settings) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController), contactSuggester_(contactSuggester), avatarManager_(avatarManager), presenceOracle_(presenceOracle), settings_(settings) {
+ uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1));
+ vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2));
+ avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1));
+ presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1));
+ window_ = nullptr;
+ discoWalker_ = nullptr;
+ loadSavedDirectories();
}
UserSearchController::~UserSearchController() {
- endDiscoWalker();
- delete discoWalker_;
- if (window_) {
- window_->onNameSuggestionRequested.disconnect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
- window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
- window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
- window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
- delete window_;
- }
- presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1));
- avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1));
- vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2));
- uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1));
+ endDiscoWalker();
+ delete discoWalker_;
+ if (window_) {
+ window_->onNameSuggestionRequested.disconnect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
+ window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
+ window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
+ window_->onContactSuggestionsRequested.disconnect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1));
+ window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
+ window_->onJIDAddRequested.disconnect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1));
+ window_->onJIDEditFieldChanged.disconnect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1));
+ delete window_;
+ }
+ presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1));
+ avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1));
+ vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2));
+ uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1));
}
UserSearchWindow* UserSearchController::getUserSearchWindow() {
- initializeUserWindow();
- assert(window_);
- return window_;
+ initializeUserWindow();
+ assert(window_);
+ return window_;
}
void UserSearchController::setCanInitiateImpromptuMUC(bool supportsImpromptu) {
- if (!window_) {
- initializeUserWindow();
- }
- window_->setCanStartImpromptuChats(supportsImpromptu);
+ if (!window_) {
+ initializeUserWindow();
+ }
+ if (window_) {
+ window_->setCanStartImpromptuChats(supportsImpromptu);
+ } // Else doesn't support search
}
-void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
- bool handle = false;
- boost::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = boost::shared_ptr<RequestAddUserDialogUIEvent>();
- RequestInviteToMUCUIEvent::ref inviteToMUCRequest = RequestInviteToMUCUIEvent::ref();
- switch (type_) {
- case AddContact:
- if ((addUserRequest = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) {
- handle = true;
- }
- break;
- case StartChat:
- if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) {
- handle = true;
- }
- break;
- case InviteToChat:
- if ((inviteToMUCRequest = boost::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) {
- handle = true;
- }
- break;
- }
- if (handle) {
- initializeUserWindow();
- window_->show();
- if (addUserRequest) {
- const std::string& name = addUserRequest->getPredefinedName();
- const JID& jid = addUserRequest->getPredefinedJID();
- if (!name.empty() && jid.isValid()) {
- window_->prepopulateJIDAndName(jid, name);
- }
- } else if (inviteToMUCRequest) {
- window_->setJIDs(inviteToMUCRequest->getInvites());
- window_->setRoomJID(inviteToMUCRequest->getRoom());
- }
- return;
- }
+void UserSearchController::handleUIEvent(std::shared_ptr<UIEvent> event) {
+ bool handle = false;
+ std::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = std::shared_ptr<RequestAddUserDialogUIEvent>();
+ auto inviteToMUCRequest = RequestInviteToMUCUIEvent::ref();
+ switch (type_) {
+ case Type::AddContact:
+ if ((addUserRequest = std::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) {
+ handle = true;
+ }
+ break;
+ case Type::StartChat:
+ if (std::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) {
+ handle = true;
+ }
+ break;
+ case Type::InviteToChat:
+ if ((inviteToMUCRequest = std::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) {
+ handle = true;
+ }
+ break;
+ }
+ if (handle) {
+ initializeUserWindow();
+ window_->show();
+ window_->addSavedServices(savedDirectories_);
+ if (addUserRequest) {
+ const std::string& name = addUserRequest->getPredefinedName();
+ const JID& jid = addUserRequest->getPredefinedJID();
+ if (!name.empty() && jid.isValid()) {
+ window_->prepopulateJIDAndName(jid, name);
+ }
+ }
+ else if (inviteToMUCRequest) {
+ window_->setCanSupplyDescription(!inviteToMUCRequest->isImpromptu());
+ window_->setJIDs(inviteToMUCRequest->getInvites());
+ window_->setOriginator(inviteToMUCRequest->getOriginator());
+ }
+ return;
+ }
}
void UserSearchController::handleFormRequested(const JID& service) {
- window_->setSearchError(false);
- window_->setServerSupportsSearch(true);
-
- //Abort a previous search if is active
- endDiscoWalker();
- delete discoWalker_;
- discoWalker_ = new DiscoServiceWalker(service, iqRouter_);
- discoWalker_->onServiceFound.connect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2));
- discoWalker_->onWalkComplete.connect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this));
- discoWalker_->beginWalk();
+ window_->setSearchError(false);
+ window_->setServerSupportsSearch(true);
+
+ //Abort a previous search if is active
+ endDiscoWalker();
+ delete discoWalker_;
+ discoWalker_ = new DiscoServiceWalker(service, iqRouter_);
+ discoWalker_->onServiceFound.connect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2));
+ discoWalker_->onWalkComplete.connect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this));
+ discoWalker_->beginWalk();
}
void UserSearchController::endDiscoWalker() {
- if (discoWalker_) {
- discoWalker_->endWalk();
- discoWalker_->onServiceFound.disconnect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2));
- discoWalker_->onWalkComplete.disconnect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this));
- }
+ if (discoWalker_) {
+ discoWalker_->endWalk();
+ discoWalker_->onServiceFound.disconnect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2));
+ discoWalker_->onWalkComplete.disconnect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this));
+ }
}
-void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) {
- //bool isUserDirectory = false;
- bool supports55 = false;
- foreach (DiscoInfo::Identity identity, info->getIdentities()) {
- if ((identity.getCategory() == "directory"
- && identity.getType() == "user")) {
- //isUserDirectory = true;
- }
- }
- std::vector<std::string> features = info->getFeatures();
- supports55 = std::find(features.begin(), features.end(), DiscoInfo::JabberSearchFeature) != features.end();
- if (/*isUserDirectory && */supports55) { //FIXME: once M-Link correctly advertises directoryness.
- /* Abort further searches.*/
- endDiscoWalker();
- boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Get, jid, boost::make_shared<SearchPayload>(), iqRouter_));
- searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleFormResponse, this, _1, _2));
- searchRequest->send();
- }
+void UserSearchController::handleDiscoServiceFound(const JID& jid, std::shared_ptr<DiscoInfo> info) {
+ //bool isUserDirectory = false;
+ bool supports55 = false;
+ // TODO: Cleanup code
+ for (const auto& identity : info->getIdentities()) {
+ if ((identity.getCategory() == "directory"
+ && identity.getType() == "user")) {
+ //isUserDirectory = true;
+ }
+ }
+ std::vector<std::string> features = info->getFeatures();
+ supports55 = std::find(features.begin(), features.end(), DiscoInfo::JabberSearchFeature) != features.end();
+ if (/*isUserDirectory && */supports55) { //FIXME: once M-Link correctly advertises directoryness.
+ /* Abort further searches.*/
+ endDiscoWalker();
+ std::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Get, jid, std::make_shared<SearchPayload>(), iqRouter_));
+ searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleFormResponse, this, _1, _2));
+ searchRequest->send();
+ }
}
-void UserSearchController::handleFormResponse(boost::shared_ptr<SearchPayload> fields, ErrorPayload::ref error) {
- if (error || !fields) {
- window_->setServerSupportsSearch(false);
- return;
- }
- window_->setSearchFields(fields);
+void UserSearchController::handleFormResponse(std::shared_ptr<SearchPayload> fields, ErrorPayload::ref error) {
+ if (error || !fields) {
+ window_->setServerSupportsSearch(false);
+ return;
+ }
+ window_->setSearchFields(fields);
}
-void UserSearchController::handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid) {
- boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Set, jid, fields, iqRouter_));
- searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleSearchResponse, this, _1, _2));
- searchRequest->send();
+void UserSearchController::handleSearch(std::shared_ptr<SearchPayload> fields, const JID& jid) {
+ addToSavedDirectories(jid);
+ std::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Set, jid, fields, iqRouter_));
+ searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleSearchResponse, this, _1, _2));
+ searchRequest->send();
}
-void UserSearchController::handleSearchResponse(boost::shared_ptr<SearchPayload> resultsPayload, ErrorPayload::ref error) {
- if (error || !resultsPayload) {
- window_->setSearchError(true);
- return;
- }
-
- std::vector<UserSearchResult> results;
-
- if (resultsPayload->getForm()) {
- window_->setResultsForm(resultsPayload->getForm());
- } else {
- foreach (SearchPayload::Item item, resultsPayload->getItems()) {
- JID jid(item.jid);
- std::map<std::string, std::string> fields;
- fields["first"] = item.first;
- fields["last"] = item.last;
- fields["nick"] = item.nick;
- fields["email"] = item.email;
- UserSearchResult result(jid, fields);
- results.push_back(result);
- }
- window_->setResults(results);
- }
+void UserSearchController::handleSearchResponse(std::shared_ptr<SearchPayload> resultsPayload, ErrorPayload::ref error) {
+ if (error || !resultsPayload) {
+ window_->setSearchError(true);
+ return;
+ }
+
+ std::vector<UserSearchResult> results;
+
+ if (resultsPayload->getForm()) {
+ window_->setResultsForm(resultsPayload->getForm());
+ } else {
+ for (auto&& item : resultsPayload->getItems()) {
+ JID jid(item.jid);
+ std::map<std::string, std::string> fields;
+ fields["first"] = item.first;
+ fields["last"] = item.last;
+ fields["nick"] = item.nick;
+ fields["email"] = item.email;
+ UserSearchResult result(jid, fields);
+ results.push_back(result);
+ }
+ window_->setResults(results);
+ }
}
void UserSearchController::handleNameSuggestionRequest(const JID &jid) {
- suggestionsJID_= jid;
- VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid);
- if (vcard) {
- handleVCardChanged(jid, vcard);
- }
+ suggestionsJID_= jid;
+ VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid);
+ if (vcard) {
+ handleVCardChanged(jid, vcard);
+ }
+}
+
+void UserSearchController::handleJIDEditingFinished(const JID& jid) {
+ if (jid.isValid()) {
+ if (rosterController_->getItem(jid)) {
+ window_->setWarning(QT_TRANSLATE_NOOP("", "This contact is already on your contact list."));
+ }
+ else if (jid.getNode().empty()) {
+ window_->setWarning(QT_TRANSLATE_NOOP("", "Part of the address you have entered is missing. An address has a structure of 'user@example.com'."));
+ }
+ else {
+ window_->setWarning(boost::optional<std::string>());
+ }
+ }
+ else {
+ window_->setWarning(QT_TRANSLATE_NOOP("", "The address you have entered is invalid."));
+ }
}
void UserSearchController::handleContactSuggestionsRequested(std::string text) {
- window_->setContactSuggestions(contactSuggester_->getSuggestions(text));
+ const std::vector<JID> existingJIDs = window_->getJIDs();
+ std::vector<Contact::ref> suggestions = contactSuggester_->getSuggestions(text, false);
+ /* do not suggest contacts that have already been added to the chat list */
+ std::vector<Contact::ref>::iterator i = suggestions.begin();
+ while (i != suggestions.end()) {
+ bool found = false;
+ for (const auto& jid : existingJIDs) {
+ if ((*i)->jid == jid) {
+ found = true;
+ break;
+ }
+ }
+
+ // remove contact suggestions which are already on the contact list in add-contact-mode
+ if (type_ == Type::AddContact) {
+ if (!found && !!rosterController_->getItem((*i)->jid)) {
+ found = true;
+ }
+ }
+
+ if (found) {
+ i = suggestions.erase(i);
+ } else {
+ i++;
+ }
+ }
+ window_->setContactSuggestions(suggestions);
}
void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) {
- if (jid == suggestionsJID_) {
- window_->setNameSuggestions(ContactEditController::nameSuggestionsFromVCard(vcard));
- suggestionsJID_ = JID();
- }
- handleJIDUpdateRequested(std::vector<JID>(1, jid));
+ if (jid == suggestionsJID_) {
+ window_->setNameSuggestions(ContactEditController::nameSuggestionsFromVCard(vcard));
+ suggestionsJID_ = JID();
+ }
+ handleJIDUpdateRequested(std::vector<JID>(1, jid));
}
void UserSearchController::handleAvatarChanged(const JID& jid) {
- handleJIDUpdateRequested(std::vector<JID>(1, jid));
+ handleJIDUpdateRequested(std::vector<JID>(1, jid));
}
void UserSearchController::handlePresenceChanged(Presence::ref presence) {
- handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare()));
+ handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare()));
}
void UserSearchController::handleJIDUpdateRequested(const std::vector<JID>& jids) {
- if (window_) {
- std::vector<Contact> updates;
- foreach(const JID& jid, jids) {
- updates.push_back(convertJIDtoContact(jid));
- }
- window_->updateContacts(updates);
- }
+ if (window_) {
+ std::vector<Contact::ref> updates;
+ for (const auto& jid : jids) {
+ updates.push_back(convertJIDtoContact(jid));
+ }
+ window_->updateContacts(updates);
+ }
+}
+
+void UserSearchController::handleJIDAddRequested(const std::vector<JID>& jids) {
+ std::vector<Contact::ref> contacts;
+ for (const auto& jid : jids) {
+ contacts.push_back(convertJIDtoContact(jid));
+ }
+ window_->addContacts(contacts);
}
-Contact UserSearchController::convertJIDtoContact(const JID& jid) {
- Contact contact;
- contact.jid = jid;
-
- // name lookup
- boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid);
- if (rosterItem && !rosterItem->getName().empty()) {
- contact.name = rosterItem->getName();
- } else {
- VCard::ref vcard = vcardManager_->getVCard(jid);
- if (vcard && !vcard->getFullName().empty()) {
- contact.name = vcard->getFullName();
- } else {
- contact.name = jid.toString();
- }
- }
-
- // presence lookup
- Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid);
- if (presence) {
- contact.statusType = presence->getShow();
- } else {
- contact.statusType = StatusShow::None;
- }
-
- // avatar lookup
- contact.avatarPath = avatarManager_->getAvatarPath(jid);
- return contact;
+Contact::ref UserSearchController::convertJIDtoContact(const JID& jid) {
+ Contact::ref contact = std::make_shared<Contact>();
+ contact->jid = jid;
+
+ // name lookup
+ boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid);
+ if (rosterItem && !rosterItem->getName().empty()) {
+ contact->name = rosterItem->getName();
+ } else {
+ VCard::ref vcard = vcardManager_->getVCard(jid);
+ if (vcard && !vcard->getFullName().empty()) {
+ contact->name = vcard->getFullName();
+ } else {
+ contact->name = jid.toString();
+ }
+ }
+
+ // presence lookup
+ Presence::ref presence = presenceOracle_->getAccountPresence(jid);
+ if (presence) {
+ contact->statusType = presence->getShow();
+ } else {
+ contact->statusType = StatusShow::None;
+ }
+
+ // avatar lookup
+ contact->avatarPath = avatarManager_->getAvatarPath(jid);
+ return contact;
}
void UserSearchController::handleDiscoWalkFinished() {
- window_->setServerSupportsSearch(false);
- endDiscoWalker();
+ window_->setServerSupportsSearch(false);
+ endDiscoWalker();
}
void UserSearchController::initializeUserWindow() {
- if (!window_) {
- UserSearchWindow::Type windowType = UserSearchWindow::AddContact;
- switch(type_) {
- case AddContact:
- windowType = UserSearchWindow::AddContact;
- break;
- case StartChat:
- windowType = UserSearchWindow::ChatToContact;
- break;
- case InviteToChat:
- windowType = UserSearchWindow::InviteToChat;
- break;
- }
-
- window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups());
- window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
- window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
- window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
- window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1));
- window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
- window_->setSelectedService(JID(jid_.getDomain()));
- window_->clear();
- }
+ if (!window_) {
+ auto windowType = UserSearchWindow::Type::AddContact;
+ switch(type_) {
+ case Type::AddContact:
+ windowType = UserSearchWindow::Type::AddContact;
+ break;
+ case Type::StartChat:
+ windowType = UserSearchWindow::Type::ChatToContact;
+ break;
+ case Type::InviteToChat:
+ windowType = UserSearchWindow::Type::InviteToChat;
+ break;
+ }
+
+ window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups());
+ if (!window_) {
+ // UI Doesn't support user search
+ return;
+ }
+ window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
+ window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
+ window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
+ window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1));
+ window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
+ window_->onJIDAddRequested.connect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1));
+ window_->onJIDEditFieldChanged.connect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1));
+ window_->setSelectedService(JID(jid_.getDomain()));
+ window_->clear();
+ }
+}
+
+void UserSearchController::loadSavedDirectories() {
+ savedDirectories_.clear();
+ for (auto&& stringItem : String::split(settings_->getStringSetting(SEARCHED_DIRECTORIES), '\n')) {
+ if(!stringItem.empty()) {
+ savedDirectories_.push_back(JID(stringItem));
+ }
+ }
+}
+
+void UserSearchController::addToSavedDirectories(const JID& jid) {
+ if (!jid.isValid()) {
+ return;
+ }
+
+ savedDirectories_.erase(std::remove(savedDirectories_.begin(), savedDirectories_.end(), jid), savedDirectories_.end());
+ savedDirectories_.insert(savedDirectories_.begin(), jid);
+
+ std::string collapsed;
+ int i = 0;
+ for (const auto& jidItem : savedDirectories_) {
+ if (i >= 15) {
+ break;
+ }
+ if (!collapsed.empty()) {
+ collapsed += "\n";
+ }
+ collapsed += jidItem.toString();
+ ++i;
+ }
+ settings_->storeString(SEARCHED_DIRECTORIES, collapsed);
+ window_->addSavedServices(savedDirectories_);
}
}
diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h
index 21cad5e..49bb63c 100644
--- a/Swift/Controllers/Chat/UserSearchController.h
+++ b/Swift/Controllers/Chat/UserSearchController.h
@@ -1,89 +1,99 @@
/*
- * Copyright (c) 2010 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * Copyright (c) 2010-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
*/
#pragma once
-#include <boost/shared_ptr.hpp>
#include <map>
+#include <memory>
+#include <string>
#include <vector>
-#include <Swiften/Base/boost_bsignals.h>
-#include <Swiften/Elements/SearchPayload.h>
-#include <string>
-#include <Swiften/JID/JID.h>
+#include <boost/signals2.hpp>
+
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/Elements/DiscoItems.h>
#include <Swiften/Elements/ErrorPayload.h>
-#include <Swiften/Elements/VCard.h>
#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/SearchPayload.h>
+#include <Swiften/Elements/VCard.h>
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/Contact.h>
namespace Swift {
- class UIEventStream;
- class UIEvent;
- class UserSearchWindow;
- class UserSearchWindowFactory;
- class IQRouter;
- class DiscoServiceWalker;
- class RosterController;
- class VCardManager;
- class ContactSuggester;
- class AvatarManager;
- class PresenceOracle;
- class Contact;
+ class UIEventStream;
+ class UIEvent;
+ class UserSearchWindow;
+ class UserSearchWindowFactory;
+ class IQRouter;
+ class DiscoServiceWalker;
+ class RosterController;
+ class VCardManager;
+ class ContactSuggester;
+ class AvatarManager;
+ class PresenceOracle;
+ class ProfileSettingsProvider;
+
+ class UserSearchResult {
+ public:
+ UserSearchResult(const JID& jid, const std::map<std::string, std::string>& fields) : jid_(jid), fields_(fields) {}
+ const JID& getJID() const {return jid_;}
+ const std::map<std::string, std::string>& getFields() const {return fields_;}
+ private:
+ JID jid_;
+ std::map<std::string, std::string> fields_;
+ };
- class UserSearchResult {
- public:
- UserSearchResult(const JID& jid, const std::map<std::string, std::string>& fields) : jid_(jid), fields_(fields) {}
- const JID& getJID() const {return jid_;}
- const std::map<std::string, std::string>& getFields() const {return fields_;}
- private:
- JID jid_;
- std::map<std::string, std::string> fields_;
- };
+ class UserSearchController {
+ public:
+ enum class Type {AddContact, StartChat, InviteToChat};
+ UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle, ProfileSettingsProvider* settings);
+ ~UserSearchController();
- class UserSearchController {
- public:
- enum Type {AddContact, StartChat, InviteToChat};
- UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle);
- ~UserSearchController();
+ UserSearchWindow* getUserSearchWindow();
+ void setCanInitiateImpromptuMUC(bool supportsImpromptu);
- UserSearchWindow* getUserSearchWindow();
- void setCanInitiateImpromptuMUC(bool supportsImpromptu);
+ private:
+ void handleUIEvent(std::shared_ptr<UIEvent> event);
+ void handleFormRequested(const JID& service);
+ void handleDiscoServiceFound(const JID& jid, std::shared_ptr<DiscoInfo> info);
+ void handleDiscoWalkFinished();
+ void handleFormResponse(std::shared_ptr<SearchPayload> items, ErrorPayload::ref error);
+ void handleSearch(std::shared_ptr<SearchPayload> fields, const JID& jid);
+ void handleSearchResponse(std::shared_ptr<SearchPayload> results, ErrorPayload::ref error);
+ void handleNameSuggestionRequest(const JID& jid);
+ void handleContactSuggestionsRequested(std::string text);
+ void handleVCardChanged(const JID& jid, VCard::ref vcard);
+ void handleAvatarChanged(const JID& jid);
+ void handlePresenceChanged(Presence::ref presence);
+ void handleJIDUpdateRequested(const std::vector<JID>& jids);
+ void handleJIDAddRequested(const std::vector<JID>& jids);
+ void handleJIDEditingFinished(const JID& jid);
+ Contact::ref convertJIDtoContact(const JID& jid);
+ void endDiscoWalker();
+ void initializeUserWindow();
- private:
- void handleUIEvent(boost::shared_ptr<UIEvent> event);
- void handleFormRequested(const JID& service);
- void handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info);
- void handleDiscoWalkFinished();
- void handleFormResponse(boost::shared_ptr<SearchPayload> items, ErrorPayload::ref error);
- void handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid);
- void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error);
- void handleNameSuggestionRequest(const JID& jid);
- void handleContactSuggestionsRequested(std::string text);
- void handleVCardChanged(const JID& jid, VCard::ref vcard);
- void handleAvatarChanged(const JID& jid);
- void handlePresenceChanged(Presence::ref presence);
- void handleJIDUpdateRequested(const std::vector<JID>& jids);
- Contact convertJIDtoContact(const JID& jid);
- void endDiscoWalker();
- void initializeUserWindow();
+ void loadSavedDirectories();
+ void addToSavedDirectories(const JID& jid);
- private:
- Type type_;
- JID jid_;
- JID suggestionsJID_;
- UIEventStream* uiEventStream_;
- VCardManager* vcardManager_;
- UserSearchWindowFactory* factory_;
- IQRouter* iqRouter_;
- RosterController* rosterController_;
- UserSearchWindow* window_;
- DiscoServiceWalker* discoWalker_;
- ContactSuggester* contactSuggester_;
- AvatarManager* avatarManager_;
- PresenceOracle* presenceOracle_;
- };
+ private:
+ Type type_;
+ JID jid_;
+ JID suggestionsJID_;
+ UIEventStream* uiEventStream_;
+ VCardManager* vcardManager_;
+ UserSearchWindowFactory* factory_;
+ IQRouter* iqRouter_;
+ RosterController* rosterController_;
+ UserSearchWindow* window_;
+ DiscoServiceWalker* discoWalker_;
+ ContactSuggester* contactSuggester_;
+ AvatarManager* avatarManager_;
+ PresenceOracle* presenceOracle_;
+ std::vector<JID> savedDirectories_;
+ ProfileSettingsProvider* settings_;
+ };
}