From 321215840b51f30c1b4e99ca62852a17845dd406 Mon Sep 17 00:00:00 2001
From: Jan Kaluza <jkaluza@redhat.com>
Date: Thu, 4 Feb 2016 06:55:14 +0100
Subject: [PATCH] Fix #92 - Handle the XMPP user's VCard change and forward it
 to the backend.

---
 include/transport/User.h                 |  6 ++++
 libtransport/NetworkPluginServer.cpp     |  5 +++
 libtransport/User.cpp                    | 17 +++++++++++
 spectrum/src/frontends/xmpp/XMPPUser.cpp | 25 +++++++++++++++
 spectrum/src/frontends/xmpp/XMPPUser.h   |  5 ++-
 tests/libtransport/user.cpp              | 39 ++++++++++++++++++++++++
 6 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/include/transport/User.h b/include/transport/User.h
index d0bd7f7b..e65e6a4d 100644
--- a/include/transport/User.h
+++ b/include/transport/User.h
@@ -76,6 +76,7 @@ class User {
 		UserManager *getUserManager() { return m_userManager; }
 		
 		virtual void disconnectUser(const std::string &error, Swift::SpectrumErrorPayload::Error e) = 0;
+		virtual void requestVCard() {}
 
 		void setData(void *data) { m_data = data; }
 		void *getData() { return m_data; }
@@ -138,6 +139,10 @@ class User {
 			m_reconnectLimit = limit;
 		}
 
+		void setStorageBackend(StorageBackend *storageBackend) {
+			m_storageBackend = storageBackend;
+		}
+
 		void leaveRoom(const std::string &room);
 
 		boost::signal<void ()> onReadyToConnect;
@@ -172,6 +177,7 @@ class User {
 		std::map<std::string, std::string> m_settings;
 		bool m_cacheMessages;
 		int m_reconnectLimit;
+		StorageBackend *m_storageBackend;
 };
 
 }
diff --git a/libtransport/NetworkPluginServer.cpp b/libtransport/NetworkPluginServer.cpp
index 6871eded..b4a9e5eb 100644
--- a/libtransport/NetworkPluginServer.cpp
+++ b/libtransport/NetworkPluginServer.cpp
@@ -1842,6 +1842,11 @@ void NetworkPluginServer::handleBlockToggled(Buddy *b) {
 
 
 void NetworkPluginServer::handleVCardUpdated(User *user, boost::shared_ptr<Swift::VCard> v) {
+	if (!v) {
+		LOG4CXX_INFO(logger, user->getJID().toString() << ": Received empty VCard");
+		return;
+	}
+
 	pbnetwork::VCard vcard;
 	vcard.set_username(user->getJID().toBare());
 	vcard.set_buddyname("");
diff --git a/libtransport/User.cpp b/libtransport/User.cpp
index b92981fc..ee35157c 100644
--- a/libtransport/User.cpp
+++ b/libtransport/User.cpp
@@ -34,6 +34,7 @@
 #include "Swiften/Elements/MUCPayload.h"
 #include "Swiften/Elements/SpectrumErrorPayload.h"
 #include "Swiften/Elements/CapsInfo.h"
+#include "Swiften/Elements/VCardUpdate.h"
 #include <boost/foreach.hpp>
 #include <stdio.h>
 #include <stdlib.h>
@@ -67,6 +68,7 @@ User::User(const Swift::JID &jid, UserInfo &userInfo, Component *component, User
 
 	m_rosterManager = component->getFrontend()->createRosterManager(this, m_component);
 	m_conversationManager = new ConversationManager(this, m_component);
+
 	LOG4CXX_INFO(logger, m_jid.toString() << ": Created");
 	updateLastActivity();
 }
@@ -170,6 +172,21 @@ void User::leaveRoom(const std::string &room) {
 
 void User::handlePresence(Swift::Presence::ref presence, bool forceJoin) {
 	LOG4CXX_INFO(logger, "PRESENCE " << presence->getFrom().toString() << " " << presence->getTo().toString());
+
+	if (m_storageBackend) {
+		boost::shared_ptr<Swift::VCardUpdate> vcardUpdate = presence->getPayload<Swift::VCardUpdate>();
+		if (vcardUpdate) {
+			std::string value = "";
+			int type = (int) TYPE_STRING;
+			m_storageBackend->getUserSetting(m_userInfo.id, "photohash", type, value);
+			if (value != vcardUpdate->getPhotoHash()) {
+				LOG4CXX_INFO(logger, m_jid.toString() << ": Requesting VCard")
+				m_storageBackend->updateUserSetting(m_userInfo.id, "photohash", vcardUpdate->getPhotoHash());
+				requestVCard();
+			}
+		}
+	}
+
 	if (!m_connected) {
 		// we are not connected to legacy network, so we should do it when disco#info arrive :)
 		if (m_readyForConnect == false) {
diff --git a/spectrum/src/frontends/xmpp/XMPPUser.cpp b/spectrum/src/frontends/xmpp/XMPPUser.cpp
index e0353b6d..ba0f113a 100644
--- a/spectrum/src/frontends/xmpp/XMPPUser.cpp
+++ b/spectrum/src/frontends/xmpp/XMPPUser.cpp
@@ -26,6 +26,7 @@
 #include "Swiften/Server/ServerStanzaChannel.h"
 #include "Swiften/Elements/StreamError.h"
 #include "Swiften/Elements/SpectrumErrorPayload.h"
+#include "Swiften/Queries/IQRouter.h"
 #include <boost/foreach.hpp>
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,6 +55,15 @@ XMPPUser::~XMPPUser(){
 		dynamic_cast<Swift::ServerStanzaChannel *>(static_cast<XMPPFrontend*>(m_component->getFrontend())->getStanzaChannel())->finishSession(m_jid, boost::shared_ptr<Swift::Element>());
 #endif
 	}
+
+	if (m_vcardRequests.size() != 0) {
+		LOG4CXX_INFO(logger, m_jid.toString() <<  ": Removing " << m_vcardRequests.size() << " unresponded IQs");
+		BOOST_FOREACH(Swift::GetVCardRequest::ref request, m_vcardRequests) {
+			request->onResponse.disconnect_all_slots();
+			static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter()->removeHandler(request);
+		}
+		m_vcardRequests.clear();
+	}
 }
 
 void XMPPUser::disconnectUser(const std::string &error, Swift::SpectrumErrorPayload::Error e) {
@@ -73,5 +83,20 @@ void XMPPUser::disconnectUser(const std::string &error, Swift::SpectrumErrorPayl
 	}
 }
 
+void XMPPUser::handleVCardReceived(boost::shared_ptr<Swift::VCard> vcard, Swift::ErrorPayload::ref error, Swift::GetVCardRequest::ref request) {
+	m_vcardRequests.remove(request);
+	request->onResponse.disconnect_all_slots();
+	m_component->getFrontend()->onVCardUpdated(this, vcard);
+}
+
+void XMPPUser::requestVCard() {
+	LOG4CXX_INFO(logger, m_jid.toString() << ": Requesting VCard");
+
+	Swift::GetVCardRequest::ref request = Swift::GetVCardRequest::create(m_jid, static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter());
+	request->onResponse.connect(boost::bind(&XMPPUser::handleVCardReceived, this, _1, _2, request));
+	request->send();
+	m_vcardRequests.push_back(request);
+}
+
 
 }
diff --git a/spectrum/src/frontends/xmpp/XMPPUser.h b/spectrum/src/frontends/xmpp/XMPPUser.h
index 9ecdc0ba..29e62432 100644
--- a/spectrum/src/frontends/xmpp/XMPPUser.h
+++ b/spectrum/src/frontends/xmpp/XMPPUser.h
@@ -29,6 +29,7 @@
 #include "Swiften/Elements/SpectrumErrorPayload.h"
 #include "Swiften/Network/Timer.h"
 #include "Swiften/Network/Connection.h"
+#include "Swiften/VCards/GetVCardRequest.h"
 
 namespace Transport {
 
@@ -54,15 +55,17 @@ class XMPPUser : public User {
 
 		void disconnectUser(const std::string &error, Swift::SpectrumErrorPayload::Error e);
 
-
+		void requestVCard();
 
 	private:
 		void onConnectingTimeout();
+		void handleVCardReceived(boost::shared_ptr<Swift::VCard> vcard, Swift::ErrorPayload::ref error, Swift::GetVCardRequest::ref request);
 
 		Swift::JID m_jid;
 		Component *m_component;
 		UserManager *m_userManager;
 		UserInfo m_userInfo;
+		std::list <Swift::GetVCardRequest::ref> m_vcardRequests;
 };
 
 }
diff --git a/tests/libtransport/user.cpp b/tests/libtransport/user.cpp
index 8d40f277..9c7191e9 100644
--- a/tests/libtransport/user.cpp
+++ b/tests/libtransport/user.cpp
@@ -30,12 +30,14 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest {
 	CPPUNIT_TEST(handleDisconnectedReconnect);
 	CPPUNIT_TEST(joinRoomHandleDisconnectedRejoin);
 	CPPUNIT_TEST(joinRoomAfterFlagNotAuthorized);
+	CPPUNIT_TEST(requestVCard);
 	CPPUNIT_TEST_SUITE_END();
 
 	public:
 		std::string room;
 		std::string roomNickname;
 		std::string roomPassword;
+		std::string photo;
 		bool readyToConnect;
 		bool disconnected;
 		Swift::Presence::ref changedPresence;
@@ -47,11 +49,14 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest {
 			room = "";
 			roomNickname = "";
 			roomPassword = "";
+			photo = "";
 
 			setMeUp();
 			userManager->onUserCreated.connect(boost::bind(&UserTest::handleUserCreated, this, _1));
 			connectUser();
 			received.clear();
+
+			frontend->onVCardUpdated.connect(boost::bind(&UserTest::handleVCardUpdated, this, _1, _2));
 		}
 
 		void tearDown (void) {
@@ -63,6 +68,10 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest {
 			tearMeDown();
 		}
 
+	void handleVCardUpdated(User *user, boost::shared_ptr<Swift::VCard> v) {
+		photo = Swift::byteArrayToString(v->getPhoto());
+	}
+
 	void handleUserCreated(User *user) {
 		user->onReadyToConnect.connect(boost::bind(&UserTest::handleUserReadyToConnect, this, user));
 		user->onPresenceChanged.connect(boost::bind(&UserTest::handleUserPresenceChanged, this, user, _1));
@@ -472,6 +481,36 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest {
 		handlePresenceJoinRoom();
 	}
 
+	void requestVCard() {
+		User *user = userManager->getUser("user@localhost");
+		user->setStorageBackend(storage);
+
+		Swift::Presence::ref response = Swift::Presence::create();
+		response->setTo("localhost");
+		response->setFrom("user@localhost/resource");
+		response->addPayload(boost::shared_ptr<Swift::Payload>(new Swift::VCardUpdate("hash")));
+
+		injectPresence(response);
+		loop->processEvents();
+
+		CPPUNIT_ASSERT_EQUAL(2, (int) received.size());
+		Swift::VCard::ref payload1 = getStanza(received[1])->getPayload<Swift::VCard>();
+		CPPUNIT_ASSERT(payload1);
+
+		boost::shared_ptr<Swift::VCard> vcard(new Swift::VCard());
+		vcard->setPhoto(Swift::createByteArray("photo"));
+		injectIQ(Swift::IQ::createResult(getStanza(received[1])->getFrom(), getStanza(received[1])->getTo(), getStanza(received[1])->getID(), vcard));
+		loop->processEvents();
+
+		CPPUNIT_ASSERT_EQUAL(2, (int) received.size());
+		CPPUNIT_ASSERT_EQUAL(std::string("photo"), photo);
+
+		received.clear();
+		injectPresence(response);
+		loop->processEvents();
+		CPPUNIT_ASSERT_EQUAL(1, (int) received.size());
+	}
+
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION (UserTest);