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