From 904db4e398210192093b688ebf1ad66fb017b6d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Thu, 9 Sep 2010 15:20:18 +0200
Subject: Added CombinedAvatarProvider


diff --git a/Swiften/Avatars/AvatarManager.cpp b/Swiften/Avatars/AvatarManager.cpp
index 9c3255d..6811a8e 100644
--- a/Swiften/Avatars/AvatarManager.cpp
+++ b/Swiften/Avatars/AvatarManager.cpp
@@ -15,15 +15,17 @@ namespace Swift {
 
 AvatarManager::AvatarManager(VCardManager* vcardManager, StanzaChannel* stanzaChannel, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) : avatarStorage(avatarStorage) {
 	vcardUpdateAvatarManager = new VCardUpdateAvatarManager(vcardManager, stanzaChannel, avatarStorage, mucRegistry);
-	vcardUpdateAvatarManager->onAvatarChanged.connect(boost::ref(onAvatarChanged));
+	combinedAvatarProvider.addProvider(vcardUpdateAvatarManager);
+	combinedAvatarProvider.onAvatarChanged.connect(boost::ref(onAvatarChanged));
 }
 
 AvatarManager::~AvatarManager() {
+	combinedAvatarProvider.removeProvider(vcardUpdateAvatarManager);
 	delete vcardUpdateAvatarManager;
 }
 
 boost::filesystem::path AvatarManager::getAvatarPath(const JID& jid) const {
-	String hash = vcardUpdateAvatarManager->getAvatarHash(jid);
+	String hash = combinedAvatarProvider.getAvatarHash(jid);
 	if (!hash.isEmpty()) {
 		return avatarStorage->getAvatarPath(hash);
 	}
diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h
index d3cdbae..04ce496 100644
--- a/Swiften/Avatars/AvatarManager.h
+++ b/Swiften/Avatars/AvatarManager.h
@@ -16,6 +16,7 @@
 #include "Swiften/Elements/Presence.h"
 #include "Swiften/Elements/VCard.h"
 #include "Swiften/Elements/ErrorPayload.h"
+#include "Swiften/Avatars/CombinedAvatarProvider.h"
 
 namespace Swift {
 	class MUCRegistry;
@@ -35,6 +36,7 @@ namespace Swift {
 			boost::signal<void (const JID&)> onAvatarChanged;
 
 		private:
+			CombinedAvatarProvider combinedAvatarProvider;
 			VCardUpdateAvatarManager* vcardUpdateAvatarManager;
 			AvatarStorage* avatarStorage;
 	};
diff --git a/Swiften/Avatars/CombinedAvatarProvider.cpp b/Swiften/Avatars/CombinedAvatarProvider.cpp
new file mode 100644
index 0000000..0e4a704
--- /dev/null
+++ b/Swiften/Avatars/CombinedAvatarProvider.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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/CombinedAvatarProvider.h"
+
+#include <algorithm>
+#include <boost/bind.hpp>
+
+namespace Swift {
+
+String CombinedAvatarProvider::getAvatarHash(const JID& jid) const {
+	for (size_t i = 0; i < providers.size(); ++i) {
+		String hash = providers[i]->getAvatarHash(jid);
+		if (!hash.isEmpty()) {
+			return hash;
+		}
+	}
+	return String();
+}
+
+void CombinedAvatarProvider::addProvider(AvatarProvider* provider) {
+	provider->onAvatarChanged.connect(reinterpret_cast<int>(this), boost::bind(&CombinedAvatarProvider::handleAvatarChanged, this, _1));
+	providers.push_back(provider);
+}
+
+void CombinedAvatarProvider::removeProvider(AvatarProvider* provider) {
+	std::vector<AvatarProvider*>::iterator i = std::remove(providers.begin(), providers.end(), provider);
+	for(std::vector<AvatarProvider*>::iterator j = i; j < providers.end(); ++j) {
+		provider->onAvatarChanged.disconnect(reinterpret_cast<int>(this));
+	}
+	providers.erase(i, providers.end());
+}
+
+void CombinedAvatarProvider::handleAvatarChanged(const JID& jid) {
+	String hash = getAvatarHash(jid);
+	std::map<JID, String>::iterator i = avatars.find(jid);
+	if (i != avatars.end()) {
+		if (i->second != hash) {
+			if (hash.isEmpty()) {
+				avatars.erase(i);
+			}
+			else {
+				avatars.insert(std::make_pair(jid, hash));
+			}
+			onAvatarChanged(jid);
+		}
+	}
+	else if (!hash.isEmpty()) {
+		avatars.insert(std::make_pair(jid, hash));
+		onAvatarChanged(jid);
+	}
+}
+
+}
diff --git a/Swiften/Avatars/CombinedAvatarProvider.h b/Swiften/Avatars/CombinedAvatarProvider.h
new file mode 100644
index 0000000..fbd6ce7
--- /dev/null
+++ b/Swiften/Avatars/CombinedAvatarProvider.h
@@ -0,0 +1,30 @@
+/*
+ * 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 <vector>
+#include <map>
+
+#include "Swiften/Avatars/AvatarProvider.h"
+#include "Swiften/JID/JID.h"
+
+namespace Swift {
+	class CombinedAvatarProvider : public AvatarProvider {
+		public:
+			virtual String getAvatarHash(const JID&) const;
+
+			void addProvider(AvatarProvider*);
+			void removeProvider(AvatarProvider*);
+
+		private:
+			void handleAvatarChanged(const JID&);
+
+		private:
+			std::vector<AvatarProvider*> providers;
+			std::map<JID, String> avatars;
+	};
+}
diff --git a/Swiften/Avatars/SConscript b/Swiften/Avatars/SConscript
index f9ce320..c5181e8 100644
--- a/Swiften/Avatars/SConscript
+++ b/Swiften/Avatars/SConscript
@@ -6,5 +6,6 @@ objects = swiften_env.StaticObject([
 			"AvatarManager.cpp",
 			"AvatarStorage.cpp",
 			"AvatarProvider.cpp",
+			"CombinedAvatarProvider.cpp",
 		])
 swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/Avatars/UnitTest/CombinedAvatarProviderTest.cpp b/Swiften/Avatars/UnitTest/CombinedAvatarProviderTest.cpp
new file mode 100644
index 0000000..ad3a0f1
--- /dev/null
+++ b/Swiften/Avatars/UnitTest/CombinedAvatarProviderTest.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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/JID/JID.h"
+#include "Swiften/Base/String.h"
+#include "Swiften/Avatars/CombinedAvatarProvider.h"
+
+using namespace Swift;
+
+class CombinedAvatarProviderTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(CombinedAvatarProviderTest);
+		CPPUNIT_TEST(testGetAvatarWithNoAvatarProviderReturnsEmpty);
+		CPPUNIT_TEST(testGetAvatarWithSingleAvatarProvider);
+		CPPUNIT_TEST(testGetAvatarWithMultipleAvatarProviderReturnsFirstAvatar);
+		CPPUNIT_TEST(testGetAvatarWithMultipleAvatarProviderAndFailingFirstProviderReturnsSecondAvatar);
+		CPPUNIT_TEST(testProviderUpdateTriggersChange);
+		CPPUNIT_TEST(testProviderUpdateWithoutChangeDoesNotTriggerChange);
+		CPPUNIT_TEST(testProviderSecondUpdateTriggersChange);
+		CPPUNIT_TEST(testProviderUpdateWithAvatarDisappearingTriggersChange);
+		CPPUNIT_TEST(testProviderUpdateAfterAvatarDisappearedTriggersChange);
+		CPPUNIT_TEST(testRemoveProviderDisconnectsUpdates);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			avatarProvider1 = new DummyAvatarProvider();
+			avatarProvider2 = new DummyAvatarProvider();
+			user1 = JID("user1@bar.com/bla");
+			user2 = JID("user2@foo.com/baz");
+			avatarHash1 = "ABCDEFG";
+			avatarHash2 = "XYZU";
+			avatarHash3 = "IDGH";
+		}
+
+		void tearDown() {
+			delete avatarProvider1;
+			delete avatarProvider2;
+		}
+
+		void testGetAvatarWithNoAvatarProviderReturnsEmpty() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+
+			CPPUNIT_ASSERT(testling->getAvatarHash(user1).isEmpty());
+		}
+
+		void testGetAvatarWithSingleAvatarProvider() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			avatarProvider1->avatars[user1] = avatarHash1;
+			testling->addProvider(avatarProvider1);
+
+			CPPUNIT_ASSERT_EQUAL(avatarHash1, testling->getAvatarHash(user1));
+		}
+
+		void testGetAvatarWithMultipleAvatarProviderReturnsFirstAvatar() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider2->avatars[user1] = avatarHash2;
+			testling->addProvider(avatarProvider1);
+			testling->addProvider(avatarProvider2);
+
+			CPPUNIT_ASSERT_EQUAL(avatarHash1, testling->getAvatarHash(user1));
+		}
+
+		void testGetAvatarWithMultipleAvatarProviderAndFailingFirstProviderReturnsSecondAvatar() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			avatarProvider2->avatars[user1] = avatarHash2;
+			testling->addProvider(avatarProvider1);
+			testling->addProvider(avatarProvider2);
+
+			CPPUNIT_ASSERT_EQUAL(avatarHash2, testling->getAvatarHash(user1));
+		}
+
+		void testProviderUpdateTriggersChange() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			testling->addProvider(avatarProvider1);
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider1->onAvatarChanged(user1);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+		}
+
+		void testProviderUpdateWithoutChangeDoesNotTriggerChange() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			testling->addProvider(avatarProvider1);
+			testling->addProvider(avatarProvider2);
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider1->onAvatarChanged(user1);
+			changes.clear();
+
+			avatarProvider2->avatars[user1] = avatarHash2;
+			avatarProvider2->onAvatarChanged(user1);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+		}
+
+		void testProviderSecondUpdateTriggersChange() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			testling->addProvider(avatarProvider1);
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider1->onAvatarChanged(user1);
+			changes.clear();
+			avatarProvider1->avatars[user1] = avatarHash2;
+			avatarProvider1->onAvatarChanged(user1);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+		}
+
+
+		void testProviderUpdateWithAvatarDisappearingTriggersChange() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			testling->addProvider(avatarProvider1);
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider1->onAvatarChanged(user1);
+			changes.clear();
+			avatarProvider1->avatars.clear();
+			avatarProvider1->onAvatarChanged(user1);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+		}
+
+		void testProviderUpdateAfterAvatarDisappearedTriggersChange() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			testling->addProvider(avatarProvider1);
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider1->onAvatarChanged(user1);
+			avatarProvider1->avatars.clear();
+			avatarProvider1->onAvatarChanged(user1);
+			changes.clear();
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider1->onAvatarChanged(user1);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+		}
+
+		void testRemoveProviderDisconnectsUpdates() {
+			std::auto_ptr<CombinedAvatarProvider> testling(createProvider());
+			testling->addProvider(avatarProvider1);
+			testling->addProvider(avatarProvider2);
+			testling->removeProvider(avatarProvider1);
+			avatarProvider1->avatars[user1] = avatarHash1;
+			avatarProvider2->avatars[user1] = avatarHash2;
+			avatarProvider1->onAvatarChanged(user1);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+		}
+
+	private:
+		std::auto_ptr<CombinedAvatarProvider> createProvider() {
+			std::auto_ptr<CombinedAvatarProvider> result(new CombinedAvatarProvider());
+			result->onAvatarChanged.connect(boost::bind(&CombinedAvatarProviderTest::handleAvatarChanged, this, _1));
+			return result;
+		}
+
+		void handleAvatarChanged(const JID& jid) {
+			changes.push_back(jid);
+		}
+
+	private:
+		struct DummyAvatarProvider : public AvatarProvider {
+			String getAvatarHash(const JID& jid) const {
+				std::map<JID, String>::const_iterator i = avatars.find(jid);
+				if (i != avatars.end()) {
+					return i->second;
+				}
+				else {
+					return String();
+				}
+			}
+
+			std::map<JID, String> avatars;
+		};
+
+		DummyAvatarProvider* avatarProvider1;
+		DummyAvatarProvider* avatarProvider2;
+		JID user1;
+		JID user2;
+		String avatarHash1;
+		String avatarHash2;
+		String avatarHash3;
+		std::vector<JID> changes;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CombinedAvatarProviderTest);
diff --git a/Swiften/Avatars/VCardUpdateAvatarManager.h b/Swiften/Avatars/VCardUpdateAvatarManager.h
index 2220906..8e11223 100644
--- a/Swiften/Avatars/VCardUpdateAvatarManager.h
+++ b/Swiften/Avatars/VCardUpdateAvatarManager.h
@@ -21,7 +21,7 @@ namespace Swift {
 	class StanzaChannel;
 	class VCardManager;
 
-	class VCardUpdateAvatarManager : public AvatarProvider {
+	class VCardUpdateAvatarManager : public AvatarProvider, public boost::bsignals::trackable {
 		public:
 			VCardUpdateAvatarManager(VCardManager*, StanzaChannel*, AvatarStorage*, MUCRegistry* = NULL);
 			virtual ~VCardUpdateAvatarManager();
diff --git a/Swiften/SConscript b/Swiften/SConscript
index f19698e..588bd47 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -141,6 +141,7 @@ if env["SCONS_STAGE"] == "build" :
 	env.Append(UNITTEST_SOURCES = [
 			File("Application/UnitTest/ApplicationPathProviderTest.cpp"),
 			File("Avatars/UnitTest/VCardUpdateAvatarManagerTest.cpp"),
+			File("Avatars/UnitTest/CombinedAvatarProviderTest.cpp"),
 			File("Base/UnitTest/IDGeneratorTest.cpp"),
 			File("Base/UnitTest/StringTest.cpp"),
 			File("Base/UnitTest/ByteArrayTest.cpp"),
-- 
cgit v0.10.2-6-g49f6