From b6a34463dfcedd454ab42230a47cdb7294a76f21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 22 Aug 2010 10:55:08 +0200
Subject: Implemented VCardManager.


diff --git a/.cproject b/.cproject
index 5473cea..b9de4c5 100644
--- a/.cproject
+++ b/.cproject
@@ -307,22 +307,23 @@
 					<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
 					<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
 					<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="be.el_tramo.ecppunit.CPPUnitErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
 				</extensions>
 			</storageModule>
 			<storageModule moduleId="cdtBuildSystem" version="4.0.0">
-				<configuration artifactName="${ProjName}" buildProperties="" description="" id="0.980756260.1834106966" name="Unit tests" parent="org.eclipse.cdt.build.core.prefbase.cfg">
+				<configuration artifactName="${ProjName}" buildProperties="" description="" errorParsers="org.eclipse.cdt.core.VCErrorParser;org.eclipse.cdt.core.GmakeErrorParser;org.eclipse.cdt.core.CWDLocator;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser;be.el_tramo.ecppunit.CPPUnitErrorParser" id="0.980756260.1834106966" name="Unit tests" parent="org.eclipse.cdt.build.core.prefbase.cfg">
 					<folderInfo id="0.980756260.1834106966." name="/" resourcePath="">
-						<toolChain id="org.eclipse.cdt.build.core.prefbase.toolchain.1171413969" name="No ToolChain" resourceTypeBasedDiscovery="false" superClass="org.eclipse.cdt.build.core.prefbase.toolchain">
+						<toolChain errorParsers="" id="org.eclipse.cdt.build.core.prefbase.toolchain.1171413969" name="No ToolChain" resourceTypeBasedDiscovery="false" superClass="org.eclipse.cdt.build.core.prefbase.toolchain">
 							<targetPlatform binaryParser="org.eclipse.cdt.core.MachO64;org.eclipse.cdt.core.ELF;org.eclipse.cdt.core.PE;org.eclipse.cdt.core.GNU_ELF" id="org.eclipse.cdt.build.core.prefbase.toolchain.1171413969.1006500186" name=""/>
-							<builder arguments="${ProjDirPath}/3rdParty/SCons/scons.py" autoBuildTarget="check=1" buildPath="" cleanBuildTarget="-c" command="python" enableAutoBuild="true" id="org.eclipse.cdt.build.core.settings.default.builder.1417638948" incrementalBuildTarget="check=1" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="org.eclipse.cdt.build.core.settings.default.builder"/>
-							<tool id="org.eclipse.cdt.build.core.settings.holder.libs.358970395" name="holder for library settings" superClass="org.eclipse.cdt.build.core.settings.holder.libs"/>
-							<tool id="org.eclipse.cdt.build.core.settings.holder.655623884" name="Assembly" superClass="org.eclipse.cdt.build.core.settings.holder">
+							<builder arguments="${ProjDirPath}/3rdParty/SCons/scons.py" autoBuildTarget="check=1 QA" buildPath="" cleanBuildTarget="-c" command="python" enableAutoBuild="true" errorParsers="org.eclipse.cdt.core.GmakeErrorParser;org.eclipse.cdt.core.CWDLocator" id="org.eclipse.cdt.build.core.settings.default.builder.1417638948" incrementalBuildTarget="check=1 QA" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="org.eclipse.cdt.build.core.settings.default.builder"/>
+							<tool errorParsers="org.eclipse.cdt.core.VCErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser" id="org.eclipse.cdt.build.core.settings.holder.libs.358970395" name="holder for library settings" superClass="org.eclipse.cdt.build.core.settings.holder.libs"/>
+							<tool errorParsers="org.eclipse.cdt.core.VCErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser" id="org.eclipse.cdt.build.core.settings.holder.655623884" name="Assembly" superClass="org.eclipse.cdt.build.core.settings.holder">
 								<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.1994670288" languageId="org.eclipse.cdt.core.assembly" languageName="Assembly" sourceContentType="org.eclipse.cdt.core.asmSource" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/>
 							</tool>
-							<tool id="org.eclipse.cdt.build.core.settings.holder.1108727159" name="GNU C++" superClass="org.eclipse.cdt.build.core.settings.holder">
+							<tool errorParsers="org.eclipse.cdt.core.VCErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser" id="org.eclipse.cdt.build.core.settings.holder.1108727159" name="GNU C++" superClass="org.eclipse.cdt.build.core.settings.holder">
 								<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.463954066" languageId="org.eclipse.cdt.core.g++" languageName="GNU C++" sourceContentType="org.eclipse.cdt.core.cxxSource,org.eclipse.cdt.core.cxxHeader" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/>
 							</tool>
-							<tool id="org.eclipse.cdt.build.core.settings.holder.569111652" name="GNU C" superClass="org.eclipse.cdt.build.core.settings.holder">
+							<tool errorParsers="org.eclipse.cdt.core.VCErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser" id="org.eclipse.cdt.build.core.settings.holder.569111652" name="GNU C" superClass="org.eclipse.cdt.build.core.settings.holder">
 								<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.966696268" languageId="org.eclipse.cdt.core.gcc" languageName="GNU C" sourceContentType="org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/>
 							</tool>
 						</toolChain>
diff --git a/.project b/.project
index 06cc260..17363c7 100644
--- a/.project
+++ b/.project
@@ -18,7 +18,7 @@
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.autoBuildTarget</key>
-					<value>dist=1</value>
+					<value>check=1 QA</value>
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.buildArguments</key>
@@ -54,7 +54,7 @@
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.fullBuildTarget</key>
-					<value>dist=1</value>
+					<value>check=1 QA</value>
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.stopOnError</key>
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 2204366..d5686bd 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -18,7 +18,10 @@
 #include "Swift/Controllers/EventController.h"
 #include "Swift/Controllers/Chat/MUCController.h"
 #include "Swiften/Presence/PresenceSender.h"
-#include "Swiften/Avatars/UnitTest/MockAvatarManager.h"
+#include "Swiften/Avatars/AvatarManager.h"
+#include "Swiften/Avatars/AvatarMemoryStorage.h"
+#include "Swiften/VCards/VCardManager.h"
+#include "Swiften/VCards/VCardMemoryStorage.h"
 #include "Swift/Controllers/NickResolver.h"
 #include "Swiften/Roster/XMPPRoster.h"
 #include "Swift/Controllers/UnitTest/MockChatWindow.h"
@@ -65,12 +68,19 @@ public:
 		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);
-		avatarManager_ = new MockAvatarManager();
+
+		vcardStorage_ = new VCardMemoryStorage();
+		vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_);
+		avatarStorage_ = new AvatarMemoryStorage();
+		avatarManager_ = new AvatarManager(vcardManager_, stanzaChannel_, avatarStorage_, NULL);
 		manager_->setAvatarManager(avatarManager_);
 	};
 	
 	void tearDown() {
 		delete avatarManager_;
+		delete avatarStorage_;
+		delete vcardManager_;
+		delete vcardStorage_;
 		delete manager_;
 		delete presenceSender_;
 		delete presenceOracle_;
@@ -303,6 +313,9 @@ private:
 	ChatWindowFactory* chatWindowFactory_;
 	NickResolver* nickResolver_;
 	PresenceOracle* presenceOracle_;
+	VCardStorage* vcardStorage_;
+	VCardManager* vcardManager_;
+	AvatarStorage* avatarStorage_;
 	AvatarManager* avatarManager_;
 	boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
 	boost::shared_ptr<XMPPRoster> xmppRoster_;
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 6eb3a2c..1032de1 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -20,6 +20,8 @@
 #include "Swift/Controllers/BuildVersion.h"
 #include "Swift/Controllers/Chat/ChatController.h"
 #include "Swiften/VCards/VCardStorageFactory.h"
+#include "Swiften/VCards/VCardManager.h"
+#include "Swiften/VCards/VCardStorage.h"
 #include "Swift/Controllers/Chat/MUCSearchController.h"
 #include "Swift/Controllers/Chat/ChatsManager.h"
 #include "Swift/Controllers/EventController.h"
@@ -85,16 +87,16 @@ MainController::MainController(
 			mainWindowFactory_(mainWindowFactory),
 			loginWindowFactory_(loginWindowFactory),
 			settings_(settings),
-			loginWindow_(NULL),
 			vcardStorageFactory_(vcardStorageFactory),
-			useDelayForLatency_(useDelayForLatency)  {
+			loginWindow_(NULL) ,
+			useDelayForLatency_(useDelayForLatency) {
 	presenceOracle_ = NULL;
-	avatarManager_ = NULL;
 	chatsManager_ = NULL;
 	eventController_ = NULL;
 	eventWindowController_ = NULL;
 	nickResolver_ = NULL;
 	avatarManager_ = NULL;
+	vcardManager_ = NULL;
 	rosterController_ = NULL;
 	xmppRosterController_ = NULL;
 	clientVersionResponder_ = NULL;
@@ -153,11 +155,13 @@ MainController::MainController(
 MainController::~MainController() {
 	delete systemTrayController_;
 	delete soundEventController_;
-	delete avatarStorage_;
 	delete xmlConsoleController_;
 	delete uiEventStream_;
 	delete eventController_;
 	resetClient();
+	for(VCardStorageMap::iterator i = vcardStorages_.begin(); i != vcardStorages_.end(); ++i) {
+		delete i->second;
+	}
 }
 
 void MainController::resetClient() {
@@ -173,6 +177,8 @@ void MainController::resetClient() {
 	nickResolver_ = NULL;
 	delete avatarManager_;
 	avatarManager_ = NULL;
+	delete vcardManager_;
+	vcardManager_ = NULL;
 	delete eventWindowController_;
 	eventWindowController_ = NULL;
 	delete rosterController_;
@@ -223,7 +229,9 @@ void MainController::handleConnected() {
 		presenceOracle_ = new PresenceOracle(client_);
 		nickResolver_ = new NickResolver(xmppRoster_);		
 
-		avatarManager_ = new AvatarManager(client_, client_, avatarStorage_);
+		vcardManager_ = new VCardManager(jid_, client_, getVCardStorageForProfile(jid_));
+		vcardManager_->onOwnVCardChanged.connect(boost::bind(&MainController::handleOwnVCardReceived, this, _1));
+		avatarManager_ = new AvatarManager(vcardManager_, client_, avatarStorage_);
 
 		rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, eventController_, uiEventStream_, client_);
 		rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));
@@ -261,10 +269,7 @@ void MainController::handleConnected() {
 	discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2));
 	discoInfoRequest->send();
 
-	boost::shared_ptr<GetVCardRequest> vCardRequest(new GetVCardRequest(JID(), client_));
-	vCardRequest->onResponse.connect(boost::bind(&MainController::handleOwnVCardReceived, this, _1, _2));
-	vCardRequest->send();
-
+	vcardManager_->requestOwnVCard();
 	
 	setManagersEnabled(true);
 	//Send presence last to catch all the incoming presences.
@@ -480,17 +485,24 @@ void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo>
 	}
 }
 
-void MainController::handleOwnVCardReceived(boost::shared_ptr<VCard> vCard, const boost::optional<ErrorPayload>& error) {
-	if (!vCard) {
+void MainController::handleOwnVCardReceived(VCard::ref vCard) {
+	if (!vCard || vCard->getPhoto().isEmpty()) {
 		return;
 	}
-	if (!error && !vCard->getPhoto().isEmpty()) {
-		vCardPhotoHash_ = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
-		if (client_ && client_->isAvailable()) {
-			sendPresence(statusTracker_->getNextPresence());
-		}
-		avatarManager_->setAvatar(jid_, vCard->getPhoto());
+	vCardPhotoHash_ = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
+	if (client_ && client_->isAvailable()) {
+		sendPresence(statusTracker_->getNextPresence());
 	}
 }
 
+VCardStorage* MainController::getVCardStorageForProfile(const JID& jid) {
+	String profile = jid.toBare().toString();
+	std::pair<VCardStorageMap::iterator, bool> r = vcardStorages_.insert(std::make_pair<String, VCardStorage*>(profile, NULL));
+	if (r.second) {
+		r.first->second = vcardStorageFactory_->createVCardStorage(profile);
+	}
+	return r.first->second;
+}
+
+
 }
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index a612175..cf04e59 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -29,6 +29,8 @@
 
 namespace Swift {
 	class AvatarStorage;
+	class VCardStorage;
+	class VCardManager;
 	class Application;
 	class Client;
 	class ChatWindowFactory;
@@ -94,7 +96,7 @@ namespace Swift {
 			void handleError(const ClientError& error);
 			void handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo>, const boost::optional<ErrorPayload>&);
 			void handleEventQueueLengthChange(int count);
-			void handleOwnVCardReceived(boost::shared_ptr<VCard> vCard, const boost::optional<ErrorPayload>& error);
+			void handleOwnVCardReceived(VCard::ref vCard);
 			void sendPresence(boost::shared_ptr<Presence> presence);
 			void handleInputIdleChanged(bool);
 			void logout();
@@ -107,6 +109,9 @@ namespace Swift {
 			void reconnectAfterError();
 			void setManagersEnabled(bool enabled);
 
+			VCardStorage* getVCardStorageForProfile(const JID& jid);
+
+		private:
 			BoostIOServiceThread boostIOServiceThread_;
 			BoostTimerFactory timerFactory_;
 			PlatformIdleQuerier idleQuerier_;
@@ -121,6 +126,7 @@ namespace Swift {
 			ProfileSettingsProvider* profileSettings_;
 			AvatarStorage* avatarStorage_;
 			VCardStorageFactory* vcardStorageFactory_;
+			VCardManager* vcardManager_;
 			ApplicationMessageDisplay* applicationMessageDisplay_;
 			ChatController* chatController_;
 			XMPPRosterController* xmppRosterController_;
@@ -153,5 +159,8 @@ namespace Swift {
 			int timeBeforeNextReconnect_;
 			Timer::ref reconnectTimer_;
 			StatusTracker* statusTracker_;
+
+			typedef std::map<String, VCardStorage*> VCardStorageMap;
+			VCardStorageMap vcardStorages_;
 	};
 }
diff --git a/Swiften/Avatars/AvatarFileStorage.cpp b/Swiften/Avatars/AvatarFileStorage.cpp
index a0ebd21..57bb1be 100644
--- a/Swiften/Avatars/AvatarFileStorage.cpp
+++ b/Swiften/Avatars/AvatarFileStorage.cpp
@@ -34,4 +34,11 @@ boost::filesystem::path AvatarFileStorage::getAvatarPath(const String& hash) con
 	return path_ / hash.getUTF8String();
 }
 
+ByteArray AvatarFileStorage::getAvatar(const String& hash) const {
+	ByteArray data;
+	data.readFromFile(getAvatarPath(hash).string());
+	return data;
+}
+
+
 }
diff --git a/Swiften/Avatars/AvatarFileStorage.h b/Swiften/Avatars/AvatarFileStorage.h
index 6318c60..5ade779 100644
--- a/Swiften/Avatars/AvatarFileStorage.h
+++ b/Swiften/Avatars/AvatarFileStorage.h
@@ -20,6 +20,7 @@ namespace Swift {
 
 			virtual bool hasAvatar(const String& hash) const;
 			virtual void addAvatar(const String& hash, const ByteArray& avatar);
+			virtual ByteArray getAvatar(const String& hash) const;
 
 			virtual boost::filesystem::path getAvatarPath(const String& hash) const;
 
diff --git a/Swiften/Avatars/AvatarManager.cpp b/Swiften/Avatars/AvatarManager.cpp
index 3861520..33b1bee 100644
--- a/Swiften/Avatars/AvatarManager.cpp
+++ b/Swiften/Avatars/AvatarManager.cpp
@@ -15,18 +15,13 @@
 #include "Swiften/StringCodecs/Hexify.h"
 #include "Swiften/Avatars/AvatarStorage.h"
 #include "Swiften/MUC/MUCRegistry.h"
+#include "Swiften/VCards/VCardManager.h"
 
 namespace Swift {
 
-AvatarManager::AvatarManager(StanzaChannel* stanzaChannel, IQRouter* iqRouter, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), avatarStorage_(avatarStorage), mucRegistry_(mucRegistry) {
+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));
-}
-
-AvatarManager::AvatarManager() {
-	stanzaChannel_ = NULL;
-	iqRouter_ = NULL;
-	avatarStorage_ = NULL;
-	mucRegistry_ = NULL;
+	vcardManager_->onVCardChanged.connect(boost::bind(&AvatarManager::handleVCardChanged, this, _1, _2));
 }
 
 AvatarManager::~AvatarManager() {
@@ -39,50 +34,30 @@ void AvatarManager::setMUCRegistry(MUCRegistry* mucRegistry) {
 
 void AvatarManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
 	boost::shared_ptr<VCardUpdate> update = presence->getPayload<VCardUpdate>();
-	if (!update) {
+	if (!update || presence->getPayload<ErrorPayload>()) {
 		return;
 	}
-	if (presence->getPayload<ErrorPayload>()) {
+	JID from = getAvatarJID(presence->getFrom());
+	if (getAvatarHash(from) == update->getPhotoHash()) {
 		return;
 	}
-	JID from = getAvatarJID(presence->getFrom());
-	String& hash = avatarHashes_[from];
-	if (hash != update->getPhotoHash()) {
-		String newHash = update->getPhotoHash();
-		if (avatarStorage_->hasAvatar(newHash)) {
-			setAvatarHash(from, newHash);
-		}
-		else {
-			boost::shared_ptr<GetVCardRequest> request(new GetVCardRequest(from, iqRouter_));
-			request->onResponse.connect(boost::bind(&AvatarManager::handleVCardReceived, this, from, newHash, _1, _2));
-			request->send();
-		}
+	if (avatarStorage_->hasAvatar(update->getPhotoHash())) {
+		setAvatarHash(from, update->getPhotoHash());
+	}
+	else {
+		vcardManager_->requestVCard(from);
 	}
 }
 
-void AvatarManager::handleVCardReceived(const JID& from, const String& promisedHash, boost::shared_ptr<VCard> vCard, const boost::optional<ErrorPayload>& error) {
-	if (error) {
-		// FIXME: What to do here?
-		std::cerr << "Warning: " << from << ": Could not get vCard" << std::endl;
-		return;
-	}
+void AvatarManager::handleVCardChanged(const JID& from, VCard::ref vCard) {
 	if (!vCard) {
 		std::cerr << "Warning: " << from << ": null vcard payload" << std::endl;
-		//FIXME: Why could this happen?
 		return;
 	}
-	String realHash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
-	if (promisedHash != realHash) {
-		std::cerr << "Warning: " << from << ": Got different vCard photo hash (" << promisedHash << " != " << realHash << ")" << std::endl;
-	}
-	avatarStorage_->addAvatar(realHash, vCard->getPhoto());
-	setAvatarHash(from, realHash);
-}
 
-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 hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
+	avatarStorage_->addAvatar(hash, vCard->getPhoto());
+	setAvatarHash(from, hash);
 }
 
 void AvatarManager::setAvatarHash(const JID& from, const String& hash) {
@@ -90,6 +65,14 @@ void AvatarManager::setAvatarHash(const JID& from, const String& 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()) {
diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h
index ad4b30f..543d167 100644
--- a/Swiften/Avatars/AvatarManager.h
+++ b/Swiften/Avatars/AvatarManager.h
@@ -9,9 +9,9 @@
 #include <boost/filesystem.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/optional.hpp>
-#include "Swiften/Base/boost_bsignals.h"
 #include <map>
 
+#include "Swiften/Base/boost_bsignals.h"
 #include "Swiften/JID/JID.h"
 #include "Swiften/Elements/Presence.h"
 #include "Swiften/Elements/VCard.h"
@@ -21,35 +21,32 @@ namespace Swift {
 	class MUCRegistry;
 	class AvatarStorage;
 	class StanzaChannel;
-	class IQRouter;
+	class VCardManager;
 
 	class AvatarManager {
 		public:
-			AvatarManager(StanzaChannel*, IQRouter*, AvatarStorage*, MUCRegistry* = NULL);
+			AvatarManager(VCardManager*, StanzaChannel*, AvatarStorage*, MUCRegistry* = NULL);
 			virtual ~AvatarManager();
 
 			virtual void setMUCRegistry(MUCRegistry*);
 
-			virtual String getAvatarHash(const JID&) const;
 			virtual boost::filesystem::path getAvatarPath(const JID&) const;
-			virtual void setAvatar(const JID&, const ByteArray& avatar);
 
-		public:
-			boost::signal<void (const JID&, const String&)> onAvatarChanged;
+//			virtual void setAvatar(const JID&, const ByteArray& avatar);*/
 
-		protected:
-			/** Used only for testing. Leads to a non-functional object. */
-			AvatarManager();
+		public:
+			boost::signal<void (const JID&, const String& /*hash*/)> onAvatarChanged;
 
 		private:
 			void handlePresenceReceived(boost::shared_ptr<Presence>);
-			void handleVCardReceived(const JID& from, const String& hash, boost::shared_ptr<VCard>, const boost::optional<ErrorPayload>&);
+			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_;
-			IQRouter* iqRouter_;
 			AvatarStorage* avatarStorage_;
 			MUCRegistry* mucRegistry_;
 			std::map<JID, String> avatarHashes_;
diff --git a/Swiften/Avatars/AvatarMemoryStorage.h b/Swiften/Avatars/AvatarMemoryStorage.h
new file mode 100644
index 0000000..f60f603
--- /dev/null
+++ b/Swiften/Avatars/AvatarMemoryStorage.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <map>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/Base/ByteArray.h"
+#include "Swiften/Avatars/AvatarStorage.h"
+
+namespace Swift {
+	class AvatarMemoryStorage : public AvatarStorage {
+		public:
+			virtual bool hasAvatar(const String& hash) const { return avatars.find(hash) != avatars.end(); }
+			virtual void addAvatar(const String& hash, const ByteArray& avatar) { avatars[hash] = avatar; }
+			virtual ByteArray getAvatar(const String& hash) const {
+				std::map<String, ByteArray>::const_iterator i = avatars.find(hash);
+				return i == avatars.end() ? ByteArray() : i->second;
+			}
+
+			virtual boost::filesystem::path getAvatarPath(const String& hash) const {
+				return boost::filesystem::path();
+			}
+
+		private:
+			std::map<String, ByteArray> avatars;
+	};
+}
diff --git a/Swiften/Avatars/AvatarStorage.h b/Swiften/Avatars/AvatarStorage.h
index bd8aa49..d699f40 100644
--- a/Swiften/Avatars/AvatarStorage.h
+++ b/Swiften/Avatars/AvatarStorage.h
@@ -18,6 +18,7 @@ namespace Swift {
 
 			virtual bool hasAvatar(const String& hash) const = 0;
 			virtual void addAvatar(const String& hash, const ByteArray& avatar) = 0;
+			virtual ByteArray getAvatar(const String& hash) const = 0;
 			virtual boost::filesystem::path getAvatarPath(const String& hash) const = 0;
 	};
 
diff --git a/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp
index f954aa1..858d257 100644
--- a/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp
+++ b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp
@@ -6,24 +6,32 @@
 
 #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/AvatarStorage.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_UpdateNewHash);
+		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_UpdateWithError);
 		/*
 		CPPUNIT_TEST(testUpdate_UpdateNewHashSameThanOtherUser);
 		CPPUNIT_TEST(testReceiveVCard);
@@ -33,99 +41,135 @@ class AvatarManagerTest : public CppUnit::TestFixture {
 
 	public:
 		void setUp() {
-			stanzaChannel_ = new DummyStanzaChannel();
-			iqRouter_ = new IQRouter(stanzaChannel_);
-			mucRegistry_ = new DummyMUCRegistry();
-			avatarStorage_ = new DummyAvatarStorage();
+			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 avatarStorage_;
-			delete mucRegistry_;
-			delete iqRouter_;
-			delete stanzaChannel_;
+			delete vcardManager;
+			delete vcardStorage;
+			delete avatarStorage;
+			delete mucRegistry;
+			delete iqRouter;
+			delete stanzaChannel;
 		}
 
-		void testUpdate_UpdateNewHash() {
+		void testUpdate_NewHashNewVCardRequestsVCard() {
 			std::auto_ptr<AvatarManager> testling = createManager();
-			stanzaChannel_->onPresenceReceived(createPresenceWithPhotoHash());
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
 
-			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel_->sentStanzas.size()));
-			CPPUNIT_ASSERT(stanzaChannel_->isRequestAtIndex<VCard>(0, JID("foo@bar.com"), IQ::Get));
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, user1.toBare(), IQ::Get));
 		}
 
-		void testUpdate_UpdateNewHashAlreadyHaveAvatar() {
-			avatarStorage_->addAvatar("aef56135bcce35eb24a43fcd684005b4ca286497", ByteArray("ghij"));
-			std::auto_ptr<AvatarManager> testling = createManager();
-			stanzaChannel_->onPresenceReceived(createPresenceWithPhotoHash());
-
-			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel_->sentStanzas.size()));
-		}
-/*
-		void testUpdate_UpdateNewHashFromMUC() {
+		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_UpdateSameHash() {
+		void testUpdate_KnownHash() {
 			std::auto_ptr<AvatarManager> testling = createManager();
-		}
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
+			stanzaChannel->onIQReceived(createVCardResult(avatar1));
+			changes.clear();
+			stanzaChannel->sentStanzas.clear();
 
-		void testUpdate_UpdateNewHashSameThanOtherUser() {
-			std::auto_ptr<AvatarManager> testling = createManager();
-		}
+			stanzaChannel->onPresenceReceived(createPresenceWithPhotoHash(user1, avatar1Hash));
 
-		void testReceiveVCard() {
-			std::auto_ptr<AvatarManager> testling = createManager();
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
 		}
 
-		void testGetAvatarPath() {
+		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 testGetAvatarPathFromMUC() {
+/*
+		void testUpdate_UpdateNewHashFromMUC() {
 			std::auto_ptr<AvatarManager> testling = createManager();
 		}
+
 		*/
 
-		void testUpdate_UpdateWithError() {
+		/*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() {
-			return std::auto_ptr<AvatarManager>(new AvatarManager(stanzaChannel_, iqRouter_, avatarStorage_, mucRegistry_));
+			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() {
+		boost::shared_ptr<Presence> createPresenceWithPhotoHash(const JID& jid, const String& hash) {
 			boost::shared_ptr<Presence> presence(new Presence());
-			presence->setFrom(JID("foo@bar.com/baz"));
-			presence->addPayload(boost::shared_ptr<VCardUpdate>(new VCardUpdate("aef56135bcce35eb24a43fcd684005b4ca286497")));
+			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_;
 		};
-		struct DummyAvatarStorage : public AvatarStorage {
-			virtual bool hasAvatar(const String& hash) const { return avatars.find(hash) != avatars.end(); }
-			virtual void addAvatar(const String& hash, const ByteArray& avatar) { avatars[hash] = avatar; }
-			virtual boost::filesystem::path getAvatarPath(const String& hash) const {
-				return boost::filesystem::path("/avatars") / hash.getUTF8String();
-			}
-			std::map<String, ByteArray> avatars;
-		};
-		DummyStanzaChannel* stanzaChannel_;
-		IQRouter* iqRouter_;
-		DummyMUCRegistry* mucRegistry_;
-		DummyAvatarStorage* avatarStorage_;
+
+		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/MockAvatarManager.cpp b/Swiften/Avatars/UnitTest/MockAvatarManager.cpp
deleted file mode 100644
index 4a96e01..0000000
--- a/Swiften/Avatars/UnitTest/MockAvatarManager.cpp
+++ /dev/null
@@ -1,32 +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 "Swiften/Avatars/UnitTest/MockAvatarManager.h"
-
-namespace Swift {
-
-MockAvatarManager::MockAvatarManager() {
-
-}
-
-MockAvatarManager::~MockAvatarManager() {
-
-}
-
-String MockAvatarManager::getAvatarHash(const JID& jid) const {
-	return jid.toBare();
-}
-
-boost::filesystem::path MockAvatarManager::getAvatarPath(const JID& jid) const {
-	return jid.getResource().getUTF8String();
-}
-
-void MockAvatarManager::setAvatar(const JID&, const ByteArray&) {
-
-}
-
-}
-
diff --git a/Swiften/Avatars/UnitTest/MockAvatarManager.h b/Swiften/Avatars/UnitTest/MockAvatarManager.h
deleted file mode 100644
index 9f31f12..0000000
--- a/Swiften/Avatars/UnitTest/MockAvatarManager.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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 "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Client/DummyStanzaChannel.h"
-
-namespace Swift {
-	class MockAvatarManager : public AvatarManager {
-		public:
-			MockAvatarManager();
-			virtual ~MockAvatarManager();
-			virtual String getAvatarHash(const JID&) const;
-			virtual boost::filesystem::path getAvatarPath(const JID&) const;
-			virtual void setAvatar(const JID&, const ByteArray& avatar);
-		private:
-			DummyStanzaChannel channel_;
-	};
-}
diff --git a/Swiften/Elements/IQ.h b/Swiften/Elements/IQ.h
index 790d032..2bb55e1 100644
--- a/Swiften/Elements/IQ.h
+++ b/Swiften/Elements/IQ.h
@@ -8,10 +8,11 @@
 
 #include "Swiften/Elements/Stanza.h"
 #include "Swiften/Elements/ErrorPayload.h"
+#include "Swiften/Base/Shared.h"
 
 namespace Swift 
 {
-	class IQ : public Stanza
+	class IQ : public Stanza, public Shared<IQ>
 	{
 		public: 
 			enum Type { Get, Set, Result, Error };
@@ -33,8 +34,8 @@ namespace Swift
 			static boost::shared_ptr<IQ> createError(
 					const JID& to,
 					const String& id,
-					ErrorPayload::Condition condition,
-					ErrorPayload::Type type);
+					ErrorPayload::Condition condition = ErrorPayload::BadRequest,
+					ErrorPayload::Type type = ErrorPayload::Cancel);
 
 		private:
 			Type type_;
diff --git a/Swiften/QA/StorageTest/SConscript b/Swiften/QA/StorageTest/SConscript
index c7401e0..c35d5b3 100644
--- a/Swiften/QA/StorageTest/SConscript
+++ b/Swiften/QA/StorageTest/SConscript
@@ -10,6 +10,7 @@ if env["TEST"] :
 	myenv.MergeFlags(myenv["BOOST_FLAGS"])
 	myenv.MergeFlags(myenv["LIBIDN_FLAGS"])
 	myenv.MergeFlags(myenv.get("EXPAT_FLAGS", {}))
+	myenv.MergeFlags(myenv.get("LIBXML_FLAGS", {}))
 
 	tester = myenv.Program("StorageTest", [
 			"VCardFileStorageTest.cpp",
diff --git a/Swiften/QA/StorageTest/VCardFileStorageTest.cpp b/Swiften/QA/StorageTest/VCardFileStorageTest.cpp
index 9704409..694e13b 100644
--- a/Swiften/QA/StorageTest/VCardFileStorageTest.cpp
+++ b/Swiften/QA/StorageTest/VCardFileStorageTest.cpp
@@ -39,9 +39,9 @@ class VCardFileStorageTest : public CppUnit::TestFixture {
 			vcard->setFullName("Alice In Wonderland");
 			vcard->setEMail("alice@wonderland.lit");
 
-			testling->setVCard(JID("alice@wonderland.lit"), vcard);
+			testling->setVCard(JID("alice@wonderland.lit/TeaRoom"), vcard);
 
-			boost::filesystem::path vcardFile(vcardsPath / "alice@wonderland.lit.xml");
+			boost::filesystem::path vcardFile(vcardsPath / "alice@wonderland.lit%2fTeaRoom.xml");
 			CPPUNIT_ASSERT(boost::filesystem::exists(vcardFile));
 			ByteArray data;
 			data.readFromFile(vcardFile.string());
diff --git a/Swiften/Queries/Requests/GetVCardRequest.h b/Swiften/Queries/Requests/GetVCardRequest.h
index 7ebacdf..f369cdc 100644
--- a/Swiften/Queries/Requests/GetVCardRequest.h
+++ b/Swiften/Queries/Requests/GetVCardRequest.h
@@ -8,9 +8,10 @@
 
 #include "Swiften/Queries/GenericRequest.h"
 #include "Swiften/Elements/VCard.h"
+#include "Swiften/Base/Shared.h"
 
 namespace Swift {
-	class GetVCardRequest : public GenericRequest<VCard> {
+	class GetVCardRequest : public GenericRequest<VCard>, public Shared<GetVCardRequest> {
 		public:
 			GetVCardRequest(const JID& jid, IQRouter* router) : GenericRequest<VCard>(IQ::Get, jid, boost::shared_ptr<Payload>(new VCard()), router) {
 			}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index f206dd0..6e2628a 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -140,7 +140,6 @@ if env["SCONS_STAGE"] == "build" :
 
 	env.Append(UNITTEST_SOURCES = [
 			File("Application/UnitTest/ApplicationPathProviderTest.cpp"),
-			File("Avatars/UnitTest/MockAvatarManager.cpp"),
 			File("Avatars/UnitTest/AvatarManagerTest.cpp"),
 			File("Base/UnitTest/IDGeneratorTest.cpp"),
 			File("Base/UnitTest/StringTest.cpp"),
@@ -233,4 +232,5 @@ if env["SCONS_STAGE"] == "build" :
 			File("StringCodecs/UnitTest/HexifyTest.cpp"),
 			File("StringCodecs/UnitTest/HMACSHA1Test.cpp"),
 			File("StringCodecs/UnitTest/PBKDF2Test.cpp"),
+			File("VCards/UnitTest/VCardManagerTest.cpp"),
 		])
diff --git a/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp b/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp
index 19a6b6e..8975818 100644
--- a/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp
@@ -10,6 +10,7 @@
 
 #include "Swiften/Serializer/XML/XMLElement.h"
 #include "Swiften/Serializer/XML/XMLTextNode.h"
+#include "Swiften/StringCodecs/Hexify.h"
 
 namespace Swift {
 
@@ -49,7 +50,20 @@ String VCardSerializer::serializePayload(boost::shared_ptr<VCard> vcard)  const
 		nickElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcard->getNickname())));
 		queryElement.addNode(nickElement);
 	}
-	// TODO
+	if (!vcard->getPhoto().isEmpty() || !vcard->getPhotoType().isEmpty()) {
+		XMLElement::ref photoElement(new XMLElement("PHOTO"));
+		if (!vcard->getPhotoType().isEmpty()) {
+			XMLElement::ref typeElement(new XMLElement("TYPE"));
+			typeElement->addNode(XMLTextNode::ref(new XMLTextNode(vcard->getPhotoType())));
+			photoElement->addNode(typeElement);
+		}
+		if (!vcard->getPhoto().isEmpty()) {
+			XMLElement::ref binvalElement(new XMLElement("BINVAL"));
+			binvalElement->addNode(XMLTextNode::ref(new XMLTextNode(Hexify::hexify(vcard->getPhoto()))));
+			photoElement->addNode(binvalElement);
+		}
+		queryElement.addNode(photoElement);
+	}
 	return queryElement.serialize();
 }
 
diff --git a/Swiften/Serializer/XML/XMLElement.h b/Swiften/Serializer/XML/XMLElement.h
index 373a939..8447ab1 100644
--- a/Swiften/Serializer/XML/XMLElement.h
+++ b/Swiften/Serializer/XML/XMLElement.h
@@ -4,18 +4,18 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_XMLElement_H
-#define SWIFTEN_XMLElement_H
+#pragma once
 
 #include <boost/shared_ptr.hpp>
 #include <vector>
 #include <map>
 
 #include "Swiften/Base/String.h"
+#include "Swiften/Base/Shared.h"
 #include "Swiften/Serializer/XML/XMLNode.h"
 
 namespace Swift {
-	class XMLElement : public XMLNode {
+	class XMLElement : public XMLNode, public Shared<XMLElement> {
 		public:
 			XMLElement(const String& tag, const String& xmlns = "");
 
@@ -30,4 +30,3 @@ namespace Swift {
 			std::vector< boost::shared_ptr<XMLNode> > childNodes_;
 	};
 }
-#endif
diff --git a/Swiften/Serializer/XML/XMLTextNode.h b/Swiften/Serializer/XML/XMLTextNode.h
index c1d13ef..e158916 100644
--- a/Swiften/Serializer/XML/XMLTextNode.h
+++ b/Swiften/Serializer/XML/XMLTextNode.h
@@ -7,9 +7,10 @@
 #pragma once
 
 #include "Swiften/Serializer/XML/XMLNode.h"
+#include "Swiften/Base/Shared.h"
 
 namespace Swift {
-	class XMLTextNode : public XMLNode {
+	class XMLTextNode : public XMLNode, public Shared<XMLTextNode> {
 		public:
 			typedef boost::shared_ptr<XMLTextNode> ref;
 
diff --git a/Swiften/VCards/SConscript b/Swiften/VCards/SConscript
index 538eb4a..e83e633 100644
--- a/Swiften/VCards/SConscript
+++ b/Swiften/VCards/SConscript
@@ -1,6 +1,7 @@
 Import("swiften_env")
 
 objects = swiften_env.StaticObject([
+			"VCardManager.cpp",
 			"VCardStorage.cpp",
 			"VCardFileStorage.cpp",
 			"VCardStorageFactory.cpp",
diff --git a/Swiften/VCards/UnitTest/VCardManagerTest.cpp b/Swiften/VCards/UnitTest/VCardManagerTest.cpp
new file mode 100644
index 0000000..927bcfe
--- /dev/null
+++ b/Swiften/VCards/UnitTest/VCardManagerTest.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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 <vector>
+#include <boost/bind.hpp>
+
+#include "Swiften/VCards/VCardManager.h"
+#include "Swiften/VCards/VCardMemoryStorage.h"
+#include "Swiften/Queries/IQRouter.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+
+using namespace Swift;
+
+class VCardManagerTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(VCardManagerTest);
+		CPPUNIT_TEST(testGet_NewVCardRequestsVCard);
+		CPPUNIT_TEST(testGet_ExistingVCard);
+		CPPUNIT_TEST(testRequest_RequestsVCard);
+		CPPUNIT_TEST(testRequest_ReceiveEmitsNotification);
+		CPPUNIT_TEST(testRequest_Error);
+		CPPUNIT_TEST(testRequest_VCardAlreadyRequested);
+		CPPUNIT_TEST(testRequest_AfterPreviousRequest);
+		CPPUNIT_TEST(testRequestOwnVCard);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			ownJID = JID("baz@fum.com/dum");
+			stanzaChannel = new DummyStanzaChannel();
+			iqRouter = new IQRouter(stanzaChannel);
+			vcardStorage = new VCardMemoryStorage();
+		}
+
+		void tearDown() {
+			delete vcardStorage;
+			delete iqRouter;
+			delete stanzaChannel;
+		}
+
+		void testGet_NewVCardRequestsVCard() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			VCard::ref result = testling->getVCardAndRequestWhenNeeded(JID("foo@bar.com/baz"));
+
+			CPPUNIT_ASSERT(!result);
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID("foo@bar.com/baz"), IQ::Get));
+		}
+
+		void testGet_ExistingVCard() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			VCard::ref vcard(new VCard());
+			vcard->setFullName("Foo Bar");
+			vcardStorage->setVCard(JID("foo@bar.com/baz"), vcard);
+
+			VCard::ref result = testling->getVCardAndRequestWhenNeeded(JID("foo@bar.com/baz"));
+
+			CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), result->getFullName());
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+		}
+
+		void testRequest_RequestsVCard() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			testling->requestVCard(JID("foo@bar.com/baz"));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID("foo@bar.com/baz"), IQ::Get));
+		}
+
+		void testRequest_ReceiveEmitsNotification() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			testling->requestVCard(JID("foo@bar.com/baz"));
+			stanzaChannel->onIQReceived(createVCardResult());
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), changes[0].first);
+			CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), changes[0].second->getFullName());
+			CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), vcardStorage->getVCard(JID("foo@bar.com/baz"))->getFullName());
+		}
+
+		void testRequest_Error() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			testling->requestVCard(JID("foo@bar.com/baz"));
+			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), changes[0].first);
+			CPPUNIT_ASSERT_EQUAL(String(""), changes[0].second->getFullName());
+			CPPUNIT_ASSERT_EQUAL(String(""), vcardStorage->getVCard(JID("foo@bar.com/baz"))->getFullName());
+		}
+
+		void testRequest_VCardAlreadyRequested() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			testling->requestVCard(JID("foo@bar.com/baz"));
+			VCard::ref result = testling->getVCardAndRequestWhenNeeded(JID("foo@bar.com/baz"));
+
+			CPPUNIT_ASSERT(!result);
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+		}
+
+		void testRequest_AfterPreviousRequest() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			testling->requestVCard(JID("foo@bar.com/baz"));
+			stanzaChannel->onIQReceived(createVCardResult());
+			testling->requestVCard(JID("foo@bar.com/baz"));
+
+			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(1, JID("foo@bar.com/baz"), IQ::Get));
+		}
+
+		void testRequestOwnVCard() {
+			std::auto_ptr<VCardManager> testling = createManager();
+			testling->requestVCard(ownJID);
+			stanzaChannel->onIQReceived(createOwnVCardResult());
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID(), IQ::Get));
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(ownJID.toBare(), changes[0].first);
+			CPPUNIT_ASSERT_EQUAL(String("Myself"), changes[0].second->getFullName());
+			CPPUNIT_ASSERT_EQUAL(String("Myself"), vcardStorage->getVCard(ownJID.toBare())->getFullName());
+		}
+
+	private:
+		std::auto_ptr<VCardManager> createManager() {
+			std::auto_ptr<VCardManager> manager(new VCardManager(ownJID, iqRouter, vcardStorage));
+			manager->onVCardChanged.connect(boost::bind(&VCardManagerTest::handleVCardChanged, this, _1, _2));
+			return manager;
+		}
+
+		void handleVCardChanged(const JID& jid, VCard::ref vcard) {
+			changes.push_back(std::pair<JID, VCard::ref>(jid, vcard));
+		}
+
+		IQ::ref createVCardResult() {
+			VCard::ref vcard(new VCard());
+			vcard->setFullName("Foo Bar");
+			return IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[0]->getID(), vcard);
+		}
+
+		IQ::ref createOwnVCardResult() {
+			VCard::ref vcard(new VCard());
+			vcard->setFullName("Myself");
+			return IQ::createResult(JID(), stanzaChannel->sentStanzas[0]->getID(), vcard);
+		}
+
+	private:
+		JID ownJID;
+		DummyStanzaChannel* stanzaChannel;
+		IQRouter* iqRouter;
+		VCardMemoryStorage* vcardStorage;
+		std::vector< std::pair<JID, VCard::ref> > changes;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VCardManagerTest);
diff --git a/Swiften/VCards/VCardFileStorage.cpp b/Swiften/VCards/VCardFileStorage.cpp
index d3163fb..66bae04 100644
--- a/Swiften/VCards/VCardFileStorage.cpp
+++ b/Swiften/VCards/VCardFileStorage.cpp
@@ -47,7 +47,9 @@ void VCardFileStorage::setVCard(const JID& jid, boost::shared_ptr<VCard> v) {
 }
 
 boost::filesystem::path VCardFileStorage::getVCardPath(const JID& jid) const {
-	return boost::filesystem::path(vcardsPath / (jid.toBare().toString().getUTF8String() + ".xml"));
+	String file(jid.toString());
+	file.replaceAll('/', "%2f");
+	return boost::filesystem::path(vcardsPath / (file.getUTF8String() + ".xml"));
 }
 
 }
diff --git a/Swiften/VCards/VCardFileStorage.h b/Swiften/VCards/VCardFileStorage.h
index d75ac92..5f8cb1a 100644
--- a/Swiften/VCards/VCardFileStorage.h
+++ b/Swiften/VCards/VCardFileStorage.h
@@ -16,8 +16,8 @@ namespace Swift {
 		public:
 			VCardFileStorage(boost::filesystem::path dir);
 
-			virtual boost::shared_ptr<VCard> getVCard(const JID& jid) const;
-			virtual void setVCard(const JID& jid, boost::shared_ptr<VCard> v);
+			virtual VCard::ref getVCard(const JID& jid) const;
+			virtual void setVCard(const JID& jid, VCard::ref v);
 
 		private:
 			boost::filesystem::path getVCardPath(const JID&) const;
diff --git a/Swiften/VCards/VCardFileStorageFactory.h b/Swiften/VCards/VCardFileStorageFactory.h
index 136c6a7..27e50af 100644
--- a/Swiften/VCards/VCardFileStorageFactory.h
+++ b/Swiften/VCards/VCardFileStorageFactory.h
@@ -18,7 +18,7 @@ namespace Swift {
 			}
 
 			virtual VCardStorage* createVCardStorage(const String& profile) {
-				return new VCardFileStorage(base / profile.getUTF8String());
+				return new VCardFileStorage(base / profile.getUTF8String() / "vcards");
 			}
 
 		private:
diff --git a/Swiften/VCards/VCardManager.cpp b/Swiften/VCards/VCardManager.cpp
index 0174dea..628f4c8 100644
--- a/Swiften/VCards/VCardManager.cpp
+++ b/Swiften/VCards/VCardManager.cpp
@@ -6,17 +6,49 @@
 
 #include "Swiften/VCards/VCardManager.h"
 
+#include <boost/bind.hpp>
+
+#include "Swiften/JID/JID.h"
+#include "Swiften/VCards/VCardStorage.h"
+#include "Swiften/Queries/Requests/GetVCardRequest.h"
+
 namespace Swift {
 
-VCardManager::VCardManager(IQRouter* iqRouter, VCardStorage* vcardStorage) : iqRouter(iqRouter), storage(vcardStorage) {
+VCardManager::VCardManager(const JID& ownJID, IQRouter* iqRouter, VCardStorage* vcardStorage) : ownJID(ownJID), iqRouter(iqRouter), storage(vcardStorage) {
 }
 
-boost::shared_ptr<VCard> VCardManager::getVCardAndRequestWhenNeeded(const JID& jid) const {
-	boost::shared_ptr<VCard> vcard = storage->getVCard(jid);
+VCard::ref VCardManager::getVCardAndRequestWhenNeeded(const JID& jid) {
+	VCard::ref vcard = storage->getVCard(jid);
 	if (!vcard) {
-		// TODO: Request vcard if necessary
+		requestVCard(jid);
 	}
 	return vcard;
 }
-	
+
+void VCardManager::requestVCard(const JID& requestedJID) {
+	JID jid = requestedJID.equals(ownJID, JID::WithoutResource) ? JID() : requestedJID;
+	if (requestedVCards.find(jid) != requestedVCards.end()) {
+		return;
+	}
+	GetVCardRequest::ref request(new GetVCardRequest(jid, iqRouter));
+	request->onResponse.connect(boost::bind(&VCardManager::handleVCardReceived, this, jid, _1, _2));
+	request->send();
+	requestedVCards.insert(jid);
+}
+
+void VCardManager::requestOwnVCard() {
+	requestVCard(JID());
+}
+
+
+void VCardManager::handleVCardReceived(const JID& actualJID, VCard::ref vcard, const boost::optional<ErrorPayload>& error) {
+	if (error) {
+		vcard = VCard::ref(new VCard());
+	}
+	requestedVCards.erase(actualJID);
+	JID jid = actualJID.isValid() ? actualJID : ownJID.toBare();
+	storage->setVCard(jid, vcard);
+	onVCardChanged(jid, vcard);
+}
+
 }
diff --git a/Swiften/VCards/VCardManager.h b/Swiften/VCards/VCardManager.h
index bbf07c0..fc99128 100644
--- a/Swiften/VCards/VCardManager.h
+++ b/Swiften/VCards/VCardManager.h
@@ -6,23 +6,40 @@
 
 #pragma once
 
+#include <boost/signals.hpp>
+#include <set>
+
+#include "Swiften/JID/JID.h"
 #include "Swiften/Elements/VCard.h"
+#include "Swiften/Elements/ErrorPayload.h"
 
 namespace Swift {
 	class JID;
 	class VCardStorage;
+	class IQRouter;
 
 	class VCardManager {
 		public:
-			VCardManager(IQRouter* iqRouter, VCardStorage* vcardStorage);
+			VCardManager(const JID& ownJID, IQRouter* iqRouter, VCardStorage* vcardStorage);
 
-			virtual boost::shared_ptr<VCard> getVCardAndRequestWhenNeeded(const JID& jid) const ;
+			VCard::ref getVCardAndRequestWhenNeeded(const JID& jid);
+			void requestVCard(const JID& jid);
+			void requestOwnVCard();
 
 		public:
-			boost::signal<void (const JID&)> onVCardChanged;
+			/**
+			 * The JID will always be bare.
+			 */
+			boost::signal<void (const JID&, VCard::ref)> onVCardChanged;
+			boost::signal<void (VCard::ref)> onOwnVCardChanged;
+
+		private:
+			void handleVCardReceived(const JID& from, VCard::ref, const boost::optional<ErrorPayload>&);
 
 		private:
+			JID ownJID;
 			IQRouter* iqRouter;
 			VCardStorage* storage;
+			std::set<JID> requestedVCards;
 	};
 }
diff --git a/Swiften/VCards/VCardMemoryStorage.h b/Swiften/VCards/VCardMemoryStorage.h
new file mode 100644
index 0000000..b2e99c6
--- /dev/null
+++ b/Swiften/VCards/VCardMemoryStorage.h
@@ -0,0 +1,38 @@
+/*
+ * 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/shared_ptr.hpp>
+#include <map>
+
+#include "Swiften/JID/JID.h"
+#include "Swiften/VCards/VCardStorage.h"
+
+namespace Swift {
+	class VCardMemoryStorage : public VCardStorage {
+		public:
+			VCardMemoryStorage() {}
+
+			virtual VCard::ref getVCard(const JID& jid) const {
+				VCardMap::const_iterator i = vcards.find(jid);
+				if (i != vcards.end()) {
+					return i->second;
+				}
+				else {
+					return VCard::ref();
+				}
+			}
+
+			virtual void setVCard(const JID& jid, VCard::ref v) {
+				vcards[jid] = v;
+			}
+
+		private:
+			typedef std::map<JID, VCard::ref> VCardMap;
+			VCardMap vcards;
+	};
+}
diff --git a/Swiften/VCards/VCardStorage.h b/Swiften/VCards/VCardStorage.h
index 2a9044c..854ccc6 100644
--- a/Swiften/VCards/VCardStorage.h
+++ b/Swiften/VCards/VCardStorage.h
@@ -17,7 +17,7 @@ namespace Swift {
 		public:
 			virtual ~VCardStorage();
 
-			virtual boost::shared_ptr<VCard> getVCard(const JID& jid) const = 0;
-			virtual void setVCard(const JID&, boost::shared_ptr<VCard>) = 0;
+			virtual VCard::ref getVCard(const JID& jid) const = 0;
+			virtual void setVCard(const JID&, VCard::ref) = 0;
 	};
 }
-- 
cgit v0.10.2-6-g49f6