From 645a7a47c8105e7cad3fecd7bb66c8c16757eafe Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Thu, 22 Jan 2015 23:24:16 +0100
Subject: Show a warning notice when trying to enter a blocked room

When the user tries to enter a blocked room, we now show a warning
notice and describing how the room can be unblocked. Swift will not
send the joining presence when trying to enter a blocked room.

Test-Information:

Tested on Mac OS X 10.9.5 against a popular open source server and its
MUC and Blocking Command implementation.

Change-Id: I875db056f21f97845c5a9a43167b0f2a16bdaa36

diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index f69ce6f..fe098de 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2014 Isode Limited.
+ * Copyright (c) 2010-2015 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -811,7 +811,7 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti
 			chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
 		}
 		boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */
-		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_);
+		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_);
 		if (chatWindowFactoryAdapter) {
 			/* The adapters are only passed to chat windows, which are deleted in their
 			 * controllers' dtor, which are deleted in ChatManager's dtor. The adapters
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 10d63a7..b5a8d2c 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -11,10 +11,12 @@
 #include <boost/algorithm/string.hpp>
 
 #include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Base/Log.h>
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Base/format.h>
-#include <Swiften/Base/Log.h>
+#include <Swiften/Client/BlockList.h>
+#include <Swiften/Client/ClientBlockListManager.h>
 #include <Swiften/Client/StanzaChannel.h>
 #include <Swiften/Disco/EntityCapsProvider.h>
 #include <Swiften/Elements/Delay.h>
@@ -39,6 +41,7 @@
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
 #include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
@@ -71,11 +74,12 @@ MUCController::MUCController (
 		HistoryController* historyController,
 		MUCRegistry* mucRegistry,
 		HighlightManager* highlightManager,
+		ClientBlockListManager* clientBlockListManager,
 		boost::shared_ptr<ChatMessageParser> chatMessageParser,
 		bool isImpromptu,
 		AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
 		VCardManager* vcardManager) :
-	ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) {
+	ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager) {
 	parting_ = true;
 	joined_ = false;
 	lastWasPresence_ = false;
@@ -100,6 +104,7 @@ MUCController::MUCController (
 	chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1));
 	chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this));
 	chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1));
+	chatWindow_->onUnblockUserRequest.connect(boost::bind(&MUCController::handleUnblockUserRequest, this));
 	muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
 	muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
 	muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
@@ -605,16 +610,23 @@ void MUCController::setOnline(bool online) {
 	} else {
 		if (shouldJoinOnReconnect_) {
 			renameCounter_ = 0;
-			if (isImpromptu_) {
-				lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection);
-			} else {
-				lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
+			boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+			if (blockList && blockList->isBlocked(muc_->getJID())) {
+				handleBlockingStateChanged();
+				lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again")), ChatWindow::DefaultDirection);
 			}
-			if (loginCheckTimer_) {
-				loginCheckTimer_->start();
+			else {
+				if (isImpromptu_) {
+					lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection);
+				} else {
+					lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
+				}
+				if (loginCheckTimer_) {
+					loginCheckTimer_->start();
+				}
+				setNick(desiredNick_);
+				rejoin();
 			}
-			setNick(desiredNick_);
-			rejoin();
 		}
 	}
 }
@@ -970,6 +982,28 @@ void MUCController::handleChangeAffiliationsRequest(const std::vector<std::pair<
 	}
 }
 
+void MUCController::handleUnblockUserRequest() {
+	eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, muc_->getJID()));
+}
+
+void MUCController::handleBlockingStateChanged() {
+	boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+	if (blockList->getState() == BlockList::Available) {
+		if (blockList->isBlocked(toJID_)) {
+			if (!blockedContactAlert_) {
+				blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again"));
+			}
+			chatWindow_->setBlockingState(ChatWindow::IsBlocked);
+		} else {
+			if (blockedContactAlert_) {
+				chatWindow_->removeAlert(*blockedContactAlert_);
+				blockedContactAlert_.reset();
+			}
+			chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
+		}
+	}
+}
+
 void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) {
 	chatWindow_->setAffiliations(affiliation, jids);
 }
@@ -1095,4 +1129,17 @@ void MUCController::handleRoomUnlocked() {
 	}
 }
 
+void MUCController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
+	ChatControllerBase::setAvailableServerFeatures(info);
+	if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
+		boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+
+		blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&MUCController::handleBlockingStateChanged, this));
+		blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&MUCController::handleBlockingStateChanged, this));
+		blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&MUCController::handleBlockingStateChanged, this));
+
+		handleBlockingStateChanged();
+	}
+}
+
 }
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 5e033ed..7c24ae2 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -39,6 +39,7 @@ namespace Swift {
 	class UIEvent;
 	class VCardManager;
 	class RosterVCardProvider;
+	class ClientBlockListManager;
 
 	enum JoinPart {Join, Part, JoinThenPart, PartThenJoin};
 
@@ -50,13 +51,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, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);
+			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);
 			virtual ~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
 			boost::signal<void ()> onImpromptuConfigCompleted;
 			boost::signal<void (const std::string&, const std::string& )> onUserNicknameChanged;
 			virtual void setOnline(bool online);
+			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
 			void rejoin();
 			static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
 			static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu);
@@ -132,6 +134,9 @@ namespace Swift {
 			void configureAsImpromptuRoom(Form::ref form);
 			Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm);
 
+			void handleUnblockUserRequest();
+			void handleBlockingStateChanged();
+
 		private:
 			MUC::ref muc_;
 			UIEventStream* events_;
@@ -157,6 +162,13 @@ namespace Swift {
 			bool isImpromptuAlreadyConfigured_;
 			RosterVCardProvider* rosterVCardProvider_;
 			std::string lastJoinMessageUID_;
+
+			ClientBlockListManager* clientBlockListManager_;
+			boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
+			boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
+			boost::bsignals::scoped_connection blockingOnItemRemovedConnection_;
+
+			boost::optional<ChatWindow::AlertID> blockedContactAlert_;
 	};
 }
 
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 5a4d3b8..0b7b793 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2014 Isode Limited.
+ * Copyright (c) 2010-2015 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -7,37 +7,40 @@
 #include <cppunit/extensions/HelperMacros.h>
 #include <cppunit/extensions/TestFactoryRegistry.h>
 #include <boost/algorithm/string.hpp>
+
 #include <hippomocks.h>
 
-#include "Swiften/Base/foreach.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swiften/Presence/DirectedPresenceSender.h"
-#include "Swiften/Presence/StanzaChannelPresenceSender.h"
-#include "Swiften/Avatars/NullAvatarManager.h"
-#include "Swift/Controllers/Chat/MUCController.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swiften/Roster/XMPPRoster.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UnitTest/MockChatWindow.h"
-#include "Swiften/MUC/UnitTest/MockMUC.h"
-#include "Swiften/Client/DummyStanzaChannel.h"
-#include "Swiften/Queries/DummyIQChannel.h"
-#include "Swiften/Presence/PresenceOracle.h"
-#include "Swiften/Network/TimerFactory.h"
-#include "Swiften/Elements/MUCUserPayload.h"
-#include "Swiften/Disco/DummyEntityCapsProvider.h"
-#include <Swiften/VCards/VCardMemoryStorage.h>
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Crypto/CryptoProvider.h>
 #include <Swiften/Crypto/PlatformCryptoProvider.h>
+#include <Swiften/Disco/DummyEntityCapsProvider.h>
+#include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/MUC/UnitTest/MockMUC.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Presence/DirectedPresenceSender.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Presence/StanzaChannelPresenceSender.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Roster/XMPPRoster.h>
 #include <Swiften/VCards/VCardManager.h>
-#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
+
 #include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Chat/MUCController.h>
 #include <Swift/Controllers/Chat/UserSearchController.h>
-#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
-#include <Swift/Controllers/Roster/Roster.h>
 #include <Swift/Controllers/Roster/GroupRosterItem.h>
-#include <Swiften/Crypto/CryptoProvider.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
+#include <Swift/Controllers/UnitTest/MockChatWindow.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
 
 using namespace Swift;
 
@@ -84,11 +87,13 @@ public:
 		chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true);
 		vcardStorage_ = new VCardMemoryStorage(crypto_.get());
 		vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
-		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL, vcardManager_);
+		clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, NULL, vcardManager_);
 	}
 
 	void tearDown() {
 		delete controller_;
+		delete clientBlockListManager_;
 		delete vcardManager_;
 		delete vcardStorage_;
 		delete highlightManager_;
@@ -432,6 +437,7 @@ private:
 	boost::shared_ptr<CryptoProvider> crypto_;
 	VCardManager* vcardManager_;
 	VCardMemoryStorage* vcardStorage_;
+	ClientBlockListManager* clientBlockListManager_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 6324e9d..33bec75 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -653,7 +653,7 @@ void QtChatWindow::handleActionButtonClicked() {
 			unblock = contextMenu.addAction(tr("Unblock"));
 			unblock->setEnabled(isOnline_);
 		}
-		else if (blockingState_ == IsUnblocked) {
+		else if (!isMUC_ && blockingState_ == IsUnblocked) {
 			block = contextMenu.addAction(tr("Block"));
 			block->setEnabled(isOnline_);
 		}
-- 
cgit v0.10.2-6-g49f6