diff options
Diffstat (limited to 'Swift/Controllers/Chat')
19 files changed, 2304 insertions, 764 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index b6ca984..debd83f 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2019 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -52,8 +52,8 @@ 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, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, nickResolver, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { +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); @@ -86,7 +86,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); startMessage += "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); - chatWindow_->onContinuationsBroken.connect([this, 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)); @@ -190,7 +190,7 @@ void ChatController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> mess // 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() << std::endl; + 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()); @@ -320,6 +320,7 @@ 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("")); } } @@ -358,6 +359,7 @@ void ChatController::setOnline(bool online) { if (!online) { for (auto& stanzaIdPair : unackedStanzas_) { chatWindow_->setAckState(stanzaIdPair.second, ChatWindow::Failed); + failedStanzas_[stanzaIdPair.second] = stanzaIdPair.first; } unackedStanzas_.clear(); @@ -405,7 +407,7 @@ void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSes } void ChatController::handleFileTransferCancel(std::string id) { - SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl; + SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")"; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->cancel(); } else { @@ -414,7 +416,7 @@ void ChatController::handleFileTransferCancel(std::string id) { } void ChatController::handleFileTransferStart(std::string id, std::string description) { - SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; + SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")"; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->start(description); } else { @@ -423,7 +425,7 @@ void ChatController::handleFileTransferStart(std::string id, std::string descrip } void ChatController::handleFileTransferAccept(std::string id, std::string filename) { - SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")"; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->accept(filename); } else { @@ -432,7 +434,7 @@ void ChatController::handleFileTransferAccept(std::string id, std::string filena } void ChatController::handleSendFileRequest(std::string filename) { - SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; + SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")"; eventStream_->send(std::make_shared<SendFileUIEvent>(getToJID(), filename)); } @@ -579,14 +581,26 @@ bool ChatController::shouldIgnoreMessage(std::shared_ptr<Message> message) { } return false; } - + JID ChatController::messageCorrectionJID(const JID& fromJID) { return fromJID.toBare(); } ChatWindow* ChatController::detachChatWindow() { - chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + 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 d5011e4..447e263 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -9,7 +9,6 @@ #include <map> #include <string> -#include <Swiften/Base/Override.h> #include <Swiften/Base/Tristate.h> #include <Swift/Controllers/Chat/ChatControllerBase.h> @@ -31,42 +30,42 @@ namespace Swift { 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, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); - virtual ~ChatController(); - virtual void setToJID(const JID& jid) SWIFTEN_OVERRIDE; - virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; - virtual void setOnline(bool online) SWIFTEN_OVERRIDE; + 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*/) SWIFTEN_OVERRIDE; - virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE; - virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; + virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) override; + virtual ChatWindow* detachChatWindow() override; + virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) override; protected: - virtual void cancelReplaces() SWIFTEN_OVERRIDE; - virtual JID getBaseJID() SWIFTEN_OVERRIDE; - virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; - virtual bool shouldIgnoreMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; - virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE; + 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) SWIFTEN_OVERRIDE; - virtual void postSendMessage(const std::string &body, std::shared_ptr<Stanza> sentStanza) SWIFTEN_OVERRIDE; - virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) SWIFTEN_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) SWIFTEN_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) SWIFTEN_OVERRIDE; - virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; - virtual void preSendMessageRequest(std::shared_ptr<Message>) SWIFTEN_OVERRIDE; - virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const SWIFTEN_OVERRIDE; + 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() SWIFTEN_OVERRIDE { lastWasPresence_ = false; } + virtual void dayTicked() override { lastWasPresence_ = false; } void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); - virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; + virtual void handleBareJIDCapsChanged(const JID& jid) override; void handleFileTransferCancel(std::string /* id */); void handleFileTransferStart(std::string /* id */, std::string /* description */); @@ -97,15 +96,12 @@ namespace Swift { std::string myLastMessageUIID_; bool isInMUC_; std::string lastStatusChangeString_; - std::map<std::shared_ptr<Stanza>, std::string> unackedStanzas_; - std::map<std::string, std::string> requestedReceipts_; StatusShow::Type lastShownStatus_; Tristate contactSupportsReceipts_; bool receivingPresenceFromUs_ = false; bool userWantsReceipts_; std::map<std::string, FileTransferController*> ftControllers; - SettingsProvider* settings_; std::string lastWbID_; std::string lastHandledMessageID_; @@ -113,9 +109,9 @@ namespace Swift { 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 0fc735a..3805084 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -20,11 +20,14 @@ #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/Queries/Requests/GetSecurityLabelsCatalogRequest.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> @@ -38,11 +41,12 @@ namespace Swift { -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) : 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) { +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()); @@ -58,6 +62,10 @@ void ChatControllerBase::handleContinuationsBroken() { } ChatWindow* ChatControllerBase::detachChatWindow() { + 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; @@ -110,8 +118,8 @@ void ChatControllerBase::handleAllMessagesRead() { } } -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) { @@ -185,16 +193,44 @@ ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std } void ChatControllerBase::updateMessageCount() { - chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); + 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, label, pathToString(avatarPath), time); + return chatWindow_->addAction(chatMessage, senderName, senderIsSelf, displayedLabel, pathToString(avatarPath), time); } else { - return chatWindow_->addMessage(chatMessage, senderName, senderIsSelf, label, pathToString(avatarPath), time); + return chatWindow_->addMessage(chatMessage, senderName, senderIsSelf, displayedLabel, pathToString(avatarPath), time); } } @@ -284,7 +320,9 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes addMessageHandleIncomingMessage(from, chatMessage, message->getID(), senderIsSelf, label, timeStamp); } +#ifdef SWIFT_EXPERIMENTAL_HISTORY logMessage(body, from, selfJID_, timeStamp, true); +#endif } chatWindow_->show(); updateMessageCount(); @@ -339,7 +377,7 @@ void ChatControllerBase::handleMUCInvitation(Message::ref message) { 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, false, true)); + 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); @@ -362,4 +400,22 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { 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 88cb95c..92c6175 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -28,6 +28,7 @@ #include <Swift/Controllers/Highlighting/HighlightManager.h> #include <Swift/Controllers/HistoryController.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> @@ -36,11 +37,12 @@ namespace Swift { class AutoAcceptMUCInviteDecider; class AvatarManager; class ChatMessageParser; + class Chattables; class ChatWindowFactory; class EntityCapsProvider; class EventController; - class HighlightManager; class Highlighter; + class HighlightManager; class IQRouter; class NickResolver; class StanzaChannel; @@ -71,7 +73,7 @@ namespace Swift { boost::signals2::signal<void (const std::string& /*activity*/)> onActivity; boost::signals2::signal<void ()> onUnreadCountChanged; boost::signals2::signal<void ()> onWindowClosed; - int getUnreadCount(); + size_t getUnreadCount(); const JID& getToJID() {return toJID_;} void handleCapsChanged(const JID& jid); void setCanStartImpromptuChats(bool supportsImpromptu); @@ -79,7 +81,7 @@ namespace Swift { 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); + 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); /** * Pass the Message appended, and the stanza used to send it. @@ -113,6 +115,7 @@ namespace Swift { * 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); private: IDGenerator idGenerator_; @@ -150,5 +153,13 @@ namespace Swift { 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 1a822a1..31be451 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.cpp +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -132,7 +132,7 @@ namespace Swift { } } - catch (std::runtime_error) { + 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); } @@ -185,7 +185,7 @@ namespace Swift { resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); } } - catch (std::runtime_error) { + 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); } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index b7087fd..193af7f 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2019 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -9,14 +9,7 @@ #include <memory> #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 <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Log.h> @@ -41,8 +34,10 @@ #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/ChatListWindowChatBoostSerialize.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/Chat/MUCSearchController.h> @@ -67,48 +62,14 @@ #include <Swift/Controllers/WhiteboardManager.h> #include <Swift/Controllers/XMPPEvents/EventController.h> -BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 2) - -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; - if (version > 0) { - ar & chat.password; - } - if (version > 1) { - ar & chat.inviteesNames; - } - } -} -} - 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, @@ -120,7 +81,9 @@ ChatsManager::ChatsManager( PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, +#ifndef NOT_YET ChatListWindowFactory* chatListWindowFactory, +#endif bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, @@ -137,7 +100,8 @@ ChatsManager::ChatsManager( HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, - VCardManager* vcardManager) : + VCardManager* vcardManager, + Chattables& chattables) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), @@ -153,7 +117,8 @@ ChatsManager::ChatsManager( highlightManager_(highlightManager), emoticons_(emoticons), clientBlockListManager_(clientBlockListManager), - vcardManager_(vcardManager) { + vcardManager_(vcardManager), + chattables_(chattables) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -169,12 +134,12 @@ ChatsManager::ChatsManager( 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)); @@ -188,24 +153,29 @@ ChatsManager::ChatsManager( 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)); + 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; } @@ -217,6 +187,7 @@ ChatsManager::~ChatsManager() { delete autoAcceptMUCInviteDecider_; } +#ifndef NOT_YET void ChatsManager::saveRecents() { std::stringstream serializeStream; boost::archive::text_oarchive oa(serializeStream); @@ -249,6 +220,7 @@ void ChatsManager::handleClearRecentsRequested() { saveRecents(); handleUnreadCountChanged(nullptr); } +#endif void ChatsManager::handleJIDAddedToRoster(const JID &jid) { updatePresenceReceivingStateOnChatController(jid); @@ -283,6 +255,7 @@ void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) } } +#ifndef NOT_YET ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const { ChatListWindow::Chat fixedChat = chat; if (fixedChat.isMUC) { @@ -338,10 +311,10 @@ void ChatsManager::loadRecents() { 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; + 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); @@ -350,6 +323,7 @@ void ChatsManager::loadRecents() { } handleUnreadCountChanged(nullptr); } +#endif void ChatsManager::setupBookmarks() { if (!mucBookmarkManager_) { @@ -358,33 +332,47 @@ void ChatsManager::setupBookmarks() { 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() { +#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 ); + 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); } - chatListWindow_->addMUCBookmark(bookmark); } void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) { +#ifndef NOT_YET chatListWindow_->removeMUCBookmark(bookmark); +#endif } +#ifndef NOT_YET ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage) { - int unreadCount = 0; + size_t unreadCount = 0; if (mucRegistry_->isMUC(jid)) { MUCController* controller = mucControllers_[jid.toBare()]; StatusShow::Type type = StatusShow::None; @@ -427,8 +415,10 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const 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) { +#ifndef NOT_YET const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC; ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage); /* FIXME: handle nick changes */ @@ -444,15 +434,20 @@ void ChatsManager::handleChatActivity(const JID& jid, const std::string& activit 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; + size_t unreadTotal = 0; bool controllerIsMUC = dynamic_cast<MUCController*>(controller); bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare()); for (ChatListWindow::Chat& chatItem : recentChats_) { @@ -484,7 +479,9 @@ boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const Cha 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_; @@ -522,23 +519,38 @@ void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) { } 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) { +#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) { @@ -615,7 +627,7 @@ void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) { invitees_[roomJID].insert(jid); } // join muc - MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true); + 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(); } @@ -625,7 +637,7 @@ void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) { 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->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu()); + 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)) { @@ -639,6 +651,7 @@ void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) { } } +#ifndef NOT_YET void ChatsManager::markAllRecentsOffline() { for (ChatListWindow::Chat& chat : recentChats_) { chat.setStatusType(StatusShow::None); @@ -646,6 +659,7 @@ void ChatsManager::markAllRecentsOffline() { chatListWindow_->setRecents(recentChats_); } +#endif void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) { JID reuseChatInvite = chatController->getToJID(); @@ -659,7 +673,7 @@ void ChatsManager::handleTransformChatToMUC(ChatController* chatController, Chat JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_); // join muc - MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow); + 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))); } @@ -668,7 +682,7 @@ void ChatsManager::handleTransformChatToMUC(ChatController* chatController, Chat */ 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()); @@ -677,8 +691,9 @@ void ChatsManager::handlePresenceChange(std::shared_ptr<Presence> newPresence) { 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; @@ -693,21 +708,25 @@ void ChatsManager::setAvatarManager(AvatarManager* 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) { +#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(std::shared_ptr<DiscoInfo> info) { @@ -744,10 +763,11 @@ void ChatsManager::setOnline(bool enabled) { 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) { @@ -771,12 +791,14 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) ChatController* ChatsManager::createNewChatController(const JID& contact) { 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) */ - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, timerFactory_, eventController_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_); + 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()); @@ -825,20 +847,8 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) { 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 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; - 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()) { if (stanzaChannel_->isAvailable()) { @@ -860,7 +870,11 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti 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_); + 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 @@ -874,7 +888,9 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); controller->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2)); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); - controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); +#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 @@ -884,12 +900,30 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti } 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; } @@ -902,7 +936,7 @@ void ChatsManager::handleUserNicknameChanged(MUCController* mucController, const JID oldMUCChatJID = mucController->getToJID().withResource(oldNickname); JID newMUCChatJID = mucController->getToJID().withResource(newNickname); - SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname << std::endl; + SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname; // get current chat controller ChatController *chatController = getChatControllerIfExists(oldMUCChatJID); @@ -949,7 +983,7 @@ void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> incomingMessag controller->handleIncomingOwnMessage(forwardedMessage); } else { - SWIFT_LOG(error) << "Carbons message ignored." << std::endl; + SWIFT_LOG(error) << "Carbons message ignored."; } return; } @@ -999,11 +1033,11 @@ void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> incomingMessag ChatWindow* window = controller->detachChatWindow(); chatControllers_.erase(fromJID); delete controller; - handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window); + 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, false, true); + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, true); return; } } @@ -1062,7 +1096,7 @@ void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWin 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; @@ -1075,13 +1109,30 @@ void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { 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, false, isImpromptu)); + 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" @@ -1090,7 +1141,7 @@ void ChatsManager::handleLocalServiceFound(const JID& service, std::shared_ptr<D && identity.getType() == "text")) { localMUCServiceJID_ = service; localMUCServiceFinderWalker_->endWalk(); - SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl; + SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_; break; } } @@ -1107,17 +1158,21 @@ void ChatsManager::handleLocalServiceWalkFinished() { onImpromptuMUCServiceDiscovered(impromptuMUCSupported); } +#ifndef NOT_YET std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const { return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); } +#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; diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 6004347..e120831 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -27,49 +27,52 @@ #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> namespace Swift { - class EventController; + class AutoAcceptMUCInviteDecider; + class AvatarManager; class ChatController; class ChatControllerBase; - class MUCController; - class MUCManager; + 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 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 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); + 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; @@ -86,10 +89,12 @@ namespace Swift { }; 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 addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = nullptr); + 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); @@ -105,18 +110,25 @@ namespace Swift { 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&); @@ -126,12 +138,13 @@ namespace Swift { 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); @@ -139,6 +152,9 @@ namespace Swift { 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_; @@ -154,9 +170,10 @@ namespace Swift { AvatarManager* avatarManager_; PresenceSender* presenceSender_; UIEventStream* uiEventStream_; - MUCBookmarkManager* mucBookmarkManager_; std::shared_ptr<DiscoInfo> serverDiscoInfo_; +#ifndef NOT_YET ChatListWindow* chatListWindow_; +#endif JoinMUCWindow* joinMUCWindow_; boost::signals2::scoped_connection uiEventConnection_; bool useDelayForLatency_; @@ -165,7 +182,9 @@ namespace Swift { EntityCapsProvider* entityCapsProvider_; MUCManager* mucManager; MUCSearchController* mucSearchController_; +#ifndef NOT_YET std::list<ChatListWindow::Chat> recentChats_; +#endif ProfileSettingsProvider* profileSettings_; FileTransferOverview* ftOverview_; XMPPRoster* roster_; @@ -182,6 +201,7 @@ namespace Swift { 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 d10e6d4..30d4933 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -23,6 +23,7 @@ #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> @@ -35,6 +36,7 @@ #include <SwifTools/TabComplete.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> @@ -59,17 +61,6 @@ namespace Swift { -class MUCBookmarkPredicate { - public: - MUCBookmarkPredicate(const JID& mucJID) : roomJID_(mucJID) { } - bool operator()(const MUCBookmark& operand) { - return operand.getRoom() == roomJID_; - } - - private: - JID roomJID_; -}; - /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ @@ -98,8 +89,10 @@ MUCController::MUCController ( bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, - MUCBookmarkManager* mucBookmarkManager) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), nickResolver, presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) { + 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; @@ -112,7 +105,7 @@ MUCController::MUCController ( isInitialJoin_ = true; chatWindowTitle_ = ""; - roster_ = std::unique_ptr<Roster>(new Roster(false, true)); + roster_ = std::make_unique<Roster>(false, true); rosterVCardProvider_ = new RosterVCardProvider(roster_.get(), vcardManager, JID::WithResource); completer_ = new TabComplete(); chatWindow_->setRosterModel(roster_.get()); @@ -171,14 +164,7 @@ MUCController::MUCController ( mucBookmarkManagerBookmarkAddedConnection_ = (mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&MUCController::handleMUCBookmarkAdded, this, _1))); mucBookmarkManagerBookmarkRemovedConnection_ = (mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&MUCController::handleMUCBookmarkRemoved, this, _1))); - std::vector<MUCBookmark> mucBookmarks = mucBookmarkManager_->getBookmarks(); - std::vector<MUCBookmark>::iterator bookmarkIterator = std::find_if(mucBookmarks.begin(), mucBookmarks.end(), MUCBookmarkPredicate(muc->getJID())); - if (bookmarkIterator != mucBookmarks.end()) { - updateChatWindowBookmarkStatus(*bookmarkIterator); - } - else { - updateChatWindowBookmarkStatus(boost::optional<MUCBookmark>()); - } + updateChatWindowBookmarkStatus(mucBookmarkManager_->lookupBookmark(muc->getJID())); } MUCController::~MUCController() { @@ -273,6 +259,9 @@ void MUCController::rejoin() { lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_); } #endif + + requestSecurityMarking(); + if (lastActivity_ == boost::posix_time::not_a_date_time) { muc_->joinAs(nick_); } @@ -492,6 +481,7 @@ void MUCController::setAvailableRoomActions(const MUCOccupant::Affiliation& affi if (role <= MUCOccupant::Visitor) { actions.push_back(ChatWindow::Invite); } + actions.push_back(ChatWindow::Leave); chatWindow_->setAvailableRoomActions(actions); } @@ -1243,4 +1233,63 @@ void MUCController::setChatWindowTitle(const std::string& 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 258eaaf..bd1148f 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -14,7 +14,6 @@ #include <boost/signals2.hpp> #include <boost/signals2/connection.hpp> -#include <Swiften/Base/Override.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/MUCOccupant.h> #include <Swiften/Elements/Message.h> @@ -54,14 +53,14 @@ namespace Swift { 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); - virtual ~MUCController(); + 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) SWIFTEN_OVERRIDE; - virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; + 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); @@ -76,18 +75,18 @@ namespace Swift { void setChatWindowTitle(const std::string& title); protected: - virtual void preSendMessageRequest(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; - virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; - virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const SWIFTEN_OVERRIDE; - virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) SWIFTEN_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) SWIFTEN_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) SWIFTEN_OVERRIDE; - virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; - virtual void cancelReplaces() SWIFTEN_OVERRIDE; - virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; - virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE; + 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); @@ -116,9 +115,9 @@ namespace Swift { bool messageTargetsMe(std::shared_ptr<Message> message); void updateJoinParts(); bool shouldUpdateJoinParts(); - virtual void dayTicked() SWIFTEN_OVERRIDE { clearPresenceQueue(); } + virtual void dayTicked() override { clearPresenceQueue(); } void processUserPart(); - virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; + virtual void handleBareJIDCapsChanged(const JID& jid) override; void handleConfigureRequest(Form::ref); void handleConfigurationFailed(ErrorPayload::ref); void handleConfigurationFormReceived(Form::ref); @@ -149,6 +148,10 @@ namespace Swift { 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_; @@ -190,4 +193,3 @@ namespace Swift { std::string chatWindowTitle_; }; } - diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index 5db917a..0b54d25 100644 --- a/Swift/Controllers/Chat/MUCSearchController.cpp +++ b/Swift/Controllers/Chat/MUCSearchController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2019 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -92,7 +92,7 @@ void MUCSearchController::handleSearchService(const JID& jid) { delete walker_; } - SWIFT_LOG(debug) << "Starting walking MUC services" << std::endl; + SWIFT_LOG(debug) << "Starting walking MUC services"; itemsInProgress_ = 0; walker_ = new DiscoServiceWalker(jid, iqRouter_); walker_->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); @@ -113,14 +113,14 @@ void MUCSearchController::handleDiscoServiceFound(const JID& jid, std::shared_pt } } if (isMUC) { - SWIFT_LOG(debug) << "MUC Service found: " << jid << std::endl; + 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)" << std::endl; + 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(); @@ -132,7 +132,7 @@ void MUCSearchController::handleDiscoServiceFound(const JID& jid, std::shared_pt } void MUCSearchController::handleDiscoWalkFinished() { - SWIFT_LOG(debug) << "MUC Walk finished" << std::endl; + SWIFT_LOG(debug) << "MUC Walk finished"; updateInProgressness(); } @@ -144,7 +144,7 @@ void MUCSearchController::removeService(const JID& jid) { 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)" << std::endl; + SWIFT_LOG(debug) << "Items received for " << jid << " (" << itemsInProgress_ << " item requests in progress)"; updateInProgressness(); if (error) { handleDiscoError(jid, error); 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/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 8fc26b5..954dd2f 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -32,9 +32,13 @@ #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> @@ -47,6 +51,7 @@ #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/EventNotifier.h> @@ -54,6 +59,7 @@ #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Settings/DummySettingsProvider.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> @@ -71,7 +77,6 @@ #include <SwifTools/Notifier/Notifier.h> #include <Swift/QtUI/QtSwiftUtil.h> -#include <Swiften/MUC/UnitTest/MockMUC.h> using namespace Swift; @@ -104,6 +109,16 @@ class DummyNotifier : public Notifier { 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); @@ -155,12 +170,23 @@ class ChatsManagerTest : public CppUnit::TestFixture { 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(); - notifier_ = std::unique_ptr<DummyNotifier>(new DummyNotifier()); + notifier_ = std::make_unique<DummyNotifier>(); jid_ = JID("test@test.com/resource"); stanzaChannel_ = new DummyStanzaChannel(); iqRouter_ = new IQRouter(stanzaChannel_); @@ -200,7 +226,8 @@ public: mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); clientBlockListManager_ = new ClientBlockListManager(iqRouter_); timerFactory_ = new DummyTimerFactory(); - manager_ = new ChatsManager(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_ = 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_); } @@ -776,7 +803,7 @@ public: // send message to participantA auto messageBody = std::string("message body to send"); window->onSendMessageRequest(messageBody, false); - auto sendMessageStanza = stanzaChannel_->getStanzaAtIndex<Message>(2); + auto sendMessageStanza = stanzaChannel_->getStanzaAtIndex<Message>(3); CPPUNIT_ASSERT_EQUAL(messageBody, *sendMessageStanza->getBody()); // receive reply with error @@ -1228,6 +1255,11 @@ public: 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.")); } } @@ -1264,6 +1296,11 @@ public: 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 @@ -1280,23 +1317,29 @@ public: 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>(); @@ -1306,19 +1349,24 @@ public: 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.")); } } @@ -1537,7 +1585,11 @@ public: uiEventStream_->send(std::make_shared<CreateImpromptuMUCUIEvent>(jids, mucJID, "")); CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle()); - auto mucJoinPresence = std::dynamic_pointer_cast<Presence>(stanzaChannel_->sentStanzas[2]); + // 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 @@ -1598,6 +1650,191 @@ public: 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: std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { std::shared_ptr<Message> message = std::make_shared<Message>(); @@ -1622,7 +1859,7 @@ private: private: JID jid_; std::unique_ptr<DummyNotifier> notifier_; - ChatsManager* manager_; + ExtendedChatsManager* manager_; DummyStanzaChannel* stanzaChannel_; IQRouter* iqRouter_; EventController* eventController_; @@ -1660,8 +1897,8 @@ private: 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 1f69f4f..5339c7b 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,13 +1,14 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <boost/algorithm/string.hpp> -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> +#include <memory> + +#include <gtest/gtest.h> #include <hippomocks.h> #include <Swiften/Avatars/NullAvatarManager.h> @@ -32,10 +33,12 @@ #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> @@ -44,587 +47,918 @@ #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(testRoleAffiliationStates); - CPPUNIT_TEST(testSubjectChangeCorrect); - CPPUNIT_TEST(testSubjectChangeIncorrectA); - CPPUNIT_TEST(testSubjectChangeIncorrectB); - CPPUNIT_TEST(testSubjectChangeIncorrectC); - CPPUNIT_TEST(testHandleOccupantNicknameChanged); - CPPUNIT_TEST(testHandleOccupantNicknameChangedRoster); - CPPUNIT_TEST(testHandleChangeSubjectRequest); - - CPPUNIT_TEST(testNonImpromptuMUCWindowTitle); - - CPPUNIT_TEST_SUITE_END(); - -public: - 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_); - 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_); - } +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 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 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); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(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"); + 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)); + } + } - 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 testAddressedToSelf() { - finishJoin(); - Message::ref message(new Message()); + 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()); + } + } + } - 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()); + 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); - 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()); + 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); + } - 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()); + 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); - 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))); + markingField->setName("x-isode#roominfo_marking"); + markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color"); + markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color"); - // The last message is ignored because self-mention highlights are matched case - // sensitive against the nickname. - CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size()); + form->addField(markingField); + form->addField(markingForegroundColorField); + form->addField(markingBackgroundColorField); - 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)3, eventController_->getEvents().size()); + auto discoInfoRef = std::make_shared<DiscoInfo>(); + discoInfoRef->addExtension(form); - 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)3, eventController_->getEvents().size()); + 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_; +}; + +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); } - 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()); + 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(); - void testAddressedToSelfBySelf() { - finishJoin(); - Message::ref message(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); - message->setBody("Hi there " + nick_); + { + Message::ref message = std::make_shared<Message>(); message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); - } + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID("3FB99C56-7C92-4755-91B0-9C0098BC7AE0"); + message->setSubject("New Room Subject"); - void 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); - 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().get()); - CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>()); + 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); } +} - void 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); - 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().get()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - } +/* + * Test that message stanzas with subject element and non-empty body element do not cause a subject change. + */ +TEST_F(MUCControllerTest, testSubjectChangeIncorrectA) { + joinCompleted(); - void 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); - 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().get()); - CPPUNIT_ASSERT_EQUAL(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); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + { + 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); } +} - 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); - } +/* + * 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); } +} - 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); +/* + * 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_); +} - 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)); - } +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_NoRoomMarkingA) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("", "Black", "Red"); - 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)); - } + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); - JID jidFromOccupant(const MUCOccupant& occupant) { - return JID(mucJID_.toString()+"/"+occupant.getNick()); - } + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); - void testRoleAffiliationStates() { + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); - 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))); + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; - /* populate the MUC with fake users */ - for (auto&& occupant : occupants) { - muc_->insertOccupant(occupant.second); - } + 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()); +} - 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()); - CPPUNIT_ASSERT(occupant != occupants.end()); - const JID jid = jidFromOccupant(occupant->second); - /* change the affiliation, leave the role in place */ - muc_->changeAffiliation(jid, alteration.getAffiliation()); - occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); - testRoleAffiliationStatesVerify(occupants); - /* change the role, leave the affiliation in place */ - muc_->changeOccupantRole(jid, alteration.getRole()); - occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); - testRoleAffiliationStatesVerify(occupants); - } - } +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_NoRoomMarkingB) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("", "Black", "Red"); - void testSubjectChangeCorrect() { - joinCompleted(); + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking(""); - { - 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"); + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); - controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("The room subject is now: New Room Subject"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); - /* - * Test that message stanzas with subject element and non-empty body element do not cause a subject change. - */ - void testSubjectChangeIncorrectA() { - joinCompleted(); + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; - { - 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."); + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking()); +} - controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingA) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red"); - /* - * Test that message stanzas with subject element and thread element do not cause a subject change. - */ - void testSubjectChangeIncorrectB() { - joinCompleted(); + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); - { - 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.")); + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); - controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } + // Test the first message matching MUC marking. This message SHOULD have a marking - /* - * Test that message stanzas with subject element and empty body element do not cause a subject change. - */ - void testSubjectChangeIncorrectC() { - joinCompleted(); + auto sentMessageEvent1 = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent1); - { - 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(""); + auto storedSecurityLabel1 = window_->lastAddedMessageSecurityLabel_; - controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } + 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()); - void testHandleOccupantNicknameChanged() { - const auto occupantCount = [&](const std::string & nick) { - auto roster = window_->getRosterModel(); - CPPUNIT_ASSERT(roster != nullptr); - const auto currentOccupantsJIDs = roster->getJIDs(); - int count = 0; - for (auto & p : currentOccupantsJIDs) { - if (p.getResource() == nick) { - ++count; - } - } - return count; - }; + // Test a consecutive message matching MUC marking. This message SHOULD NOT have a marking - muc_->insertOccupant(MUCOccupant("TestUserOne", MUCOccupant::Participant, MUCOccupant::Owner)); - muc_->insertOccupant(MUCOccupant("TestUserTwo", MUCOccupant::Participant, MUCOccupant::Owner)); - muc_->insertOccupant(MUCOccupant("TestUserThree", MUCOccupant::Participant, MUCOccupant::Owner)); + auto sentMessageEvent2 = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent2); - muc_->onOccupantNicknameChanged("TestUserOne", "TestUserTwo"); + auto storedSecurityLabel2 = window_->lastAddedMessageSecurityLabel_; - CPPUNIT_ASSERT_EQUAL(0, occupantCount("TestUserOne")); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserTwo")); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserThree")); - } + ASSERT_EQ(false, storedSecurityLabel2 == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string(""), storedSecurityLabel2->getDisplayMarking()); +} - void testHandleOccupantNicknameChangedRoster() { - const auto occupantCount = [&](const std::string & nick) { - auto roster = window_->getRosterModel(); - CPPUNIT_ASSERT(roster != nullptr); - const auto participants = roster->getGroup("Participants"); - CPPUNIT_ASSERT(participants != nullptr); - const auto displayedParticipants = participants->getDisplayedChildren(); - int count = 0; - for (auto & p : displayedParticipants) { - if (p->getDisplayName() == nick) { - ++count; - } - } - return count; - }; +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingB) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("Test|Lower Security Marking", "Black", "Red"); - muc_->insertOccupant(MUCOccupant("TestUserOne", MUCOccupant::Participant, MUCOccupant::Owner)); - muc_->insertOccupant(MUCOccupant("TestUserTwo", MUCOccupant::Participant, MUCOccupant::Owner)); - muc_->insertOccupant(MUCOccupant("TestUserThree", MUCOccupant::Participant, MUCOccupant::Owner)); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserOne")); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserTwo")); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserThree")); + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); - muc_->onOccupantNicknameChanged("TestUserOne", "TestUserTwo"); + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); - CPPUNIT_ASSERT_EQUAL(0, occupantCount("TestUserOne")); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserTwo")); - CPPUNIT_ASSERT_EQUAL(1, occupantCount("TestUserThree")); - } + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); - 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); - CPPUNIT_ASSERT(child); - for (auto childItem : child->getChildren()) { - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); - CPPUNIT_ASSERT(item); - std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); - CPPUNIT_ASSERT(occupant != occupants.end()); - CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); - CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); - } - } - } + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; - void testHandleChangeSubjectRequest() { - std::string testStr("New Subject"); - CPPUNIT_ASSERT_EQUAL(std::string(""), muc_->newSubjectSet_); - window_->onChangeSubjectRequest(testStr); - CPPUNIT_ASSERT_EQUAL(testStr, muc_->newSubjectSet_); - } + 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()); +} - void testNonImpromptuMUCWindowTitle() { - CPPUNIT_ASSERT_EQUAL(muc_->getJID().getNode(), window_->name_); - } +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingC) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red"); -private: - 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_; -}; + 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); -CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); + 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 395b050..1d980d3 100644 --- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -20,7 +20,7 @@ namespace Swift { void removeWhiteboardSession(const JID& /*jid*/) {} void setBookmarksEnabled(bool /*enabled*/) {} void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} - void setUnreadCount(int /*unread*/) {} + void setUnreadCount(size_t /*unread*/) {} void clearBookmarks() {} void setOnline(bool /*isOnline*/) {} }; |