From 042c12c31cda77b3d57ed41d5c121a2b4383e69a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Tue, 24 Aug 2010 09:26:43 +0200
Subject: Split out VCardUpdateAvatarManager from AvatarManager.


diff --git a/Swiften/Avatars/AvatarManager.cpp b/Swiften/Avatars/AvatarManager.cpp
index 33b1bee..909b07c 100644
--- a/Swiften/Avatars/AvatarManager.cpp
+++ b/Swiften/Avatars/AvatarManager.cpp
@@ -8,92 +8,25 @@
 
 #include <boost/bind.hpp>
 
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swiften/Elements/VCardUpdate.h"
-#include "Swiften/Queries/Requests/GetVCardRequest.h"
-#include "Swiften/StringCodecs/SHA1.h"
-#include "Swiften/StringCodecs/Hexify.h"
-#include "Swiften/Avatars/AvatarStorage.h"
-#include "Swiften/MUC/MUCRegistry.h"
-#include "Swiften/VCards/VCardManager.h"
+#include "Swiften/Avatars/VCardUpdateAvatarManager.h"
 
 namespace Swift {
 
-AvatarManager::AvatarManager(VCardManager* vcardManager, StanzaChannel* stanzaChannel, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) : vcardManager_(vcardManager), stanzaChannel_(stanzaChannel), avatarStorage_(avatarStorage), mucRegistry_(mucRegistry) {
-	stanzaChannel->onPresenceReceived.connect(boost::bind(&AvatarManager::handlePresenceReceived, this, _1));
-	vcardManager_->onVCardChanged.connect(boost::bind(&AvatarManager::handleVCardChanged, this, _1, _2));
+AvatarManager::AvatarManager(VCardManager* vcardManager, StanzaChannel* stanzaChannel, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) {
+	vcardUpdateAvatarManager = new VCardUpdateAvatarManager(vcardManager, stanzaChannel, avatarStorage, mucRegistry);
+	vcardUpdateAvatarManager->onAvatarChanged.connect(boost::ref(onAvatarChanged));
 }
 
 AvatarManager::~AvatarManager() {
-
+	delete vcardUpdateAvatarManager;
 }
 
 void AvatarManager::setMUCRegistry(MUCRegistry* mucRegistry) {
-	mucRegistry_ = mucRegistry;
-}
-
-void AvatarManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
-	boost::shared_ptr<VCardUpdate> update = presence->getPayload<VCardUpdate>();
-	if (!update || presence->getPayload<ErrorPayload>()) {
-		return;
-	}
-	JID from = getAvatarJID(presence->getFrom());
-	if (getAvatarHash(from) == update->getPhotoHash()) {
-		return;
-	}
-	if (avatarStorage_->hasAvatar(update->getPhotoHash())) {
-		setAvatarHash(from, update->getPhotoHash());
-	}
-	else {
-		vcardManager_->requestVCard(from);
-	}
-}
-
-void AvatarManager::handleVCardChanged(const JID& from, VCard::ref vCard) {
-	if (!vCard) {
-		std::cerr << "Warning: " << from << ": null vcard payload" << std::endl;
-		return;
-	}
-
-	String hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
-	avatarStorage_->addAvatar(hash, vCard->getPhoto());
-	setAvatarHash(from, hash);
-}
-
-void AvatarManager::setAvatarHash(const JID& from, const String& hash) {
-	avatarHashes_[from] = hash;
-	onAvatarChanged(from, hash);
-}
-
-/*
-void AvatarManager::setAvatar(const JID& jid, const ByteArray& avatar) {
-	String hash = Hexify::hexify(SHA1::getHash(avatar));
-	avatarStorage_->addAvatar(hash, avatar);
-	setAvatarHash(getAvatarJID(jid), hash);
-}
-*/
-
-String AvatarManager::getAvatarHash(const JID& jid) const {
-	std::map<JID, String>::const_iterator i = avatarHashes_.find(getAvatarJID(jid));
-	if (i != avatarHashes_.end()) {
-		return i->second;
-	}
-	else {
-		return "";
-	}
+	vcardUpdateAvatarManager->setMUCRegistry(mucRegistry);
 }
 
 boost::filesystem::path AvatarManager::getAvatarPath(const JID& jid) const {
-	String hash = getAvatarHash(jid);
-	if (!hash.isEmpty()) {
-		return avatarStorage_->getAvatarPath(hash);
-	}
-	return boost::filesystem::path();
-}
-
-JID AvatarManager::getAvatarJID(const JID& jid) const {
-	JID bareFrom = jid.toBare();
-	return (mucRegistry_ && mucRegistry_->isMUC(bareFrom)) ? jid : bareFrom;
+	return vcardUpdateAvatarManager->getAvatarPath(jid);
 }
 
 
diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h
index 543d167..d3d593d 100644
--- a/Swiften/Avatars/AvatarManager.h
+++ b/Swiften/Avatars/AvatarManager.h
@@ -22,6 +22,7 @@ namespace Swift {
 	class AvatarStorage;
 	class StanzaChannel;
 	class VCardManager;
+	class VCardUpdateAvatarManager;
 
 	class AvatarManager {
 		public:
@@ -32,23 +33,10 @@ namespace Swift {
 
 			virtual boost::filesystem::path getAvatarPath(const JID&) const;
 
-//			virtual void setAvatar(const JID&, const ByteArray& avatar);*/
-
 		public:
 			boost::signal<void (const JID&, const String& /*hash*/)> onAvatarChanged;
 
 		private:
-			void handlePresenceReceived(boost::shared_ptr<Presence>);
-			void handleVCardChanged(const JID& from, VCard::ref);
-			void setAvatarHash(const JID& from, const String& hash);
-			JID getAvatarJID(const JID& o) const;
-			String getAvatarHash(const JID&) const;
-
-		private:
-			VCardManager* vcardManager_;
-			StanzaChannel* stanzaChannel_;
-			AvatarStorage* avatarStorage_;
-			MUCRegistry* mucRegistry_;
-			std::map<JID, String> avatarHashes_;
+			VCardUpdateAvatarManager* vcardUpdateAvatarManager;
 	};
 }
diff --git a/Swiften/Avatars/AvatarMemoryStorage.h b/Swiften/Avatars/AvatarMemoryStorage.h
index f60f603..13fbd9b 100644
--- a/Swiften/Avatars/AvatarMemoryStorage.h
+++ b/Swiften/Avatars/AvatarMemoryStorage.h
@@ -22,7 +22,7 @@ namespace Swift {
 				return i == avatars.end() ? ByteArray() : i->second;
 			}
 
-			virtual boost::filesystem::path getAvatarPath(const String& hash) const {
+			virtual boost::filesystem::path getAvatarPath(const String& /*hash*/) const {
 				return boost::filesystem::path();
 			}
 
diff --git a/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp
deleted file mode 100644
index 858d257..0000000
--- a/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include <cppunit/extensions/HelperMacros.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include <boost/bind.hpp>
-
-#include "Swiften/Elements/VCardUpdate.h"
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Avatars/AvatarMemoryStorage.h"
-#include "Swiften/VCards/VCardMemoryStorage.h"
-#include "Swiften/VCards/VCardManager.h"
-#include "Swiften/MUC/MUCRegistry.h"
-#include "Swiften/Queries/IQRouter.h"
-#include "Swiften/Client/DummyStanzaChannel.h"
-#include "Swiften/StringCodecs/SHA1.h"
-#include "Swiften/StringCodecs/Hexify.h"
-
-using namespace Swift;
-
-class AvatarManagerTest : public CppUnit::TestFixture {
-		CPPUNIT_TEST_SUITE(AvatarManagerTest);
-		CPPUNIT_TEST(testUpdate_NewHashNewVCardRequestsVCard);
-		CPPUNIT_TEST(testUpdate_NewHashStoresAvatarAndEmitsNotificationOnVCardReceive);
-		CPPUNIT_TEST(testUpdate_KnownHash);
-		CPPUNIT_TEST(testUpdate_KnownHashFromDifferentUserDoesNotRequestVCardButTriggersNotification);
-		/*&
-		CPPUNIT_TEST(testUpdate_UpdateNewHashAlreadyHaveAvatar);
-		CPPUNIT_TEST(testUpdate_UpdateNewHashFromMUC);
-		CPPUNIT_TEST(testUpdate_UpdateSameHash);*/
-		//CPPUNIT_TEST(testUpdate_UpdateWithError);
-		/*
-		CPPUNIT_TEST(testUpdate_UpdateNewHashSameThanOtherUser);
-		CPPUNIT_TEST(testReceiveVCard);
-		CPPUNIT_TEST(testGetAvatarPath);
-		CPPUNIT_TEST(testGetAvatarPathFromMUC);*/
-		CPPUNIT_TEST_SUITE_END();
-
-	public:
-		void setUp() {
-			ownJID = JID("foo@fum.com/bum");
-			stanzaChannel = new DummyStanzaChannel();
-			iqRouter = new IQRouter(stanzaChannel);
-			mucRegistry = new DummyMUCRegistry();
-			avatarStorage = new AvatarMemoryStorage();
-			vcardStorage = new VCardMemoryStorage();
-			vcardManager = new VCardManager(ownJID, iqRouter, vcardStorage);
-			avatar1 = ByteArray("abcdefg");
-			avatar1Hash = Hexify::hexify(SHA1::getHash(avatar1));
-			user1 = JID("user1@bar.com/bla");
-			user2 = JID("user2@foo.com/baz");
-		}
-
-		void tearDown() {
-			delete vcardManager;
-			delete vcardStorage;
-			delete avatarStorage;
-			delete mucRegistry;
-			delete iqRouter;
-			delete stanzaChannel;
-		}
-
-		void testUpdate_NewHashNewVCardRequestsVCard() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
-
-			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
-			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, user1.toBare(), IQ::Get));
-		}
-
-		void testUpdate_NewHashStoresAvatarAndEmitsNotificationOnVCardReceive() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
-			stanzaChannel->onIQReceived(createVCardResult(avatar1));
-
-			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
-			CPPUNIT_ASSERT_EQUAL(user1.toBare(), changes[0].first);
-			CPPUNIT_ASSERT_EQUAL(avatar1Hash, changes[0].second);
-			CPPUNIT_ASSERT(avatarStorage->hasAvatar(avatar1Hash));
-			CPPUNIT_ASSERT_EQUAL(avatar1, avatarStorage->getAvatar(avatar1Hash));
-		}
-
-		void testUpdate_KnownHash() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
-			stanzaChannel->onIQReceived(createVCardResult(avatar1));
-			changes.clear();
-			stanzaChannel->sentStanzas.clear();
-
-			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
-
-			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
-			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
-		}
-
-		void testUpdate_KnownHashFromDifferentUserDoesNotRequestVCardButTriggersNotification() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
-			stanzaChannel->onIQReceived(createVCardResult(avatar1));
-			changes.clear();
-			stanzaChannel->sentStanzas.clear();
-
-			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user2, avatar1Hash));
-
-			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
-			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
-			CPPUNIT_ASSERT_EQUAL(user2.toBare(), changes[0].first);
-			CPPUNIT_ASSERT_EQUAL(avatar1Hash, changes[0].second);
-		}
-
-/*
-		void testUpdate_UpdateNewHashFromMUC() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-		}
-
-		*/
-
-		/*void testUpdate_UpdateWithError() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-			boost::shared_ptr<Presence> update = createPresenceWithPhotoHash();
-			update->addPayload(boost::shared_ptr<ErrorPayload>(new ErrorPayload()));
-			stanzaChannel_->onPresenceReceived(update);
-
-			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel_->sentStanzas.size()));
-		}*/
-
-
-	private:
-		std::auto_ptr<AvatarManager> createManager() {
-			std::auto_ptr<AvatarManager> result(new AvatarManager(vcardManager, stanzaChannel, avatarStorage, mucRegistry));
-			result->onAvatarChanged.connect(boost::bind(&AvatarManagerTest::handleAvatarChanged, this, _1, _2));
-			return result;
-		}
-
-		boost::shared_ptr<Presence> createPresenceWithPhotoHash(const JID& jid, const String& hash) {
-			boost::shared_ptr<Presence> presence(new Presence());
-			presence->setFrom(jid);
-			presence->addPayload(boost::shared_ptr<VCardUpdate>(new VCardUpdate(hash)));
-			return presence;
-		}
-
-		IQ::ref createVCardResult(const ByteArray& avatar) {
-			VCard::ref vcard(new VCard());
-			vcard->setPhoto(avatar);
-			return IQ::createResult(JID("baz@fum.com"), stanzaChannel->sentStanzas[0]->getID(), vcard);
-		}
-
-		void handleAvatarChanged(const JID& jid, const String& hash) {
-			changes.push_back(std::pair<JID,String>(jid, hash));
-		}
-
-	private:
-		struct DummyMUCRegistry : public MUCRegistry {
-			bool isMUC(const JID& jid) const { return std::find(mucs_.begin(), mucs_.end(), jid) != mucs_.end(); }
-			std::vector<JID> mucs_;
-		};
-
-		JID ownJID;
-		DummyStanzaChannel* stanzaChannel;
-		IQRouter* iqRouter;
-		DummyMUCRegistry* mucRegistry;
-		AvatarMemoryStorage* avatarStorage;
-		VCardManager* vcardManager;
-		VCardMemoryStorage* vcardStorage;
-		ByteArray avatar1;
-		String avatar1Hash;
-		std::vector<std::pair<JID,String> > changes;
-		JID user1;
-		JID user2;
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(AvatarManagerTest);
diff --git a/Swiften/Avatars/UnitTest/VCardUpdateAvatarManagerTest.cpp b/Swiften/Avatars/UnitTest/VCardUpdateAvatarManagerTest.cpp
new file mode 100644
index 0000000..e3d9c58
--- /dev/null
+++ b/Swiften/Avatars/UnitTest/VCardUpdateAvatarManagerTest.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <boost/bind.hpp>
+
+#include "Swiften/Elements/VCardUpdate.h"
+#include "Swiften/Avatars/VCardUpdateAvatarManager.h"
+#include "Swiften/Avatars/AvatarMemoryStorage.h"
+#include "Swiften/VCards/VCardMemoryStorage.h"
+#include "Swiften/VCards/VCardManager.h"
+#include "Swiften/MUC/MUCRegistry.h"
+#include "Swiften/Queries/IQRouter.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+#include "Swiften/StringCodecs/SHA1.h"
+#include "Swiften/StringCodecs/Hexify.h"
+
+using namespace Swift;
+
+class VCardUpdateAvatarManagerTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(VCardUpdateAvatarManagerTest);
+		CPPUNIT_TEST(testUpdate_NewHashNewVCardRequestsVCard);
+		CPPUNIT_TEST(testUpdate_NewHashStoresAvatarAndEmitsNotificationOnVCardReceive);
+		CPPUNIT_TEST(testUpdate_KnownHash);
+		CPPUNIT_TEST(testUpdate_KnownHashFromDifferentUserDoesNotRequestVCardButTriggersNotification);
+		/*&
+		CPPUNIT_TEST(testUpdate_UpdateNewHashAlreadyHaveAvatar);
+		CPPUNIT_TEST(testUpdate_UpdateNewHashFromMUC);
+		CPPUNIT_TEST(testUpdate_UpdateSameHash);*/
+		//CPPUNIT_TEST(testUpdate_UpdateWithError);
+		/*
+		CPPUNIT_TEST(testUpdate_UpdateNewHashSameThanOtherUser);
+		CPPUNIT_TEST(testReceiveVCard);
+		CPPUNIT_TEST(testGetAvatarPath);
+		CPPUNIT_TEST(testGetAvatarPathFromMUC);*/
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			ownJID = JID("foo@fum.com/bum");
+			stanzaChannel = new DummyStanzaChannel();
+			iqRouter = new IQRouter(stanzaChannel);
+			mucRegistry = new DummyMUCRegistry();
+			avatarStorage = new AvatarMemoryStorage();
+			vcardStorage = new VCardMemoryStorage();
+			vcardManager = new VCardManager(ownJID, iqRouter, vcardStorage);
+			avatar1 = ByteArray("abcdefg");
+			avatar1Hash = Hexify::hexify(SHA1::getHash(avatar1));
+			user1 = JID("user1@bar.com/bla");
+			user2 = JID("user2@foo.com/baz");
+		}
+
+		void tearDown() {
+			delete vcardManager;
+			delete vcardStorage;
+			delete avatarStorage;
+			delete mucRegistry;
+			delete iqRouter;
+			delete stanzaChannel;
+		}
+
+		void testUpdate_NewHashNewVCardRequestsVCard() {
+			std::auto_ptr<VCardUpdateAvatarManager> testling = createManager();
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, user1.toBare(), IQ::Get));
+		}
+
+		void testUpdate_NewHashStoresAvatarAndEmitsNotificationOnVCardReceive() {
+			std::auto_ptr<VCardUpdateAvatarManager> testling = createManager();
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
+			stanzaChannel->onIQReceived(createVCardResult(avatar1));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(user1.toBare(), changes[0].first);
+			CPPUNIT_ASSERT_EQUAL(avatar1Hash, changes[0].second);
+			CPPUNIT_ASSERT(avatarStorage->hasAvatar(avatar1Hash));
+			CPPUNIT_ASSERT_EQUAL(avatar1, avatarStorage->getAvatar(avatar1Hash));
+		}
+
+		void testUpdate_KnownHash() {
+			std::auto_ptr<VCardUpdateAvatarManager> testling = createManager();
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
+			stanzaChannel->onIQReceived(createVCardResult(avatar1));
+			changes.clear();
+			stanzaChannel->sentStanzas.clear();
+
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+		}
+
+		void testUpdate_KnownHashFromDifferentUserDoesNotRequestVCardButTriggersNotification() {
+			std::auto_ptr<VCardUpdateAvatarManager> testling = createManager();
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
+			stanzaChannel->onIQReceived(createVCardResult(avatar1));
+			changes.clear();
+			stanzaChannel->sentStanzas.clear();
+
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user2, avatar1Hash));
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(user2.toBare(), changes[0].first);
+			CPPUNIT_ASSERT_EQUAL(avatar1Hash, changes[0].second);
+		}
+
+/*
+		void testUpdate_UpdateNewHashFromMUC() {
+			std::auto_ptr<VCardUpdateAvatarManager> testling = createManager();
+		}
+
+		*/
+
+		/*void testUpdate_UpdateWithError() {
+			std::auto_ptr<VCardUpdateAvatarManager> testling = createManager();
+			boost::shared_ptr<Presence> update = createPresenceWithPhotoHash();
+			update->addPayload(boost::shared_ptr<ErrorPayload>(new ErrorPayload()));
+			stanzaChannel_->onPresenceReceived(update);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel_->sentStanzas.size()));
+		}*/
+
+
+	private:
+		std::auto_ptr<VCardUpdateAvatarManager> createManager() {
+			std::auto_ptr<VCardUpdateAvatarManager> result(new VCardUpdateAvatarManager(vcardManager, stanzaChannel, avatarStorage, mucRegistry));
+			result->onAvatarChanged.connect(boost::bind(&VCardUpdateAvatarManagerTest::handleAvatarChanged, this, _1, _2));
+			return result;
+		}
+
+		boost::shared_ptr<Presence> createPresenceWithPhotoHash(const JID& jid, const String& hash) {
+			boost::shared_ptr<Presence> presence(new Presence());
+			presence->setFrom(jid);
+			presence->addPayload(boost::shared_ptr<VCardUpdate>(new VCardUpdate(hash)));
+			return presence;
+		}
+
+		IQ::ref createVCardResult(const ByteArray& avatar) {
+			VCard::ref vcard(new VCard());
+			vcard->setPhoto(avatar);
+			return IQ::createResult(JID("baz@fum.com"), stanzaChannel->sentStanzas[0]->getID(), vcard);
+		}
+
+		void handleAvatarChanged(const JID& jid, const String& hash) {
+			changes.push_back(std::pair<JID,String>(jid, hash));
+		}
+
+	private:
+		struct DummyMUCRegistry : public MUCRegistry {
+			bool isMUC(const JID& jid) const { return std::find(mucs_.begin(), mucs_.end(), jid) != mucs_.end(); }
+			std::vector<JID> mucs_;
+		};
+
+		JID ownJID;
+		DummyStanzaChannel* stanzaChannel;
+		IQRouter* iqRouter;
+		DummyMUCRegistry* mucRegistry;
+		AvatarMemoryStorage* avatarStorage;
+		VCardManager* vcardManager;
+		VCardMemoryStorage* vcardStorage;
+		ByteArray avatar1;
+		String avatar1Hash;
+		std::vector<std::pair<JID,String> > changes;
+		JID user1;
+		JID user2;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VCardUpdateAvatarManagerTest);
diff --git a/Swiften/Avatars/VCardUpdateAvatarManager.cpp b/Swiften/Avatars/VCardUpdateAvatarManager.cpp
new file mode 100644
index 0000000..c7f1637
--- /dev/null
+++ b/Swiften/Avatars/VCardUpdateAvatarManager.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Avatars/VCardUpdateAvatarManager.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Client/StanzaChannel.h"
+#include "Swiften/Elements/VCardUpdate.h"
+#include "Swiften/Queries/Requests/GetVCardRequest.h"
+#include "Swiften/StringCodecs/SHA1.h"
+#include "Swiften/StringCodecs/Hexify.h"
+#include "Swiften/Avatars/AvatarStorage.h"
+#include "Swiften/MUC/MUCRegistry.h"
+#include "Swiften/VCards/VCardManager.h"
+
+namespace Swift {
+
+VCardUpdateAvatarManager::VCardUpdateAvatarManager(VCardManager* vcardManager, StanzaChannel* stanzaChannel, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) : vcardManager_(vcardManager), stanzaChannel_(stanzaChannel), avatarStorage_(avatarStorage), mucRegistry_(mucRegistry) {
+	stanzaChannel->onPresenceReceived.connect(boost::bind(&VCardUpdateAvatarManager::handlePresenceReceived, this, _1));
+	vcardManager_->onVCardChanged.connect(boost::bind(&VCardUpdateAvatarManager::handleVCardChanged, this, _1, _2));
+}
+
+VCardUpdateAvatarManager::~VCardUpdateAvatarManager() {
+
+}
+
+void VCardUpdateAvatarManager::setMUCRegistry(MUCRegistry* mucRegistry) {
+	mucRegistry_ = mucRegistry;
+}
+
+void VCardUpdateAvatarManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
+	boost::shared_ptr<VCardUpdate> update = presence->getPayload<VCardUpdate>();
+	if (!update || presence->getPayload<ErrorPayload>()) {
+		return;
+	}
+	JID from = getAvatarJID(presence->getFrom());
+	if (getAvatarHash(from) == update->getPhotoHash()) {
+		return;
+	}
+	if (avatarStorage_->hasAvatar(update->getPhotoHash())) {
+		setAvatarHash(from, update->getPhotoHash());
+	}
+	else {
+		vcardManager_->requestVCard(from);
+	}
+}
+
+void VCardUpdateAvatarManager::handleVCardChanged(const JID& from, VCard::ref vCard) {
+	if (!vCard) {
+		std::cerr << "Warning: " << from << ": null vcard payload" << std::endl;
+		return;
+	}
+
+	String hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
+	avatarStorage_->addAvatar(hash, vCard->getPhoto());
+	setAvatarHash(from, hash);
+}
+
+void VCardUpdateAvatarManager::setAvatarHash(const JID& from, const String& hash) {
+	avatarHashes_[from] = hash;
+	onAvatarChanged(from, hash);
+}
+
+/*
+void VCardUpdateAvatarManager::setAvatar(const JID& jid, const ByteArray& avatar) {
+	String hash = Hexify::hexify(SHA1::getHash(avatar));
+	avatarStorage_->addAvatar(hash, avatar);
+	setAvatarHash(getAvatarJID(jid), hash);
+}
+*/
+
+String VCardUpdateAvatarManager::getAvatarHash(const JID& jid) const {
+	std::map<JID, String>::const_iterator i = avatarHashes_.find(getAvatarJID(jid));
+	if (i != avatarHashes_.end()) {
+		return i->second;
+	}
+	else {
+		return "";
+	}
+}
+
+boost::filesystem::path VCardUpdateAvatarManager::getAvatarPath(const JID& jid) const {
+	String hash = getAvatarHash(jid);
+	if (!hash.isEmpty()) {
+		return avatarStorage_->getAvatarPath(hash);
+	}
+	return boost::filesystem::path();
+}
+
+JID VCardUpdateAvatarManager::getAvatarJID(const JID& jid) const {
+	JID bareFrom = jid.toBare();
+	return (mucRegistry_ && mucRegistry_->isMUC(bareFrom)) ? jid : bareFrom;
+}
+
+
+}
diff --git a/Swiften/Avatars/VCardUpdateAvatarManager.h b/Swiften/Avatars/VCardUpdateAvatarManager.h
new file mode 100644
index 0000000..b7ef34b
--- /dev/null
+++ b/Swiften/Avatars/VCardUpdateAvatarManager.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+#include <map>
+
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swiften/JID/JID.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/Elements/VCard.h"
+#include "Swiften/Elements/ErrorPayload.h"
+
+namespace Swift {
+	class MUCRegistry;
+	class AvatarStorage;
+	class StanzaChannel;
+	class VCardManager;
+
+	class VCardUpdateAvatarManager {
+		public:
+			VCardUpdateAvatarManager(VCardManager*, StanzaChannel*, AvatarStorage*, MUCRegistry* = NULL);
+			virtual ~VCardUpdateAvatarManager();
+
+			virtual void setMUCRegistry(MUCRegistry*);
+
+			virtual boost::filesystem::path getAvatarPath(const JID&) const;
+
+		public:
+			boost::signal<void (const JID&, const String& /*hash*/)> onAvatarChanged;
+
+		private:
+			void handlePresenceReceived(boost::shared_ptr<Presence>);
+			void handleVCardChanged(const JID& from, VCard::ref);
+			void setAvatarHash(const JID& from, const String& hash);
+			JID getAvatarJID(const JID& o) const;
+			String getAvatarHash(const JID&) const;
+
+		private:
+			VCardManager* vcardManager_;
+			StanzaChannel* stanzaChannel_;
+			AvatarStorage* avatarStorage_;
+			MUCRegistry* mucRegistry_;
+			std::map<JID, String> avatarHashes_;
+	};
+}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 6e2628a..bd54e7c 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -25,6 +25,7 @@ if env["SCONS_STAGE"] == "build" :
 	myenv.MergeFlags(myenv["OPENSSL_FLAGS"])
 	sources = [
 			"Avatars/AvatarFileStorage.cpp",
+			"Avatars/VCardUpdateAvatarManager.cpp",
 			"Avatars/AvatarManager.cpp",
 			"Avatars/AvatarStorage.cpp",
 			"Chat/ChatStateTracker.cpp",
@@ -140,7 +141,7 @@ if env["SCONS_STAGE"] == "build" :
 
 	env.Append(UNITTEST_SOURCES = [
 			File("Application/UnitTest/ApplicationPathProviderTest.cpp"),
-			File("Avatars/UnitTest/AvatarManagerTest.cpp"),
+			File("Avatars/UnitTest/VCardUpdateAvatarManagerTest.cpp"),
 			File("Base/UnitTest/IDGeneratorTest.cpp"),
 			File("Base/UnitTest/StringTest.cpp"),
 			File("Base/UnitTest/ByteArrayTest.cpp"),
-- 
cgit v0.10.2-6-g49f6