From 497b647fe034a3d2cdc6d75ce0ff70e3df3aaf04 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 8 Mar 2013 16:17:30 +0100 Subject: Adding support for Blocking Command (XEP-0191) to Swift(-en). Change-Id: I7c92518dc389474d520d4cf96f96a11459f73d26 License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. diff --git a/Swift/Controllers/BlockListController.cpp b/Swift/Controllers/BlockListController.cpp new file mode 100644 index 0000000..e7bc45d --- /dev/null +++ b/Swift/Controllers/BlockListController.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Swift { + +BlockListController::BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController) : blockListManager_(blockListManager), blockListEditorWidgetFactory_(blockListEditorWidgetFactory), blockListEditorWidget_(0), eventController_(eventController), remainingRequests_(0) { + uiEventStream->onUIEvent.connect(boost::bind(&BlockListController::handleUIEvent, this, _1)); + blockListManager_->getBlockList()->onItemAdded.connect(boost::bind(&BlockListController::handleBlockListChanged, this)); + blockListManager_->getBlockList()->onItemRemoved.connect(boost::bind(&BlockListController::handleBlockListChanged, this)); +} + +BlockListController::~BlockListController() { + blockListManager_->getBlockList()->onItemAdded.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this)); + blockListManager_->getBlockList()->onItemRemoved.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this)); +} + +void BlockListController::blockListDifferences(const std::vector &newBlockList, std::vector &jidsToUnblock, std::vector &jidsToBlock) const { + foreach (const JID& jid, blockListBeforeEdit) { + if (std::find(newBlockList.begin(), newBlockList.end(), jid) == newBlockList.end()) { + jidsToUnblock.push_back(jid); + } + } + + foreach (const JID& jid, newBlockList) { + if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) { + jidsToBlock.push_back(jid); + } + } +} + +void BlockListController::handleUIEvent(boost::shared_ptr rawEvent) { + // handle UI dialog + boost::shared_ptr requestDialogEvent = boost::dynamic_pointer_cast(rawEvent); + if (requestDialogEvent != NULL) { + if (blockListEditorWidget_ == NULL) { + blockListEditorWidget_ = blockListEditorWidgetFactory_->createBlockListEditorWidget(); + blockListEditorWidget_->onSetNewBlockList.connect(boost::bind(&BlockListController::handleSetNewBlockList, this, _1)); + } + blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); + blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + blockListEditorWidget_->show(); + return; + } + + // handle block state change + boost::shared_ptr changeStateEvent = boost::dynamic_pointer_cast(rawEvent); + if (changeStateEvent != NULL) { + if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Blocked) { + GenericRequest::ref blockRequest = blockListManager_->createBlockJIDRequest(changeStateEvent->getContact()); + blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, std::vector(1, changeStateEvent->getContact()), false)); + blockRequest->send(); + } else if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Unblocked) { + GenericRequest::ref unblockRequest = blockListManager_->createUnblockJIDRequest(changeStateEvent->getContact()); + unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, std::vector(1, changeStateEvent->getContact()), false)); + unblockRequest->send(); + } + return; + } +} + +void BlockListController::handleBlockResponse(GenericRequest::ref request, boost::shared_ptr, ErrorPayload::ref error, const std::vector& jids, bool originEditor) { + if (error) { + std::string errorMessage; + // FIXME: Handle reporting of list of JIDs in a translatable way. + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to block %1%.")) % jids.at(0).toString()); + if (!error->getText().empty()) { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); + } + eventController_->handleIncomingEvent(boost::make_shared(request->getReceiver(), errorMessage)); + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0)) { + blockListEditorWidget_->setBusy(false); + } + } +} + +void BlockListController::handleUnblockResponse(GenericRequest::ref request, boost::shared_ptr, ErrorPayload::ref error, const std::vector& jids, bool originEditor) { + if (error) { + std::string errorMessage; + // FIXME: Handle reporting of list of JIDs in a translatable way. + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to unblock %1%.")) % jids.at(0).toString()); + if (!error->getText().empty()) { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); + } + eventController_->handleIncomingEvent(boost::make_shared(request->getReceiver(), errorMessage)); + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0)) { + blockListEditorWidget_->setBusy(false); + } + } +} + +void BlockListController::handleSetNewBlockList(const std::vector &newBlockList) { + std::vector jidsToBlock; + std::vector jidsToUnblock; + + blockListDifferences(newBlockList, jidsToUnblock, jidsToBlock); + + if (!jidsToBlock.empty()) { + remainingRequests_++; + GenericRequest::ref blockRequest = blockListManager_->createBlockJIDsRequest(jidsToBlock); + blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, jidsToBlock, true)); + blockRequest->send(); + } + if (!jidsToUnblock.empty()) { + remainingRequests_++; + GenericRequest::ref unblockRequest = blockListManager_->createUnblockJIDsRequest(jidsToUnblock); + unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, jidsToUnblock, true)); + unblockRequest->send(); + } + if (!jidsToBlock.empty() || jidsToUnblock.empty()) { + assert(blockListEditorWidget_); + blockListEditorWidget_->setBusy(true); + } +} + +void BlockListController::handleBlockListChanged() { + if (blockListEditorWidget_) { + std::vector jidsToBlock; + std::vector jidsToUnblock; + + blockListDifferences(blockListEditorWidget_->getCurrentBlockList(), jidsToUnblock, jidsToBlock); + blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); + + foreach (const JID& jid, jidsToBlock) { + blockListBeforeEdit.push_back(jid); + } + + foreach (const JID& jid, jidsToUnblock) { + blockListBeforeEdit.erase(std::remove(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid), blockListBeforeEdit.end());; + } + + blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + } +} + +} diff --git a/Swift/Controllers/BlockListController.h b/Swift/Controllers/BlockListController.h new file mode 100644 index 0000000..4c9caad --- /dev/null +++ b/Swift/Controllers/BlockListController.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace Swift { + +class BlockPayload; +class UnblockPayload; +class ClientBlockListManager; +class EventController; + +class BlockListController { +public: + BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController); + ~BlockListController(); + +private: + void blockListDifferences(const std::vector &newBlockList, std::vector& jidsToUnblock, std::vector& jidsToBlock) const; + + void handleUIEvent(boost::shared_ptr event); + + void handleBlockResponse(GenericRequest::ref, boost::shared_ptr, ErrorPayload::ref error, const std::vector& jids, bool originEditor); + void handleUnblockResponse(GenericRequest::ref, boost::shared_ptr, ErrorPayload::ref error, const std::vector& jids, bool originEditor); + + void handleSetNewBlockList(const std::vector& newBlockList); + + void handleBlockListChanged(); + +private: + ClientBlockListManager* blockListManager_; + BlockListEditorWidgetFactory* blockListEditorWidgetFactory_; + BlockListEditorWidget* blockListEditorWidget_; + EventController* eventController_; + std::vector blockListBeforeEdit; + int remainingRequests_; +}; + +} diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 3c0e933..f0de59c 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -36,14 +36,15 @@ #include #include #include +#include namespace Swift { /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ -ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) { +ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager) + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -145,6 +146,19 @@ void ChatController::setToJID(const JID& jid) { handleBareJIDCapsChanged(toJID_); } +void ChatController::setAvailableServerFeatures(boost::shared_ptr info) { + ChatControllerBase::setAvailableServerFeatures(info); + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + boost::shared_ptr blockList = clientBlockListManager_->getBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingItemAdded, this, _1)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingItemRemoved, this, _1)); + + handleBlockingStateChanged(); + } +} + bool ChatController::isIncomingMessageFromMe(boost::shared_ptr) { return false; } @@ -216,6 +230,36 @@ void ChatController::checkForDisplayingDisplayReceiptsAlert() { } } +void ChatController::handleBlockingStateChanged() { + boost::shared_ptr blockList = clientBlockListManager_->getBlockList(); + if (blockList->getState() == BlockList::Available) { + if (blockList->isBlocked(toJID_.toBare())) { + chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); + chatWindow_->setInputEnabled(false); + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + } else { + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + } + } +} + +void ChatController::handleBlockingItemAdded(const JID& jid) { + if (jid == toJID_.toBare()) { + chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); + chatWindow_->setInputEnabled(false); + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + } +} + +void ChatController::handleBlockingItemRemoved(const JID& jid) { + if (jid == toJID_.toBare()) { + // FIXME: Support for different types of alerts. + chatWindow_->cancelAlert(); + chatWindow_->setInputEnabled(true); + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + } +} + void ChatController::postSendMessage(const std::string& body, boost::shared_ptr sentStanza) { boost::shared_ptr replace = sentStanza->getPayload(); if (replace) { diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 6021ec1..4322240 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -23,12 +23,14 @@ namespace Swift { class SettingsProvider; class HistoryController; class HighlightManager; + class ClientBlockListManager; class ChatController : public ChatControllerBase { public: - ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager); + ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager); virtual ~ChatController(); virtual void setToJID(const JID& jid); + virtual void setAvailableServerFeatures(boost::shared_ptr info); virtual void setOnline(bool online); virtual void handleNewFileTransferController(FileTransferController* ftc); virtual void handleWhiteboardSessionRequest(bool senderIsSelf); @@ -67,6 +69,10 @@ namespace Swift { void handleSettingChanged(const std::string& settingPath); void checkForDisplayingDisplayReceiptsAlert(); + void handleBlockingStateChanged(); + void handleBlockingItemAdded(const JID&); + void handleBlockingItemRemoved(const JID&); + private: NickResolver* nickResolver_; ChatStateNotifier* chatStateNotifier_; @@ -86,6 +92,11 @@ namespace Swift { std::map ftControllers; SettingsProvider* settings_; std::string lastWbID_; + + ClientBlockListManager* clientBlockListManager_; + boost::bsignals::scoped_connection blockingOnStateChangedConnection_; + boost::bsignals::scoped_connection blockingOnItemAddedConnection_; + boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index baef9e6..84bd06a 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -48,7 +48,7 @@ namespace Swift { virtual ~ChatControllerBase(); void showChatWindow(); void activateChatWindow(); - void setAvailableServerFeatures(boost::shared_ptr info); + virtual void setAvailableServerFeatures(boost::shared_ptr info); void handleIncomingMessage(boost::shared_ptr message); std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index dba8565..d6010e9 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace Swift { @@ -75,7 +76,8 @@ ChatsManager::ChatsManager( SettingsProvider* settings, HistoryController* historyController, WhiteboardManager* whiteboardManager, - HighlightManager* highlightManager) : + HighlightManager* highlightManager, + ClientBlockListManager* clientBlockListManager) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), @@ -88,7 +90,8 @@ ChatsManager::ChatsManager( settings_(settings), historyController_(historyController), whiteboardManager_(whiteboardManager), - highlightManager_(highlightManager) { + highlightManager_(highlightManager), + clientBlockListManager_(clientBlockListManager) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -523,7 +526,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) ChatController* ChatsManager::createNewChatController(const JID& contact) { assert(chatControllers_.find(contact) == chatControllers_.end()); - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_); + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 55e62b9..4d7e9a8 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -51,10 +51,11 @@ namespace Swift { class WhiteboardManager; class HistoryController; class HighlightManager; + class ClientBlockListManager; class ChatsManager { 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); + 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); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); @@ -138,5 +139,6 @@ namespace Swift { HistoryController* historyController_; WhiteboardManager* whiteboardManager_; HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index dd90d66..84a407c 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -10,48 +10,49 @@ #include -#include "Swift/Controllers/Chat/ChatsManager.h" - -#include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" -#include "Swift/Controllers/Settings/DummySettingsProvider.h" -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h" -#include "Swiften/Client/Client.h" -#include "Swiften/Disco/EntityCapsManager.h" -#include "Swiften/Disco/CapsProvider.h" -#include "Swiften/MUC/MUCManager.h" -#include "Swift/Controllers/Chat/ChatController.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swiften/Presence/StanzaChannelPresenceSender.h" -#include "Swiften/Avatars/NullAvatarManager.h" -#include "Swiften/Avatars/AvatarMemoryStorage.h" -#include "Swiften/VCards/VCardManager.h" -#include "Swiften/VCards/VCardMemoryStorage.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/Presence/DirectedPresenceSender.h" -#include "Swiften/Roster/XMPPRosterImpl.h" -#include "Swift/Controllers/UnitTest/MockChatWindow.h" -#include "Swiften/Client/DummyStanzaChannel.h" -#include "Swiften/Queries/DummyIQChannel.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Jingle/JingleSessionManager.h" -#include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" -#include "Swiften/Elements/DeliveryReceiptRequest.h" -#include "Swiften/Elements/DeliveryReceipt.h" +#include +#include +#include #include #include #include #include +#include using namespace Swift; @@ -109,7 +110,8 @@ public: highlightManager_ = new HighlightManager(settings_); mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_); manager_->setAvatarManager(avatarManager_); } @@ -120,6 +122,7 @@ public: delete profileSettings_; delete avatarManager_; delete manager_; + delete clientBlockListManager_; delete ftOverview_; delete ftManager_; delete wbSessionManager_; @@ -484,6 +487,7 @@ private: WhiteboardSessionManager* wbSessionManager_; WhiteboardManager* wbManager_; HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 0c9c09c..32d045d 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -87,6 +87,8 @@ #include #include #include +#include +#include namespace Swift { @@ -130,6 +132,7 @@ MainController::MainController( historyViewController_ = NULL; eventWindowController_ = NULL; profileController_ = NULL; + blockListController_ = NULL; showProfileController_ = NULL; contactEditController_ = NULL; userSearchControllerChat_ = NULL; @@ -321,11 +324,13 @@ void MainController::handleConnected() { client_->getFileTransferManager()->startListeningOnPort(randomPort); ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); fileTransferListController_->setFileTransferOverview(ftOverview_); - rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_); + rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager()); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this)); + blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_); + contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_); whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager()); @@ -337,9 +342,9 @@ void MainController::handleConnected() { #ifdef SWIFT_EXPERIMENTAL_HISTORY historyController_ = new HistoryController(storages_->getHistoryStorage()); historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); - chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_); + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager()); #else - chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_); + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager()); #endif client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); @@ -731,6 +736,10 @@ void MainController::handleServerDiscoInfoResponse(boost::shared_ptr if (!error) { chatsManager_->setServerDiscoInfo(info); adHocManager_->setServerDiscoInfo(info); + if (info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + rosterController_->getWindow()->setBlockingCommandAvailable(true); + rosterController_->initBlockingCommand(); + } } } diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 4f37e12..d60805a 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -74,6 +74,7 @@ namespace Swift { class WhiteboardManager; class HighlightManager; class HighlightEditorController; + class BlockListController; class MainController { public: @@ -154,6 +155,7 @@ namespace Swift { HistoryViewController* historyViewController_; HistoryController* historyController_; FileTransferListController* fileTransferListController_; + BlockListController* blockListController_; ChatsManager* chatsManager_; ProfileController* profileController_; ShowProfileController* showProfileController_; diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index f301552..bf85167 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -16,7 +16,7 @@ namespace Swift { -ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID) { +ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID), blockState_(BlockingNotSupported) { } ContactRosterItem::~ContactRosterItem() { @@ -134,6 +134,14 @@ bool ContactRosterItem::supportsFeature(const Feature feature) const { return features_.find(feature) != features_.end(); } +void ContactRosterItem::setBlockState(BlockState state) { + blockState_ = state; +} + +ContactRosterItem::BlockState ContactRosterItem::blockState() const { + return blockState_; +} + } diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 247c606..fc65d6d 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -28,6 +28,12 @@ class ContactRosterItem : public RosterItem { FileTransferFeature, WhiteboardFeature }; + + enum BlockState { + BlockingNotSupported, + IsBlocked, + IsUnblocked + }; public: ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent); @@ -53,6 +59,9 @@ class ContactRosterItem : public RosterItem { void setSupportedFeatures(const std::set& features); bool supportsFeature(Feature feature) const; + void setBlockState(BlockState state); + BlockState blockState() const; + private: JID jid_; JID displayJID_; @@ -63,6 +72,7 @@ class ContactRosterItem : public RosterItem { boost::shared_ptr shownPresence_; std::vector groups_; std::set features_; + BlockState blockState_; }; } diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index b5c5998..3d0ca2e 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -22,7 +22,7 @@ namespace Swift { -Roster::Roster(bool sortByStatus, bool fullJIDMapping) { +Roster::Roster(bool sortByStatus, bool fullJIDMapping) : blockingSupported_(false) { sortByStatus_ = sortByStatus; fullJIDMapping_ = fullJIDMapping; root_ = new GroupRosterItem("Dummy-Root", NULL, sortByStatus_); @@ -71,6 +71,28 @@ void Roster::setAvailableFeatures(const JID& jid, const std::set &jids, ContactRosterItem::BlockState state) { + if (!blockingSupported_ ) { + foreach(ItemMap::value_type i, itemMap_) { + foreach(ContactRosterItem* item, i.second) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } + } + } + + foreach(const JID& jid, jids) { + ItemMap::const_iterator i = itemMap_.find(fullJIDMapping_ ? jid : jid.toBare()); + if (i == itemMap_.end()) { + continue; + } + foreach(ContactRosterItem* item, i->second) { + item->setBlockState(state); + } + } + + blockingSupported_ = true; +} + void Roster::removeGroup(const std::string& group) { root_->removeGroupChild(group); } @@ -87,6 +109,9 @@ void Roster::addContact(const JID& jid, const JID& displayJID, const std::string GroupRosterItem* group(getGroup(groupName)); ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); item->setAvatarPath(avatarPath); + if (blockingSupported_) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } group->addChild(item); ItemMap::iterator i = itemMap_.insert(std::make_pair(fullJIDMapping_ ? jid : jid.toBare(), std::vector())).first; if (!i->second.empty()) { diff --git a/Swift/Controllers/Roster/Roster.h b/Swift/Controllers/Roster/Roster.h index 74547d6..91a152f 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -45,6 +45,7 @@ class Roster { boost::signal onDataChanged; GroupRosterItem* getGroup(const std::string& groupName); void setAvailableFeatures(const JID& jid, const std::set& features); + void setBlockedState(const std::vector& jids, ContactRosterItem::BlockState state); private: void handleDataChanged(RosterItem* item); @@ -58,6 +59,7 @@ class Roster { ItemMap itemMap_; bool fullJIDMapping_; bool sortByStatus_; + bool blockingSupported_; }; } diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index ec52993..d09ef4c 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -44,14 +44,15 @@ #include #include #include +#include namespace Swift { /** * The controller does not gain ownership of these parameters. */ -RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview) { +RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) { assert(fileTransferOverview); iqRouter_ = iqRouter; presenceOracle_ = presenceOracle; @@ -183,6 +184,20 @@ void RosterController::handleSettingChanged(const std::string& settingPath) { } } +void RosterController::handleBlockingStateChanged() { + if (clientBlockListManager_->getBlockList()->getState() == BlockList::Available) { + roster_->setBlockedState(clientBlockListManager_->getBlockList()->getItems(), ContactRosterItem::IsBlocked); + } +} + +void RosterController::handleBlockingItemAdded(const JID& jid) { + roster_->setBlockedState(std::vector(1, jid), ContactRosterItem::IsBlocked); +} + +void RosterController::handleBlockingItemRemoved(const JID& jid) { + roster_->setBlockedState(std::vector(1, jid), ContactRosterItem::IsUnblocked); +} + void RosterController::handleUIEvent(boost::shared_ptr event) { if (boost::shared_ptr addContactEvent = boost::dynamic_pointer_cast(event)) { RosterItemPayload item; @@ -256,6 +271,18 @@ void RosterController::updateItem(const XMPPRosterItem& item) { request->send(); } +void RosterController::initBlockingCommand() { + boost::shared_ptr blockList = clientBlockListManager_->getBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&RosterController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&RosterController::handleBlockingItemAdded, this, _1)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&RosterController::handleBlockingItemRemoved, this, _1)); + + if (blockList->getState() == BlockList::Available) { + roster_->setBlockedState(blockList->getItems(), ContactRosterItem::IsBlocked); + } +} + void RosterController::handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr rosterPayload) { if (!error) { return; diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index ec07574..06b551e 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -39,10 +39,11 @@ namespace Swift { class NickManager; class EntityCapsProvider; class FileTransferManager; - + class ClientBlockListManager; + class RosterController { public: - RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview); + RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager); ~RosterController(); void showRosterWindow(); MainWindow* getWindow() {return mainWindow_;} @@ -57,6 +58,8 @@ namespace Swift { void setContactGroups(const JID& jid, const std::vector& groups); void updateItem(const XMPPRosterItem&); + void initBlockingCommand(); + private: void handleOnJIDAdded(const JID &jid); void handleRosterCleared(); @@ -76,6 +79,10 @@ namespace Swift { void handleOnCapsChanged(const JID& jid); void handleSettingChanged(const std::string& settingPath); + void handleBlockingStateChanged(); + void handleBlockingItemAdded(const JID& jid); + void handleBlockingItemRemoved(const JID& jid); + JID myJID_; XMPPRoster* xmppRoster_; MainWindowFactory* mainWindowFactory_; @@ -94,7 +101,11 @@ namespace Swift { UIEventStream* uiEventStream_; EntityCapsProvider* entityCapsManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; + boost::bsignals::scoped_connection blockingOnStateChangedConnection_; + boost::bsignals::scoped_connection blockingOnItemAddedConnection_; + boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; boost::bsignals::scoped_connection changeStatusConnection_; boost::bsignals::scoped_connection signOutConnection_; boost::bsignals::scoped_connection uiEventConnection_; diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index e439c78..b0034e6 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -37,6 +37,7 @@ #include #include #include +#include using namespace Swift; @@ -82,12 +83,14 @@ class RosterControllerTest : public CppUnit::TestFixture { ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_); + clientBlockListManager_ = new ClientBlockListManager(router_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_); mainWindow_ = mainWindowFactory_->last; } void tearDown() { delete rosterController_; + delete clientBlockListManager_; delete ftManager_; delete jingleSessionManager_; @@ -337,6 +340,7 @@ class RosterControllerTest : public CppUnit::TestFixture { JingleSessionManager* jingleSessionManager_; FileTransferManager* ftManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; }; CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest); diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 26b9334..6f36a52 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -49,6 +49,7 @@ if env["SCONS_STAGE"] == "build" : "HistoryViewController.cpp", "HistoryController.cpp", "FileTransferListController.cpp", + "BlockListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", diff --git a/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h new file mode 100644 index 0000000..d29cb4f --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +namespace Swift { + +class RequestBlockListDialogUIEvent : public UIEvent { +}; + +} diff --git a/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h new file mode 100644 index 0000000..9b7abcb --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +#include + +namespace Swift { + +class RequestChangeBlockStateUIEvent : public UIEvent { + public: + enum BlockState { + Blocked, + Unblocked + }; + + public: + RequestChangeBlockStateUIEvent(BlockState newState, const JID& contact) : state_(newState), contact_(contact) {} + + BlockState getBlockState() const { + return state_; + } + + JID getContact() const { + return contact_; + } + private: + BlockState state_; + JID contact_; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h new file mode 100644 index 0000000..60a1c11 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +#include +#include + +namespace Swift { + + class ClientBlockListManager; + + class BlockListEditorWidget { + public: + virtual ~BlockListEditorWidget() {} + + virtual void show() = 0; + + virtual void setCurrentBlockList(const std::vector& blockedJIDs) = 0; + virtual void setBusy(bool isBusy) = 0; + + virtual std::vector getCurrentBlockList() const = 0; + + boost::signal& /* blockedJID */)> onSetNewBlockList; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h new file mode 100644 index 0000000..eb91ac1 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class BlockListEditorWidget; + + class BlockListEditorWidgetFactory { + public: + virtual ~BlockListEditorWidgetFactory() {} + + virtual BlockListEditorWidget* createBlockListEditorWidget() = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index d6b3656..b8e6722 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -39,6 +39,7 @@ namespace Swift { enum RoomAction {ChangeSubject, Configure, Affiliations, Destroy, Invite}; enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; enum WhiteboardSessionState {WhiteboardAccepted, WhiteboardTerminated, WhiteboardRejected}; + enum BlockingState {BlockingUnsupported, IsBlocked, IsUnblocked}; ChatWindow() {} virtual ~ChatWindow() {} @@ -89,6 +90,7 @@ namespace Swift { virtual void setSubject(const std::string& subject) = 0; virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector&) = 0; virtual void setAvailableRoomActions(const std::vector &actions) = 0; + virtual void setBlockingState(BlockingState state) = 0; /** * Set an alert on the window. * @param alertText Description of alert (required). diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h index 2df2c10..3b10041 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -34,6 +34,7 @@ namespace Swift { /** Must be able to cope with NULL to clear the roster */ virtual void setRosterModel(Roster* roster) = 0; virtual void setConnecting() = 0; + virtual void setBlockingCommandAvailable(bool isAvailable) = 0; virtual void setAvailableAdHocCommands(const std::vector& commands) = 0; virtual void setStreamEncryptionStatus(bool tlsInPlaceAndValid) = 0; virtual void openCertificateDialog(const std::vector& chain) = 0; diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index dcd1779..990dc98 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace Swift { class UIFactory : @@ -40,7 +41,8 @@ namespace Swift { public AdHocCommandWindowFactory, public FileTransferListWidgetFactory, public WhiteboardWindowFactory, - public HighlightEditorWidgetFactory { + public HighlightEditorWidgetFactory, + public BlockListEditorWidgetFactory { public: virtual ~UIFactory() {} }; diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 84aaa04..8af65f7 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -60,6 +60,8 @@ namespace Swift { virtual void setAvailableRoomActions(const std::vector &) {} virtual InviteToChatWindow* createInviteToChatWindow() {return NULL;} + virtual void setBlockingState(BlockingState) {} + std::string name_; std::string lastMessageBody_; std::vector labels_; diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h index 19ab522..69a4e25 100644 --- a/Swift/Controllers/UnitTest/MockMainWindow.h +++ b/Swift/Controllers/UnitTest/MockMainWindow.h @@ -24,6 +24,7 @@ namespace Swift { virtual void setConnecting() {} virtual void setStreamEncryptionStatus(bool /*tlsInPlaceAndValid*/) {} virtual void openCertificateDialog(const std::vector& /*chain*/) {} + virtual void setBlockingCommandAvailable(bool /*isAvailable*/) {} Roster* roster; }; diff --git a/Swift/QtUI/QtBlockListEditorWindow.cpp b/Swift/QtUI/QtBlockListEditorWindow.cpp new file mode 100644 index 0000000..63e8d1f --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Swift { + +class QtJIDValidator : public QValidator { + public: + QtJIDValidator(QObject* parent) : QValidator(parent) {} + virtual ~QtJIDValidator() {} + virtual QValidator::State validate(QString& input, int&) const { + return JID(Q2PSTRING(input)).isValid() ? Acceptable : Intermediate; + } +}; + +class QtJIDValidatedItemDelegate : public QItemDelegate { + public: + QtJIDValidatedItemDelegate(QObject* parent) : QItemDelegate(parent) {} + virtual ~QtJIDValidatedItemDelegate() {} + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const { + QLineEdit *editor = new QLineEdit(parent); + editor->setValidator(new QtJIDValidator(editor)); + return editor; + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const { + QString value = index.model()->data(index, Qt::EditRole).toString(); + + QLineEdit *lineEdit = static_cast(editor); + lineEdit->setText(value); + } + + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { + QLineEdit *lineEdit = static_cast(editor); + QString currentValue = lineEdit->text(); + int pos = 0; + if (lineEdit->validator()->validate(currentValue, pos) == QValidator::Acceptable) { + model->setData(index, lineEdit->text(), Qt::EditRole); + } else { + model->setData(index, QString(), Qt::EditRole); + } + } + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { + editor->setGeometry(option.rect); + } +}; + +QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow) { + ui->setupUi(this); + new QShortcut(QKeySequence::Close, this, SLOT(close())); + ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); + + itemDelegate = new QtRemovableItemDelegate(style()); + + connect(ui->savePushButton, SIGNAL(clicked()), SLOT(applyChanges())); + + ui->blockListTreeWidget->setColumnCount(2); + ui->blockListTreeWidget->header()->setStretchLastSection(false); + int closeIconWidth = ui->blockListTreeWidget->fontMetrics().height(); + ui->blockListTreeWidget->header()->resizeSection(1, closeIconWidth); + +#if QT_VERSION >= 0x050000 + ui->blockListTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); +#else + ui->blockListTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch); +#endif + + ui->blockListTreeWidget->setHeaderHidden(true); + ui->blockListTreeWidget->setRootIsDecorated(false); + ui->blockListTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); + ui->blockListTreeWidget->setItemDelegateForColumn(0, new QtJIDValidatedItemDelegate(this)); + ui->blockListTreeWidget->setItemDelegateForColumn(1, itemDelegate); + connect(ui->blockListTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int))); + + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); +} + +QtBlockListEditorWindow::~QtBlockListEditorWindow() { +} + +void QtBlockListEditorWindow::show() { + QWidget::show(); + QWidget::activateWindow(); +} + +void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *, int) { + bool hasEmptyRow = false; + QList rows = ui->blockListTreeWidget->findItems("", Qt::MatchFixedString); + foreach(QTreeWidgetItem* row, rows) { + if (row->text(0).isEmpty()) { + hasEmptyRow = true; + } + } + + if (!hasEmptyRow) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); + } +} + +void QtBlockListEditorWindow::applyChanges() { + onSetNewBlockList(getCurrentBlockList()); +} + +void Swift::QtBlockListEditorWindow::setCurrentBlockList(const std::vector &blockedJIDs) { + ui->blockListTreeWidget->clear(); + + foreach(const JID& jid, blockedJIDs) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(P2QSTRING(jid.toString())) << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); + } + handleItemChanged(0,0); +} + +void Swift::QtBlockListEditorWindow::setBusy(bool isBusy) { + if (isBusy) { + ui->throbberLabel->movie()->start(); + ui->throbberLabel->show(); + } else { + ui->throbberLabel->movie()->stop(); + ui->throbberLabel->hide(); + } +} + +std::vector Swift::QtBlockListEditorWindow::getCurrentBlockList() const { + std::vector futureBlockedJIDs; + + for(int i=0; i < ui->blockListTreeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem* row = ui->blockListTreeWidget->topLevelItem(i); + if (!row->text(0).isEmpty()) { + futureBlockedJIDs.push_back(JID(Q2PSTRING(row->text(0)))); + } + } + return futureBlockedJIDs; +} + +} diff --git a/Swift/QtUI/QtBlockListEditorWindow.h b/Swift/QtUI/QtBlockListEditorWindow.h new file mode 100644 index 0000000..4b124a3 --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace Ui { + class QtBlockListEditorWindow; +} + +namespace Swift { + +class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { + Q_OBJECT + + public: + QtBlockListEditorWindow(); + virtual ~QtBlockListEditorWindow(); + + virtual void show(); + virtual void setCurrentBlockList(const std::vector& blockedJIDs); + virtual void setBusy(bool isBusy); + virtual std::vector getCurrentBlockList() const; + + private slots: + void handleItemChanged(QTreeWidgetItem*, int); + void applyChanges(); + + private: + Ui::QtBlockListEditorWindow* ui; + QtRemovableItemDelegate* itemDelegate; +}; + +} diff --git a/Swift/QtUI/QtBlockListEditorWindow.ui b/Swift/QtUI/QtBlockListEditorWindow.ui new file mode 100644 index 0000000..f71bbae --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.ui @@ -0,0 +1,89 @@ + + + QtBlockListEditorWindow + + + + 0 + 0 + 348 + 262 + + + + Edit Block List + + + + 5 + + + 5 + + + + + false + + + + 1 + + + + + + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Qt::AlignCenter + + + Qt::NoTextInteraction + + + + + + + Save + + + true + + + + + + + + + + diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 11e64ab..efdab99 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "QtChatWindowJSBridge.h" #include "QtUtilities.h" @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -66,7 +68,7 @@ const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetrans const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite"); -QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons) { +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons), blockingState_(BlockingUnsupported) { settings_ = settings; unreadCount_ = 0; idCounter_ = 0; @@ -105,21 +107,18 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt alertLabel_->setStyleSheet(alertStyleSheet_); alertWidget_->hide(); - QBoxLayout* subjectLayout = new QBoxLayout(QBoxLayout::LeftToRight); + subjectLayout_ = new QBoxLayout(QBoxLayout::LeftToRight); subject_ = new QLineEdit(this); - subjectLayout->addWidget(subject_); + subjectLayout_->addWidget(subject_); setSubject(""); subject_->setReadOnly(true); - actionButton_ = new QPushButton(this); + QPushButton* actionButton_ = new QPushButton(this); actionButton_->setIcon(QIcon(":/icons/actions.png")); connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked())); - subjectLayout->addWidget(actionButton_); - subject_->hide(); - actionButton_->hide(); - layout->addLayout(subjectLayout); + layout->addLayout(subjectLayout_); logRosterSplitter_ = new QSplitter(this); logRosterSplitter_->setAutoFillBackground(true); @@ -159,6 +158,12 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt correctingLabel_ = new QLabel(tr("Correcting"), this); inputBarLayout->addWidget(correctingLabel_); correctingLabel_->hide(); + + // using an extra layout to work around Qt margin glitches on OS X + QHBoxLayout* actionLayout = new QHBoxLayout(); + actionLayout->addWidget(actionButton_); + + inputBarLayout->addLayout(actionLayout); layout->addLayout(inputBarLayout); inputClearing_ = false; @@ -415,7 +420,6 @@ void QtChatWindow::convertToMUC() { setAcceptDrops(false); treeWidget_->show(); subject_->show(); - actionButton_->show(); } void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { @@ -504,8 +508,7 @@ QString QtChatWindow::linkimoticonify(const QString& message) const { return messageHTML; } -QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight) -{ +QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight) { QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor())); QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground())); if (color.isEmpty()) { @@ -922,15 +925,26 @@ void QtChatWindow::handleActionButtonClicked() { QAction* destroy = NULL; QAction* invite = NULL; - foreach(ChatWindow::RoomAction availableAction, availableRoomActions_) - { - switch(availableAction) + QAction* block = NULL; + QAction* unblock = NULL; + + if (availableRoomActions_.empty()) { + if (blockingState_ == IsBlocked) { + unblock = contextMenu.addAction(tr("Unblock")); + } else if (blockingState_ == IsUnblocked) { + block = contextMenu.addAction(tr("Block")); + } + } else { + foreach(ChatWindow::RoomAction availableAction, availableRoomActions_) { - case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break; - case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break; - case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break; - case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break; - case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break; + switch(availableAction) + { + case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break; + case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break; + case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break; + case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break; + case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break; + } } } @@ -970,6 +984,12 @@ void QtChatWindow::handleActionButtonClicked() { else if (result == invite) { onInvitePersonToThisMUCRequest(); } + else if (result == block) { + eventStream_->send(boost::make_shared(RequestChangeBlockStateUIEvent::Blocked, JID(Q2PSTRING(contact_)))); + } + else if (result == unblock) { + eventStream_->send(boost::make_shared(RequestChangeBlockStateUIEvent::Unblocked, JID(Q2PSTRING(contact_)))); + } } void QtChatWindow::handleAffiliationEditorAccepted() { @@ -981,11 +1001,14 @@ void QtChatWindow::setAffiliations(MUCOccupant::Affiliation affiliation, const s affiliationEditor_->setAffiliations(affiliation, jids); } -void QtChatWindow::setAvailableRoomActions(const std::vector &actions) -{ +void QtChatWindow::setAvailableRoomActions(const std::vector& actions) { availableRoomActions_ = actions; } +void QtChatWindow::setBlockingState(BlockingState state) { + blockingState_ = state; +} + void QtChatWindow::showRoomConfigurationForm(Form::ref form) { if (mucConfigurationWindow_) { delete mucConfigurationWindow_.data(); diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index 4abd456..3ccea31 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -132,7 +132,8 @@ namespace Swift { void showRoomConfigurationForm(Form::ref); void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true); void setAffiliations(MUCOccupant::Affiliation, const std::vector&); - void setAvailableRoomActions(const std::vector &actions); + void setAvailableRoomActions(const std::vector& actions); + void setBlockingState(BlockingState state); InviteToChatWindow* createInviteToChatWindow(); @@ -209,6 +210,7 @@ namespace Swift { QtChatTheme* theme_; QtTextEdit* input_; QWidget* midBar_; + QBoxLayout* subjectLayout_; QComboBox* labelsWidget_; QtOccupantListWidget* treeWidget_; QLabel* correctingLabel_; @@ -217,7 +219,6 @@ namespace Swift { QPushButton* alertButton_; TabComplete* completer_; QLineEdit* subject_; - QPushButton* actionButton_; bool isCorrection_; bool previousMessageWasSelf_; PreviousMessageKind previousMessageKind_; @@ -240,5 +241,6 @@ namespace Swift { bool showEmoticons_; QPalette defaultLabelsPalette_; LabelModel* labelModel_; + BlockingState blockingState_; }; } diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 9749397..572b06f 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +139,10 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr connect(viewLogsAction, SIGNAL(triggered()), SLOT(handleViewLogsAction())); actionsMenu->addAction(viewLogsAction); #endif + openBlockingListEditor_ = new QAction(tr("Edit &Blocking List…"), this); + connect(openBlockingListEditor_, SIGNAL(triggered()), SLOT(handleEditBlockingList())); + actionsMenu->addAction(openBlockingListEditor_); + openBlockingListEditor_->setVisible(false); addUserAction_ = new QAction(tr("&Add Contact…"), this); connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool))); actionsMenu->addAction(addUserAction_); @@ -190,6 +195,10 @@ void QtMainWindow::handleShowCertificateInfo() { onShowCertificateRequest(); } +void QtMainWindow::handleEditBlockingList() { + uiEventStream_->send(boost::make_shared()); +} + QtEventWindow* QtMainWindow::getEventWindow() { return eventWindow_; } @@ -359,5 +368,9 @@ void QtMainWindow::setAvailableAdHocCommands(const std::vector } } +void QtMainWindow::setBlockingCommandAvailable(bool isAvailable) { + openBlockingListEditor_->setVisible(isAvailable); +} + } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 99bc675..3e6e1d3 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -53,6 +53,7 @@ namespace Swift { QtChatListWindow* getChatListWindow(); void setRosterModel(Roster* roster); void setAvailableAdHocCommands(const std::vector& commands); + void setBlockingCommandAvailable(bool isAvailable); private slots: void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage); void handleSettingChanged(const std::string& settingPath); @@ -72,6 +73,7 @@ namespace Swift { void handleTabChanged(int index); void handleToggleRequestDeliveryReceipts(bool enabled); void handleShowCertificateInfo(); + void handleEditBlockingList(); private: SettingsProvider* settings_; @@ -85,6 +87,7 @@ namespace Swift { QAction* showOfflineAction_; QAction* compactRosterAction_; QAction* showEmoticonsAction_; + QAction* openBlockingListEditor_; QAction* toggleRequestDeliveryReceipts_; QMenu* serverAdHocMenu_; QtTabWidget* tabs_; diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 7ec25df..0c7fbc2 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -4,34 +4,35 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtUIFactory.h" +#include #include -#include "QtXMLConsoleWidget.h" -#include "QtChatTabs.h" -#include "QtMainWindow.h" -#include "QtLoginWindow.h" -#include "QtSystemTray.h" -#include "QtSettingsProvider.h" -#include "QtMainWindow.h" -#include "QtChatWindow.h" -#include "QtJoinMUCWindow.h" -#include "QtChatWindowFactory.h" -#include "QtSwiftUtil.h" -#include "MUCSearch/QtMUCSearchWindow.h" -#include "UserSearch/QtUserSearchWindow.h" -#include "QtProfileWindow.h" -#include "QtContactEditWindow.h" -#include "QtAdHocCommandWindow.h" -#include "QtFileTransferListWidget.h" -#include -#include "Whiteboard/QtWhiteboardWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include #include -#include +#include +#include namespace Swift { @@ -167,6 +168,10 @@ HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() { return new QtHighlightEditorWidget(); } +BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { + return new QtBlockListEditorWindow(); +} + void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr command) { new QtAdHocCommandWindow(command); } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index a1baa82..662c78e 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -49,6 +49,7 @@ namespace Swift { virtual FileTransferListWidget* createFileTransferListWidget(); virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr whiteboardSession); virtual HighlightEditorWidget* createHighlightEditorWidget(); + virtual BlockListEditorWidget* createBlockListEditorWidget(); virtual void createAdHocCommandWindow(boost::shared_ptr command); private slots: diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index b783ff6..6bf3989 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -4,24 +4,25 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Roster/QtRosterWidget.h" +#include #include #include #include #include -#include "Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h" -#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" -#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" -#include "Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h" -#include "Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h" -#include "QtContactEditWindow.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "QtSwiftUtil.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace Swift { @@ -59,6 +60,17 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { QAction* editContact = contextMenu.addAction(tr("Edit…")); QAction* removeContact = contextMenu.addAction(tr("Remove")); QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile")); + + QAction* unblockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsBlocked) { + unblockContact = contextMenu.addAction(tr("Unblock")); + } + + QAction* blockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsUnblocked) { + blockContact = contextMenu.addAction(tr("Block")); + } + #ifdef SWIFT_EXPERIMENTAL_FT QAction* sendFile = NULL; if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { @@ -71,6 +83,7 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat")); } #endif + QAction* result = contextMenu.exec(event->globalPos()); if (result == editContact) { eventStream_->send(boost::make_shared(contact->getJID())); @@ -83,6 +96,12 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { else if (result == showProfileForContact) { eventStream_->send(boost::make_shared(contact->getJID())); } + else if (unblockContact && result == unblockContact) { + eventStream_->send(boost::make_shared(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID())); + } + else if (blockContact && result == blockContact) { + eventStream_->send(boost::make_shared(RequestChangeBlockStateUIEvent::Blocked, contact->getJID())); + } #ifdef SWIFT_EXPERIMENTAL_FT else if (sendFile && result == sendFile) { QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;")); diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp index 2909c05..8f39f35 100644 --- a/Swift/QtUI/Roster/RosterModel.cpp +++ b/Swift/QtUI/Roster/RosterModel.cpp @@ -162,6 +162,10 @@ QString RosterModel::getStatusText(RosterItem* item) const { QIcon RosterModel::getPresenceIcon(RosterItem* item) const { ContactRosterItem* contact = dynamic_cast(item); if (!contact) return QIcon(); + if (contact->blockState() == ContactRosterItem::IsBlocked) { + return QIcon(":/icons/stop.png"); + } + QString iconString; switch (contact->getStatusShow()) { case StatusShow::Online: iconString = "online";break; diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 0d69d6b..0bcab5e 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -94,6 +94,7 @@ sources = [ "QtLoginWindow.cpp", "QtMainWindow.cpp", "QtProfileWindow.cpp", + "QtBlockListEditorWindow.cpp", "QtNameWidget.cpp", "QtSettingsProvider.cpp", "QtStatusWidget.cpp", @@ -273,6 +274,7 @@ myenv.Uic4("QtHistoryWindow.ui") myenv.Uic4("QtConnectionSettings.ui") myenv.Uic4("QtHighlightRuleWidget.ui") myenv.Uic4("QtHighlightEditorWidget.ui") +myenv.Uic4("QtBlockListEditorWindow.ui") myenv.Uic4("QtSpellCheckerWindow.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index f1b3140..eeef80d 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -41,5 +41,6 @@ ../resources/icons/star-checked2.png ../resources/icons/star-unchecked2.png ../resources/icons/zzz.png + ../resources/icons/stop.png diff --git a/Swift/resources/icons/stop.png b/Swift/resources/icons/stop.png new file mode 100644 index 0000000..1574342 Binary files /dev/null and b/Swift/resources/icons/stop.png differ diff --git a/Swift/resources/icons/stop.svg b/Swift/resources/icons/stop.svg new file mode 100644 index 0000000..b6171ef --- /dev/null +++ b/Swift/resources/icons/stop.svg @@ -0,0 +1,74 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/Swift/resources/icons/stop.txt b/Swift/resources/icons/stop.txt new file mode 100644 index 0000000..9d4a1b1 --- /dev/null +++ b/Swift/resources/icons/stop.txt @@ -0,0 +1,2 @@ +License: Public Domain +Source: https://commons.wikimedia.org/wiki/File:Blank_stop_sign_octagon.svg diff --git a/Swiften/Client/BlockList.cpp b/Swiften/Client/BlockList.cpp index 0b2fc12..3ee7864 100644 --- a/Swiften/Client/BlockList.cpp +++ b/Swiften/Client/BlockList.cpp @@ -6,8 +6,17 @@ #include +#include + using namespace Swift; BlockList::~BlockList() { } + +bool BlockList::isBlocked(const JID& jid) const { + const std::vector& items = getItems(); + return (std::find(items.begin(), items.end(), jid.toBare()) != items.end()) || + (std::find(items.begin(), items.end(), JID(jid.getDomain())) != items.end()) || + (std::find(items.begin(), items.end(), jid) != items.end()); +} diff --git a/Swiften/Client/BlockList.h b/Swiften/Client/BlockList.h index d8cff60..99c83c1 100644 --- a/Swiften/Client/BlockList.h +++ b/Swiften/Client/BlockList.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include #include @@ -15,6 +15,7 @@ namespace Swift { class BlockList { public: enum State { + Init, Requesting, Available, Error @@ -23,7 +24,9 @@ namespace Swift { virtual State getState() const = 0; - virtual const std::set& getItems() const = 0; + virtual const std::vector& getItems() const = 0; + + bool isBlocked(const JID& jid) const; public: boost::signal onStateChanged; diff --git a/Swiften/Client/BlockListImpl.cpp b/Swiften/Client/BlockListImpl.cpp index dfaaaf1..5950233 100644 --- a/Swiften/Client/BlockListImpl.cpp +++ b/Swiften/Client/BlockListImpl.cpp @@ -8,30 +8,47 @@ #include +#include + using namespace Swift; -BlockListImpl::BlockListImpl() { +BlockListImpl::BlockListImpl() : state(Init) { } void BlockListImpl::setItems(const std::vector& items) { - this->items = std::set(items.begin(), items.end()); + foreach (const JID& jid, this->items) { + if (std::find(items.begin(), items.end(), jid) != items.end()) { + onItemRemoved(jid); + } + } + + foreach (const JID& jid, items) { + if (std::find(this->items.begin(), this->items.end(), jid) != this->items.end()) { + onItemAdded(jid); + } + } + this->items = items; } void BlockListImpl::addItem(const JID& item) { - if (items.insert(item).second) { + if (std::find(items.begin(), items.end(), item) == items.end()) { + items.push_back(item); onItemAdded(item); } } void BlockListImpl::removeItem(const JID& item) { - if (items.erase(item)) { + size_t oldSize = items.size(); + items.erase(std::remove(items.begin(), items.end(), item), items.end()); + if (items.size() != oldSize) { onItemRemoved(item); } } void BlockListImpl::setState(State state) { if (this->state != state) { + this->state = state; onStateChanged(); } } @@ -43,14 +60,13 @@ void BlockListImpl::addItems(const std::vector& items) { } void BlockListImpl::removeItems(const std::vector& items) { - foreach (const JID& item, items) { + std::vector itemsToRemove = items; + foreach (const JID& item, itemsToRemove) { removeItem(item); } } void BlockListImpl::removeAllItems() { - foreach (const JID& item, items) { - removeItem(item); - } + removeItems(items); } diff --git a/Swiften/Client/BlockListImpl.h b/Swiften/Client/BlockListImpl.h index ef08340..2a799ae 100644 --- a/Swiften/Client/BlockListImpl.h +++ b/Swiften/Client/BlockListImpl.h @@ -19,7 +19,7 @@ namespace Swift { void setState(State state); - virtual const std::set& getItems() const { + virtual const std::vector& getItems() const { return items; } @@ -32,6 +32,6 @@ namespace Swift { private: State state; - std::set items; + std::vector items; }; } diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp index 1a6c64b..00f0f44 100644 --- a/Swiften/Client/Client.cpp +++ b/Swiften/Client/Client.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #ifndef SWIFT_EXPERIMENTAL_FT #include #endif @@ -68,6 +69,7 @@ Client::Client(const JID& jid, const SafeString& password, NetworkFactories* net blindCertificateTrustChecker = new BlindCertificateTrustChecker(); jingleSessionManager = new JingleSessionManager(getIQRouter()); + blockListManager = new ClientBlockListManager(getIQRouter()); fileTransferManager = NULL; whiteboardSessionManager = NULL; diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h index 126572a..f192539 100644 --- a/Swiften/Client/Client.h +++ b/Swiften/Client/Client.h @@ -37,6 +37,7 @@ namespace Swift { class JingleSessionManager; class FileTransferManager; class WhiteboardSessionManager; + class ClientBlockListManager; /** * Provides the core functionality for writing XMPP client software. @@ -135,6 +136,10 @@ namespace Swift { ClientDiscoManager* getDiscoManager() const { return discoManager; } + + ClientBlockListManager* getClientBlockListManager() const { + return blockListManager; + } /** * Returns a FileTransferManager for the client. This is only available after the onConnected @@ -188,6 +193,7 @@ namespace Swift { JingleSessionManager* jingleSessionManager; FileTransferManager* fileTransferManager; BlindCertificateTrustChecker* blindCertificateTrustChecker; - WhiteboardSessionManager* whiteboardSessionManager; + WhiteboardSessionManager* whiteboardSessionManager; + ClientBlockListManager* blockListManager; }; } diff --git a/Swiften/Client/ClientBlockListManager.cpp b/Swiften/Client/ClientBlockListManager.cpp index 7222cea..6646a5c 100644 --- a/Swiften/Client/ClientBlockListManager.cpp +++ b/Swiften/Client/ClientBlockListManager.cpp @@ -66,11 +66,14 @@ namespace { } ClientBlockListManager::ClientBlockListManager(IQRouter* iqRouter) : iqRouter(iqRouter) { + } ClientBlockListManager::~ClientBlockListManager() { - unblockResponder->stop(); - blockResponder->stop(); + if (blockList && blockList->getState() == BlockList::Available) { + unblockResponder->stop(); + blockResponder->stop(); + } if (getRequest) { getRequest->onResponse.disconnect(boost::bind(&ClientBlockListManager::handleBlockListReceived, this, _1, _2)); } @@ -88,13 +91,36 @@ boost::shared_ptr ClientBlockListManager::getBlockList() { return blockList; } +GenericRequest::ref ClientBlockListManager::createBlockJIDRequest(const JID& jid) { + return createBlockJIDsRequest(std::vector(1, jid)); +} + +GenericRequest::ref ClientBlockListManager::createBlockJIDsRequest(const std::vector& jids) { + boost::shared_ptr payload = boost::make_shared(jids); + return boost::make_shared< GenericRequest >(IQ::Set, JID(), payload, iqRouter); +} + +GenericRequest::ref ClientBlockListManager::createUnblockJIDRequest(const JID& jid) { + return createUnblockJIDsRequest(std::vector(1, jid)); +} + +GenericRequest::ref ClientBlockListManager::createUnblockJIDsRequest(const std::vector& jids) { + boost::shared_ptr payload = boost::make_shared(jids); + return boost::make_shared< GenericRequest >(IQ::Set, JID(), payload, iqRouter); +} + +GenericRequest::ref ClientBlockListManager::createUnblockAllRequest() { + return createUnblockJIDsRequest(std::vector()); +} + + void ClientBlockListManager::handleBlockListReceived(boost::shared_ptr payload, ErrorPayload::ref error) { if (error || !payload) { blockList->setState(BlockList::Error); } else { - blockList->setState(BlockList::Available); blockList->setItems(payload->getItems()); + blockList->setState(BlockList::Available); blockResponder = boost::make_shared(blockList, iqRouter); blockResponder->start(); unblockResponder = boost::make_shared(blockList, iqRouter); diff --git a/Swiften/Client/ClientBlockListManager.h b/Swiften/Client/ClientBlockListManager.h index 21d35e3..e8d4ac6 100644 --- a/Swiften/Client/ClientBlockListManager.h +++ b/Swiften/Client/ClientBlockListManager.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -25,13 +26,18 @@ namespace Swift { ClientBlockListManager(IQRouter *iqRouter); ~ClientBlockListManager(); - bool isSupported() const; - /** * Returns the blocklist. */ boost::shared_ptr getBlockList(); + GenericRequest::ref createBlockJIDRequest(const JID& jid); + GenericRequest::ref createBlockJIDsRequest(const std::vector& jids); + + GenericRequest::ref createUnblockJIDRequest(const JID& jid); + GenericRequest::ref createUnblockJIDsRequest(const std::vector& jids); + GenericRequest::ref createUnblockAllRequest(); + private: void handleBlockListReceived(boost::shared_ptr payload, ErrorPayload::ref); diff --git a/Swiften/Client/UnitTest/ClientBlockListManagerTest.cpp b/Swiften/Client/UnitTest/ClientBlockListManagerTest.cpp new file mode 100644 index 0000000..9010042 --- /dev/null +++ b/Swiften/Client/UnitTest/ClientBlockListManagerTest.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +using namespace Swift; + +class ClientBlockListManagerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ClientBlockListManagerTest); + CPPUNIT_TEST(testFetchBlockList); + CPPUNIT_TEST(testBlockCommand); + CPPUNIT_TEST(testUnblockCommand); + CPPUNIT_TEST(testUnblockAllCommand); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + ownJID_ = JID("kev@wonderland.lit"); + stanzaChannel_ = new DummyStanzaChannel(); + iqRouter_ = new IQRouter(stanzaChannel_); + iqRouter_->setJID(ownJID_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + } + + void testFetchBlockList() { + std::vector blockJids; + blockJids.push_back(JID("romeo@montague.net")); + blockJids.push_back(JID("iago@shakespeare.lit")); + helperInitialBlockListFetch(blockJids); + + CPPUNIT_ASSERT_EQUAL(static_cast(2), clientBlockListManager_->getBlockList()->getItems().size()); + } + + void testBlockCommand() { + // start with an already fetched block list + helperInitialBlockListFetch(std::vector(1, JID("iago@shakespeare.lit"))); + + CPPUNIT_ASSERT_EQUAL(static_cast(1), clientBlockListManager_->getBlockList()->getItems().size()); + CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState()); + + GenericRequest::ref blockRequest = clientBlockListManager_->createBlockJIDRequest(JID("romeo@montague.net")); + blockRequest->send(); + IQ::ref request = stanzaChannel_->getStanzaAtIndex(2); + CPPUNIT_ASSERT(request.get() != NULL); + boost::shared_ptr blockPayload = request->getPayload(); + CPPUNIT_ASSERT(blockPayload.get() != NULL); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), blockPayload->getItems().at(0)); + + IQ::ref blockRequestResponse = IQ::createResult(request->getFrom(), JID(), request->getID()); + stanzaChannel_->sendIQ(blockRequestResponse); + stanzaChannel_->onIQReceived(blockRequestResponse); + + CPPUNIT_ASSERT_EQUAL(static_cast(1), clientBlockListManager_->getBlockList()->getItems().size()); + + // send block push + boost::shared_ptr pushPayload = boost::make_shared(); + pushPayload->addItem(JID("romeo@montague.net")); + IQ::ref blockPush = IQ::createRequest(IQ::Set, ownJID_, "push1", pushPayload); + stanzaChannel_->sendIQ(blockPush); + stanzaChannel_->onIQReceived(blockPush); + + std::vector blockedJIDs = clientBlockListManager_->getBlockList()->getItems(); + CPPUNIT_ASSERT(blockedJIDs.end() != std::find(blockedJIDs.begin(), blockedJIDs.end(), JID("romeo@montague.net"))); + } + + void testUnblockCommand() { + // start with an already fetched block list + std::vector initialBlockList = std::vector(1, JID("iago@shakespeare.lit")); + initialBlockList.push_back(JID("romeo@montague.net")); + helperInitialBlockListFetch(initialBlockList); + + CPPUNIT_ASSERT_EQUAL(static_cast(2), clientBlockListManager_->getBlockList()->getItems().size()); + CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState()); + + GenericRequest::ref unblockRequest = clientBlockListManager_->createUnblockJIDRequest(JID("romeo@montague.net")); + unblockRequest->send(); + IQ::ref request = stanzaChannel_->getStanzaAtIndex(2); + CPPUNIT_ASSERT(request.get() != NULL); + boost::shared_ptr unblockPayload = request->getPayload(); + CPPUNIT_ASSERT(unblockPayload.get() != NULL); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), unblockPayload->getItems().at(0)); + + IQ::ref unblockRequestResponse = IQ::createResult(request->getFrom(), JID(), request->getID()); + stanzaChannel_->sendIQ(unblockRequestResponse); + stanzaChannel_->onIQReceived(unblockRequestResponse); + + CPPUNIT_ASSERT_EQUAL(static_cast(2), clientBlockListManager_->getBlockList()->getItems().size()); + + // send block push + boost::shared_ptr pushPayload = boost::make_shared(); + pushPayload->addItem(JID("romeo@montague.net")); + IQ::ref unblockPush = IQ::createRequest(IQ::Set, ownJID_, "push1", pushPayload); + stanzaChannel_->sendIQ(unblockPush); + stanzaChannel_->onIQReceived(unblockPush); + + std::vector blockedJIDs = clientBlockListManager_->getBlockList()->getItems(); + CPPUNIT_ASSERT(blockedJIDs.end() == std::find(blockedJIDs.begin(), blockedJIDs.end(), JID("romeo@montague.net"))); + } + + void testUnblockAllCommand() { + // start with an already fetched block list + std::vector initialBlockList = std::vector(1, JID("iago@shakespeare.lit")); + initialBlockList.push_back(JID("romeo@montague.net")); + initialBlockList.push_back(JID("benvolio@montague.net")); + helperInitialBlockListFetch(initialBlockList); + + CPPUNIT_ASSERT_EQUAL(static_cast(3), clientBlockListManager_->getBlockList()->getItems().size()); + CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState()); + + GenericRequest::ref unblockRequest = clientBlockListManager_->createUnblockAllRequest(); + unblockRequest->send(); + IQ::ref request = stanzaChannel_->getStanzaAtIndex(2); + CPPUNIT_ASSERT(request.get() != NULL); + boost::shared_ptr unblockPayload = request->getPayload(); + CPPUNIT_ASSERT(unblockPayload.get() != NULL); + CPPUNIT_ASSERT_EQUAL(true, unblockPayload->getItems().empty()); + + IQ::ref unblockRequestResponse = IQ::createResult(request->getFrom(), JID(), request->getID()); + stanzaChannel_->sendIQ(unblockRequestResponse); + stanzaChannel_->onIQReceived(unblockRequestResponse); + + CPPUNIT_ASSERT_EQUAL(static_cast(3), clientBlockListManager_->getBlockList()->getItems().size()); + + // send block push + boost::shared_ptr pushPayload = boost::make_shared(); + IQ::ref unblockPush = IQ::createRequest(IQ::Set, ownJID_, "push1", pushPayload); + stanzaChannel_->sendIQ(unblockPush); + stanzaChannel_->onIQReceived(unblockPush); + + CPPUNIT_ASSERT_EQUAL(true, clientBlockListManager_->getBlockList()->getItems().empty()); + } + + void tearDown() { + delete clientBlockListManager_; + delete iqRouter_; + delete stanzaChannel_; + } + + private: + void helperInitialBlockListFetch(const std::vector& blockedJids) { + boost::shared_ptr blockList = clientBlockListManager_->getBlockList(); + CPPUNIT_ASSERT(blockList); + + // check for IQ request + IQ::ref request = stanzaChannel_->getStanzaAtIndex(0); + CPPUNIT_ASSERT(request.get() != NULL); + boost::shared_ptr requestPayload = request->getPayload(); + CPPUNIT_ASSERT(requestPayload.get() != NULL); + + CPPUNIT_ASSERT_EQUAL(BlockList::Requesting, blockList->getState()); + CPPUNIT_ASSERT_EQUAL(BlockList::Requesting, clientBlockListManager_->getBlockList()->getState()); + + // build IQ response + boost::shared_ptr responsePayload = boost::make_shared(); + foreach(const JID& jid, blockedJids) { + responsePayload->addItem(jid); + } + + IQ::ref response = IQ::createResult(ownJID_, JID(), request->getID(), responsePayload); + stanzaChannel_->sendIQ(response); + stanzaChannel_->onIQReceived(response); + + CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState()); + CPPUNIT_ASSERT(responsePayload->getItems() == clientBlockListManager_->getBlockList()->getItems()); + } + + + private: + JID ownJID_; + IQRouter* iqRouter_; + DummyStanzaChannel* stanzaChannel_; + ClientBlockListManager* clientBlockListManager_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ClientBlockListManagerTest); + diff --git a/Swiften/Elements/BlockListPayload.h b/Swiften/Elements/BlockListPayload.h index 25cb602..49df75f 100644 --- a/Swiften/Elements/BlockListPayload.h +++ b/Swiften/Elements/BlockListPayload.h @@ -14,7 +14,7 @@ namespace Swift { class BlockListPayload : public Payload { public: - BlockListPayload() { + BlockListPayload(const std::vector& items = std::vector()) : items(items) { } void addItem(const JID& item) { diff --git a/Swiften/Elements/BlockPayload.h b/Swiften/Elements/BlockPayload.h index 6dd5170..49a0463 100644 --- a/Swiften/Elements/BlockPayload.h +++ b/Swiften/Elements/BlockPayload.h @@ -14,7 +14,7 @@ namespace Swift { class BlockPayload : public Payload { public: - BlockPayload() { + BlockPayload(const std::vector& jids = std::vector()) : items(jids) { } void addItem(const JID& jid) { diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp index 1683916..f353c42 100644 --- a/Swiften/Elements/DiscoInfo.cpp +++ b/Swiften/Elements/DiscoInfo.cpp @@ -23,6 +23,7 @@ const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp: const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams"); const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:xmpp:receipts"); const std::string DiscoInfo::WhiteboardFeature = std::string("http://swift.im/whiteboard"); +const std::string DiscoInfo::BlockingCommandFeature = std::string("urn:xmpp:blocking"); bool DiscoInfo::Identity::operator<(const Identity& other) const { if (category_ == other.category_) { diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h index 3334ac8..3701096 100644 --- a/Swiften/Elements/DiscoInfo.h +++ b/Swiften/Elements/DiscoInfo.h @@ -34,6 +34,7 @@ namespace Swift { static const std::string Bytestream; static const std::string MessageDeliveryReceiptsFeature; static const std::string WhiteboardFeature; + static const std::string BlockingCommandFeature; class Identity { public: diff --git a/Swiften/Elements/UnblockPayload.h b/Swiften/Elements/UnblockPayload.h index b6593ab..c5e7c80 100644 --- a/Swiften/Elements/UnblockPayload.h +++ b/Swiften/Elements/UnblockPayload.h @@ -14,7 +14,7 @@ namespace Swift { class UnblockPayload : public Payload { public: - UnblockPayload() { + UnblockPayload(const std::vector& jids = std::vector()) : items(jids) { } void addItem(const JID& item) { diff --git a/Swiften/Parser/PayloadParsers/BlockParser.h b/Swiften/Parser/PayloadParsers/BlockParser.h index cd727ad..6ee47a2 100644 --- a/Swiften/Parser/PayloadParsers/BlockParser.h +++ b/Swiften/Parser/PayloadParsers/BlockParser.h @@ -7,13 +7,14 @@ #pragma once #include +#include #include namespace Swift { template class BlockParser : public GenericPayloadParser { public: - BlockParser() : GenericPayloadParser() { + BlockParser() : GenericPayloadParser(), level(0) { } virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { diff --git a/Swiften/Parser/PayloadParsers/UnitTest/BlockParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/BlockParserTest.cpp new file mode 100644 index 0000000..ddd3a88 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/BlockParserTest.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include +#include + +#include +#include + +#include +#include +#include + +using namespace Swift; + +class BlockParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(BlockParserTest); + CPPUNIT_TEST(testExample4); + CPPUNIT_TEST(testExample6); + CPPUNIT_TEST(testExample10); + CPPUNIT_TEST_SUITE_END(); + + public: + BlockParserTest() {} + + void testExample4() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("" + "" + "" + "")); + + BlockListPayload* payload = dynamic_cast(parser.getPayload().get()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT(2 == payload->getItems().size()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), payload->getItems()[0]); + CPPUNIT_ASSERT_EQUAL(JID("iago@shakespeare.lit"), payload->getItems()[1]); + } + + void testExample6() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("" + "" + "")); + + BlockPayload* payload = dynamic_cast(parser.getPayload().get()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT(1 == payload->getItems().size()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), payload->getItems()[0]); + } + + void testExample10() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("" + "" + "")); + + UnblockPayload* payload = dynamic_cast(parser.getPayload().get()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT(1 == payload->getItems().size()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), payload->getItems()[0]); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BlockParserTest); diff --git a/Swiften/Queries/GenericRequest.h b/Swiften/Queries/GenericRequest.h index 68c86c5..b1b7f8a 100644 --- a/Swiften/Queries/GenericRequest.h +++ b/Swiften/Queries/GenericRequest.h @@ -22,6 +22,9 @@ namespace Swift { template class GenericRequest : public Request { public: + typedef boost::shared_ptr > ref; + + public: /** * Create a request suitable for client use. * @param type Iq type - Get or Set. @@ -61,7 +64,7 @@ namespace Swift { onResponse(boost::dynamic_pointer_cast(payload), error); } - protected: + public: boost::shared_ptr getPayloadGeneric() const { return boost::dynamic_pointer_cast(getPayload()); } diff --git a/Swiften/SConscript b/Swiften/SConscript index a62d344..b9fad17 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -318,6 +318,7 @@ if env["SCONS_STAGE"] == "build" : # File("Chat/UnitTest/ChatStateTrackerTest.cpp"), File("Client/UnitTest/ClientSessionTest.cpp"), File("Client/UnitTest/NickResolverTest.cpp"), + File("Client/UnitTest/ClientBlockListManagerTest.cpp"), File("Compress/UnitTest/ZLibCompressorTest.cpp"), File("Compress/UnitTest/ZLibDecompressorTest.cpp"), File("Component/UnitTest/ComponentHandshakeGeneratorTest.cpp"), @@ -347,6 +348,7 @@ if env["SCONS_STAGE"] == "build" : File("Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp"), File("Network/UnitTest/BOSHConnectionTest.cpp"), File("Network/UnitTest/BOSHConnectionPoolTest.cpp"), + File("Parser/PayloadParsers/UnitTest/BlockParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/DiscoItemsParserTest.cpp"), @@ -401,6 +403,7 @@ if env["SCONS_STAGE"] == "build" : File("Roster/UnitTest/XMPPRosterControllerTest.cpp"), File("Roster/UnitTest/XMPPRosterSignalHandler.cpp"), File("Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.cpp"), + File("Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/BlockSerializer.h b/Swiften/Serializer/PayloadSerializers/BlockSerializer.h index 10ae8b0..fc628c2 100644 --- a/Swiften/Serializer/PayloadSerializers/BlockSerializer.h +++ b/Swiften/Serializer/PayloadSerializers/BlockSerializer.h @@ -8,6 +8,7 @@ #include +#include #include #include @@ -24,6 +25,7 @@ namespace Swift { for (std::vector::const_iterator i = items.begin(); i != items.end(); ++i) { boost::shared_ptr item = boost::make_shared("item"); item->setAttribute("jid", *i); + element.addNode(item); } return element.serialize(); } diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp new file mode 100644 index 0000000..7772381 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include +#include + +#include +#include +#include +#include +#include + +using namespace Swift; + +class BlockSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(BlockSerializerTest); + CPPUNIT_TEST(testExample4); + CPPUNIT_TEST(testExample6); + CPPUNIT_TEST(testExample10); + CPPUNIT_TEST_SUITE_END(); + + public: + BlockSerializerTest() {} + + void testExample4() { + BlockSerializer testling("blocklist"); + boost::shared_ptr blocklist = boost::make_shared(); + blocklist->addItem(JID("romeo@montague.net")); + blocklist->addItem(JID("iago@shakespeare.lit")); + + CPPUNIT_ASSERT_EQUAL(std::string(""), testling.serialize(blocklist)); + } + + void testExample6() { + BlockSerializer testling("block"); + boost::shared_ptr block = boost::make_shared(); + block->addItem(JID("romeo@montague.net")); + + CPPUNIT_ASSERT_EQUAL(std::string(""), testling.serialize(block)); + } + + void testExample10() { + BlockSerializer testling("unblock"); + boost::shared_ptr unblock = boost::make_shared(); + unblock->addItem(JID("romeo@montague.net")); + + CPPUNIT_ASSERT_EQUAL(std::string(""), testling.serialize(unblock)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BlockSerializerTest); -- cgit v0.10.2-6-g49f6