From 5a91a3ef54c00a6d4d960725f2ff84b5e0c43cab Mon Sep 17 00:00:00 2001 From: Kevin Smith <git@kismith.co.uk> Date: Sat, 18 Sep 2010 21:58:48 +0100 Subject: Use caps for enabling chat state notifications. Resolves: #93 diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 7fbf677..90ca7f8 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -7,12 +7,14 @@ #include "Swift/Controllers/Chat/ChatController.h" #include <boost/bind.hpp> +#include <stdio.h> #include "Swiften/Avatars/AvatarManager.h" #include "Swiften/Chat/ChatStateNotifier.h" #include "Swiften/Chat/ChatStateMessageSender.h" #include "Swiften/Chat/ChatStateTracker.h" #include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Disco/EntityCapsManager.h" #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" #include "Swift/Controllers/NickResolver.h" @@ -23,11 +25,14 @@ 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) +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, EntityCapsManager* entityCapsManager) : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory) { isInMUC_ = isInMUC; lastWasPresence_ = false; + entityCapsManager_ = entityCapsManager; chatStateNotifier_ = new ChatStateNotifier(); + entityCapsManager_->onCapsChanged.connect(boost::bind(&ChatController::handleCapsChanged, this, _1)); + handleCapsChanged(toJID_); chatStateMessageSender_ = new ChatStateMessageSender(chatStateNotifier_, stanzaChannel, contact); chatStateTracker_ = new ChatStateTracker(); nickResolver_ = nickResolver; @@ -62,9 +67,19 @@ ChatController::~ChatController() { delete chatStateTracker_; } +void ChatController::handleCapsChanged(const JID& jid) { + if (jid == toJID_) { + DiscoInfo::ref caps = entityCapsManager_->getCaps(toJID_); + bool hasCSN = caps && caps->hasFeature(ChatState::getFeatureNamespace()); + chatStateNotifier_->setContactHas85Caps(hasCSN); + } +} + void ChatController::setToJID(const JID& jid) { + chatStateNotifier_->contactJIDHasChanged(); chatStateMessageSender_->setContact(jid); ChatControllerBase::setToJID(jid); + handleCapsChanged(jid); } bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { @@ -77,7 +92,7 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me JID from = message->getFrom(); if (!from.equals(toJID_, JID::WithResource)) { if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){ - toJID_ = from; + setToJID(from); } } chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>()); diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 6cb1443..1e530ac 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -15,9 +15,10 @@ namespace Swift { class ChatStateMessageSender; class ChatStateTracker; class NickResolver; + class EntityCapsManager; 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); + 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, EntityCapsManager* entityCapsManager); virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setEnabled(bool enabled); @@ -33,13 +34,14 @@ namespace Swift { virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const; void handleStanzaAcked(boost::shared_ptr<Stanza> stanza); void dayTicked() {lastWasPresence_ = false;} + void handleCapsChanged(const JID& jid); private: NickResolver* nickResolver_; - JID contact_; ChatStateNotifier* chatStateNotifier_; ChatStateMessageSender* chatStateMessageSender_; ChatStateTracker* chatStateTracker_; + EntityCapsManager* entityCapsManager_; bool isInMUC_; bool lastWasPresence_; String lastStatusChangeString_; diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index bd4fcb8..8c93120 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -26,7 +26,7 @@ namespace Swift { typedef std::pair<JID, ChatController*> JIDChatControllerPair; typedef std::pair<JID, MUCController*> JIDMUCControllerPair; -ChatsManager::ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, boost::shared_ptr<DiscoInfo> serverDiscoInfo, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry) : jid_(jid), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry) { +ChatsManager::ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, boost::shared_ptr<DiscoInfo> serverDiscoInfo, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsManager* entityCapsManager) : jid_(jid), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry), entityCapsManager_(entityCapsManager) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -183,7 +183,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) } ChatController* ChatsManager::createNewChatController(const JID& contact) { - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_); + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsManager_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); return controller; diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 17a5d94..1e31458 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -34,10 +34,11 @@ namespace Swift { class ChatListWindow; class ChatListWindowFactory; class TimerFactory; + class EntityCapsManager; class ChatsManager { public: - ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, boost::shared_ptr<DiscoInfo> serverDiscoInfo, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry); + ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, boost::shared_ptr<DiscoInfo> serverDiscoInfo, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsManager* entityCapsManager); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setEnabled(bool enabled); @@ -76,5 +77,6 @@ namespace Swift { bool useDelayForLatency_; TimerFactory* timerFactory_; MUCRegistry* mucRegistry_; + EntityCapsManager* entityCapsManager_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index a17575c..e770e88 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -14,6 +14,8 @@ #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" #include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h" #include "Swiften/Client/Client.h" +#include "Swiften/Disco/EntityCapsManager.h" +#include "Swiften/Disco/CapsProvider.h" #include "Swift/Controllers/Chat/ChatController.h" #include "Swift/Controllers/XMPPEvents/EventController.h" #include "Swift/Controllers/Chat/MUCController.h" @@ -35,6 +37,10 @@ using namespace Swift; +class DummyCapsProvider : public CapsProvider { + DiscoInfo::ref getCaps(const String&) const {return DiscoInfo::ref(new DiscoInfo());} +}; + class ChatsManagerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ChatsManagerTest); @@ -57,6 +63,7 @@ public: stanzaChannel_ = new DummyStanzaChannel(); iqChannel_ = new DummyIQChannel(); iqRouter_ = new IQRouter(iqChannel_); + capsProvider_ = new DummyCapsProvider(); eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); xmppRoster_ = new XMPPRoster(); @@ -66,9 +73,10 @@ public: serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>(new DiscoInfo()); presenceSender_ = new PresenceSender(stanzaChannel_); uiEventStream_ = new UIEventStream(); + entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createWindow).With(uiEventStream_).Return(NULL); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_); avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); @@ -89,6 +97,8 @@ public: delete iqRouter_; delete uiEventStream_; delete xmppRoster_; + delete entityCapsManager_; + delete capsProvider_; } void testFirstOpenWindowIncoming() { @@ -319,6 +329,8 @@ private: UIEventStream* uiEventStream_; ChatListWindowFactory* chatListWindowFactory_; MUCRegistry* mucRegistry_; + EntityCapsManager* entityCapsManager_; + CapsProvider* capsProvider_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 3cfa2a7..fdfab98 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -44,6 +44,7 @@ #include "Swiften/Base/String.h" #include "Swiften/Client/Client.h" #include "Swiften/Presence/PresenceSender.h" +#include "Swiften/Elements/ChatState.h" #include "Swiften/Elements/Presence.h" #include "Swiften/Elements/VCardUpdate.h" #include "Swiften/Queries/Responders/SoftwareVersionResponder.h" @@ -257,7 +258,7 @@ void MainController::handleConnected() { rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); - chatsManager_ = new ChatsManager(jid_, client_, client_->getIQRouter(), eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, useDelayForLatency_, &timerFactory_, mucRegistry_); + chatsManager_ = new ChatsManager(jid_, client_, client_->getIQRouter(), eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, useDelayForLatency_, &timerFactory_, mucRegistry_, entityCapsManager_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(avatarManager_); @@ -271,6 +272,7 @@ void MainController::handleConnected() { DiscoInfo discoInfo; discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc")); discoInfo.addFeature("urn:xmpp:sec-label:0"); + discoInfo.addFeature(ChatState::getFeatureNamespace()); capsInfo_ = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator(CLIENT_NODE).generateCapsInfo(discoInfo))); discoResponder_ = new DiscoInfoResponder(client_->getIQRouter()); diff --git a/Swiften/Chat/ChatStateNotifier.cpp b/Swiften/Chat/ChatStateNotifier.cpp index 5024958..667c244 100644 --- a/Swiften/Chat/ChatStateNotifier.cpp +++ b/Swiften/Chat/ChatStateNotifier.cpp @@ -9,18 +9,22 @@ namespace Swift { ChatStateNotifier::ChatStateNotifier() { - contactHas85Caps_ = false; - isInConversation_ = false; - contactHasSentActive_ = false; - userIsTyping_ = false; + contactJIDHasChanged(); } void ChatStateNotifier::setContactHas85Caps(bool hasCaps) { contactHas85Caps_ = hasCaps; } +void ChatStateNotifier::contactJIDHasChanged() { + contactHasSentActive_ = false; + contactHas85Caps_ = false; + userIsTyping_ = false; +} + void ChatStateNotifier::setUserIsTyping() { - if (contactShouldReceiveStates() && !userIsTyping_) { + bool should = contactShouldReceiveStates(); + if (should && !userIsTyping_) { userIsTyping_ = true; onChatStateChanged(ChatState::Composing); } @@ -38,14 +42,15 @@ void ChatStateNotifier::userCancelledNewMessage() { } void ChatStateNotifier::receivedMessageFromContact(bool hasActiveElement) { - isInConversation_ = true; contactHasSentActive_ = hasActiveElement; } bool ChatStateNotifier::contactShouldReceiveStates() { /* So, yes, the XEP says to look at caps, but it also says that once you've - heard from the contact, the active state overrides this.*/ - return contactHasSentActive_ || (contactHas85Caps_ && !isInConversation_);; + heard from the contact, the active state overrides this. + *HOWEVER* it says that the MUST NOT send csn if you haven't received + active is OPTIONAL behaviour for if you haven't got caps.*/ + return contactHasSentActive_ || contactHas85Caps_ ; } } diff --git a/Swiften/Chat/ChatStateNotifier.h b/Swiften/Chat/ChatStateNotifier.h index 71febfa..8dcd5cd 100644 --- a/Swiften/Chat/ChatStateNotifier.h +++ b/Swiften/Chat/ChatStateNotifier.h @@ -21,11 +21,11 @@ namespace Swift { void userCancelledNewMessage(); void receivedMessageFromContact(bool hasActiveElement); bool contactShouldReceiveStates(); + void contactJIDHasChanged(); boost::signal<void (ChatState::ChatStateType)> onChatStateChanged; private: bool contactHas85Caps_; - bool isInConversation_; bool contactHasSentActive_; bool userIsTyping_; }; diff --git a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp index 712ba10..2fa4c26 100644 --- a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp +++ b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp @@ -114,7 +114,11 @@ public: void testContactShouldReceiveStates_ActiveOverrideOff() { notifier_->setContactHas85Caps(true); notifier_->receivedMessageFromContact(false); - CPPUNIT_ASSERT_EQUAL(false, notifier_->contactShouldReceiveStates()); + /* I originally read the MUST NOT send after receiving without Active and + * thought this should check for false, but I later found it was OPTIONAL + * (MAY) behaviour only for if you didn't receive caps. + */ + CPPUNIT_ASSERT_EQUAL(true, notifier_->contactShouldReceiveStates()); } diff --git a/Swiften/Elements/ChatState.h b/Swiften/Elements/ChatState.h index 8dcf77c..8a96bf1 100644 --- a/Swiften/Elements/ChatState.h +++ b/Swiften/Elements/ChatState.h @@ -19,7 +19,7 @@ namespace Swift { ChatStateType getChatState() { return state_; } void setChatState(ChatStateType state) {state_ = state;} - + static String getFeatureNamespace() {return "http://jabber.org/protocol/chatstates";} private: ChatStateType state_; }; -- cgit v0.10.2-6-g49f6