From 6eb30e0e1f0a8e7ee936f3c006c7f710785653df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sat, 20 Jun 2009 13:47:21 +0200
Subject: Added vCard-based avatars support.


diff --git a/Swift/Controllers/ChatController.cpp b/Swift/Controllers/ChatController.cpp
index 8c0b8bb..39e9144 100644
--- a/Swift/Controllers/ChatController.cpp
+++ b/Swift/Controllers/ChatController.cpp
@@ -9,8 +9,8 @@ namespace Swift {
 /**
  * The controller does not gain ownership of the stanzaChannel, nor the factory.
  */
-ChatController::ChatController(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle)
- : ChatControllerBase(stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle) {
+ChatController::ChatController(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager)
+ : ChatControllerBase(stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager) {
 	nickResolver_ = nickResolver;
 }
 
@@ -28,7 +28,7 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<Message> message
 }
 
 void ChatController::postSendMessage(const String& body) {
-	chatWindow_->addMessage(body, "me", true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel() : boost::optional<SecurityLabel>());
+	chatWindow_->addMessage(body, "me", true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel() : boost::optional<SecurityLabel>(), "");
 }
 
 String ChatController::senderDisplayNameFromMessage(JID from) {
diff --git a/Swift/Controllers/ChatController.h b/Swift/Controllers/ChatController.h
index 98e66bc..265bc91 100644
--- a/Swift/Controllers/ChatController.h
+++ b/Swift/Controllers/ChatController.h
@@ -4,17 +4,20 @@
 #include "Swift/Controllers/ChatControllerBase.h"
 
 namespace Swift {
+	class AvatarManager;
 	class NickResolver;
 	class ChatController : public ChatControllerBase {
 		public:
-			ChatController(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle);
-			~ChatController() {};
+			ChatController(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager*);
+
 			//boost::signal<void (const JID&, const JID&)> onJIDChanged;
+		
 		protected:
 			bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
 			void postSendMessage(const String &body);
 			void preHandleIncomingMessage(boost::shared_ptr<Message> message);
 			String senderDisplayNameFromMessage(JID from);
+
 		private:
 			NickResolver* nickResolver_;
 			JID contact_;
diff --git a/Swift/Controllers/ChatControllerBase.cpp b/Swift/Controllers/ChatControllerBase.cpp
index 0d22571..e84ee04 100644
--- a/Swift/Controllers/ChatControllerBase.cpp
+++ b/Swift/Controllers/ChatControllerBase.cpp
@@ -3,6 +3,7 @@
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 
+#include "Swiften/Avatars/AvatarManager.h"
 #include "Swiften/Client/StanzaChannel.h"
 #include "Swiften/Base/foreach.h"
 #include "Swift/Controllers/ChatWindow.h"
@@ -11,7 +12,7 @@
 
 namespace Swift {
 
-ChatControllerBase::ChatControllerBase(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle) {
+ChatControllerBase::ChatControllerBase(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
 	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1));
@@ -130,7 +131,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 		boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
 		boost::optional<SecurityLabel> maybeLabel = label ? boost::optional<SecurityLabel>(*label) : boost::optional<SecurityLabel>();
 		JID from = message->getFrom();
-		chatWindow_->addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), maybeLabel);
+		chatWindow_->addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), maybeLabel, String(avatarManager_->getAvatarPath(from).string()));
 	}
 }
 
diff --git a/Swift/Controllers/ChatControllerBase.h b/Swift/Controllers/ChatControllerBase.h
index 1967977..58d3a1d 100644
--- a/Swift/Controllers/ChatControllerBase.h
+++ b/Swift/Controllers/ChatControllerBase.h
@@ -19,6 +19,7 @@ namespace Swift {
 	class StanzaChannel;
 	class ChatWindow;
 	class ChatWindowFactory;
+	class AvatarManager;
 
 	class ChatControllerBase  {
 		public:
@@ -28,7 +29,8 @@ namespace Swift {
 			void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
 
 		protected:
-			ChatControllerBase(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle);
+			ChatControllerBase(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager);
+
 			virtual void postSendMessage(const String&) {};
 			virtual String senderDisplayNameFromMessage(JID from);
 			void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence);
@@ -36,6 +38,14 @@ namespace Swift {
 			virtual void preHandleIncomingMessage(boost::shared_ptr<Message>) {};
 			virtual void preSendMessageRequest(boost::shared_ptr<Message>) {};
 
+		private:
+			void handleSendMessageRequest(const String &body);
+			String getStatusChangeString(boost::shared_ptr<Presence> presence);
+			void handleAllMessagesRead();
+			void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, const boost::optional<Error>& error);
+			String getErrorMessage(boost::shared_ptr<Error>);
+
+		protected:
 			std::vector<boost::shared_ptr<MessageEvent> > unreadMessages_;
 			StanzaChannel* stanzaChannel_;
 			IQRouter* iqRouter_;
@@ -44,13 +54,7 @@ namespace Swift {
 			JID toJID_;
 			bool labelsEnabled_;
 			PresenceOracle* presenceOracle_;
-
-		private:
-			void handleSendMessageRequest(const String &body);
-			String getStatusChangeString(boost::shared_ptr<Presence> presence);
-			void handleAllMessagesRead();
-			void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, const boost::optional<Error>& error);
-			String getErrorMessage(boost::shared_ptr<Error>);
+			AvatarManager* avatarManager_;
 	};
 }
 
diff --git a/Swift/Controllers/ChatWindow.h b/Swift/Controllers/ChatWindow.h
index 04d0007..dd71bf9 100644
--- a/Swift/Controllers/ChatWindow.h
+++ b/Swift/Controllers/ChatWindow.h
@@ -10,12 +10,15 @@
 #include "Swiften/Elements/SecurityLabel.h"
 
 namespace Swift {
+	class AvatarManager;
 	class TreeWidget;
+
 	class ChatWindow {
 		public:
+			ChatWindow() {}
 			virtual ~ChatWindow() {};
 
-			virtual void addMessage(const String& message, const String& senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label) = 0;
+			virtual void addMessage(const String& message, const String& senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath) = 0;
 			virtual void addSystemMessage(const String& message) = 0;
 			virtual void addErrorMessage(const String& message) = 0;
 
diff --git a/Swift/Controllers/MUCController.cpp b/Swift/Controllers/MUCController.cpp
index 93880c4..27ddcb8 100644
--- a/Swift/Controllers/MUCController.cpp
+++ b/Swift/Controllers/MUCController.cpp
@@ -23,8 +23,9 @@ MUCController::MUCController (
 		IQRouter* iqRouter, 
 		ChatWindowFactory* chatWindowFactory, 
 		TreeWidgetFactory *treeWidgetFactory,
-		PresenceOracle* presenceOracle) : 
-			ChatControllerBase(stanzaChannel, iqRouter, chatWindowFactory, muc, presenceOracle),
+		PresenceOracle* presenceOracle,
+		AvatarManager* avatarManager) : 
+			ChatControllerBase(stanzaChannel, iqRouter, chatWindowFactory, muc, presenceOracle, avatarManager),
 			muc_(new MUC(stanzaChannel, muc)), 
 			nick_(nick), 
 			treeWidgetFactory_(treeWidgetFactory) { 
diff --git a/Swift/Controllers/MUCController.h b/Swift/Controllers/MUCController.h
index c87695e..b2f396c 100644
--- a/Swift/Controllers/MUCController.h
+++ b/Swift/Controllers/MUCController.h
@@ -18,10 +18,11 @@ namespace Swift {
 	class ChatWindowFactory;
 	class Roster;
 	class TreeWidgetFactory;
+	class AvatarManager;
 
 	class MUCController : public ChatControllerBase {
 		public:
-			MUCController(const JID &muc, const String &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, TreeWidgetFactory *treeWidgetFactory, PresenceOracle* presenceOracle);
+			MUCController(const JID &muc, const String &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, TreeWidgetFactory *treeWidgetFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager);
 			~MUCController();
 		
 		protected:
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index e9177f8..20dfaa1 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -32,6 +32,8 @@
 #include "Swiften/Queries/Responders/DiscoInfoResponder.h"
 #include "Swiften/Disco/CapsInfoGenerator.h"
 #include "Swiften/Queries/Requests/GetDiscoInfoRequest.h"
+#include "Swiften/Avatars/AvatarFileStorage.h"
+#include "Swiften/Avatars/AvatarManager.h"
 
 namespace Swift {
 
@@ -45,8 +47,11 @@ typedef std::pair<JID, MUCController*> JIDMUCControllerPair;
 MainController::MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, TreeWidgetFactory *treeWidgetFactory, SettingsProvider *settings, Application* application, SystemTray* systemTray)
 		: client_(NULL), chatWindowFactory_(chatWindowFactory), mainWindowFactory_(mainWindowFactory), loginWindowFactory_(loginWindowFactory), treeWidgetFactory_(treeWidgetFactory), settings_(settings),
 		xmppRosterController_(NULL), rosterController_(NULL), loginWindow_(NULL), clientVersionResponder_(NULL), nickResolver_(NULL), discoResponder_(NULL), 
-		serverDiscoInfo_(new DiscoInfo()), presenceOracle_(NULL) {
+		serverDiscoInfo_(new DiscoInfo()), presenceOracle_(NULL), avatarManager_(NULL) {
 	application_ = application;
+
+	avatarStorage_ = new AvatarFileStorage(application_->getAvatarDir());
+
 	eventController_ = new EventController();
 	eventController_->onEventQueueLengthChange.connect(boost::bind(&MainController::handleEventQueueLengthChange, this, _1));
 	systemTrayController_ = new SystemTrayController(eventController_, systemTray);
@@ -55,6 +60,7 @@ MainController::MainController(ChatWindowFactory* chatWindowFactory, MainWindowF
 }
 
 MainController::~MainController() {
+	delete avatarManager_;
 	delete discoResponder_;
 	delete clientVersionResponder_;
 	delete xmppRosterController_;
@@ -69,6 +75,7 @@ MainController::~MainController() {
 	delete nickResolver_;
 	delete client_;
 	delete systemTrayController_;
+	delete avatarStorage_;
 }
 
 void MainController::handleConnected() {
@@ -97,6 +104,9 @@ void MainController::handleConnected() {
 	clientVersionResponder_ = new SoftwareVersionResponder(CLIENT_NAME, CLIENT_VERSION, client_);
 	loginWindow_->morphInto(rosterController_->getWindow());
 
+	delete avatarManager_;
+	avatarManager_ = new AvatarManager(client_, client_, avatarStorage_);
+
 	DiscoInfo discoInfo;
 	discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc"));
 	capsInfo_ = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator(CLIENT_NODE).generateCapsInfo(discoInfo)));
@@ -206,7 +216,7 @@ ChatController* MainController::getChatController(const JID &contact) {
 		lookupContact = JID(contact.toBare());
 	}
 	if (chatControllers_.find(lookupContact) == chatControllers_.end()) {
-		chatControllers_[contact] = new ChatController(client_, client_, chatWindowFactory_, contact, nickResolver_, presenceOracle_);
+		chatControllers_[contact] = new ChatController(client_, client_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_);
 		chatControllers_[contact]->setAvailableServerFeatures(serverDiscoInfo_);
 		lookupContact = contact;
 	}
@@ -219,7 +229,7 @@ void MainController::handleChatControllerJIDChanged(const JID& from, const JID&
 }
 
 void MainController::handleJoinMUCRequest(const JID &muc, const String &nick) {
-	mucControllers_[muc] = new MUCController(muc, nick, client_, client_, chatWindowFactory_, treeWidgetFactory_, presenceOracle_);
+	mucControllers_[muc] = new MUCController(muc, nick, client_, client_, chatWindowFactory_, treeWidgetFactory_, presenceOracle_, avatarManager_);
 	mucControllers_[muc]->setAvailableServerFeatures(serverDiscoInfo_);
 }
 
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index 8b3a702..aa6a85b 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 namespace Swift {
+	class AvatarStorage;
 	class Application;
 	class Client;
 	class ChatWindowFactory;
@@ -30,6 +31,7 @@ namespace Swift {
 	class RosterController;
 	class XMPPRosterController;
 	class DiscoInfoResponder;
+	class AvatarManager;
 	class LoginWindow;
 	class EventLoop;
 	class SoftwareVersionResponder;
@@ -68,6 +70,7 @@ namespace Swift {
 			TreeWidgetFactory* treeWidgetFactory_;
 			SettingsProvider *settings_;
 			Application* application_;
+			AvatarStorage* avatarStorage_;
 			ChatController* chatController_;
 			XMPPRosterController* xmppRosterController_;
 			RosterController* rosterController_;
@@ -82,6 +85,7 @@ namespace Swift {
 			boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
 			PresenceOracle* presenceOracle_;
 			SystemTrayController* systemTrayController_;
+			AvatarManager* avatarManager_;
 	};
 }
 #endif
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 5b63e55..8e916c3 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -126,7 +126,7 @@ void QtChatWindow::updateTitleWithUnreadCount() {
 	emit titleUpdated();
 }
 
-void QtChatWindow::addMessage(const String &message, const String &senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label) {
+void QtChatWindow::addMessage(const String &message, const String &senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath) {
 	if (isActiveWindow()) {
 		onAllMessagesRead();
 	}
@@ -141,7 +141,7 @@ void QtChatWindow::addMessage(const String &message, const String &senderName, b
 	htmlString += messageHTML;
 
 	bool appendToPrevious = !previousMessageWasSystem_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName)));
-	messageLog_->addMessage(MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), QDateTime::currentDateTime(), "qrc:/icons/avatar.png", senderIsSelf, appendToPrevious));
+	messageLog_->addMessage(MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), QDateTime::currentDateTime(), (avatarPath.isEmpty() ? "qrc:/icons/avatar.png" : "file://" + P2QSTRING(avatarPath)), senderIsSelf, appendToPrevious));
 
 	previousMessageWasSelf_ = senderIsSelf;
 	previousSenderName_ = P2QSTRING(senderName);
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index bbb1a7e..20a53b9 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -18,7 +18,7 @@ namespace Swift {
 		Q_OBJECT
 		public:
 			QtChatWindow(const QString &contact, QtTreeWidgetFactory* treeWidgetFactory);
-			void addMessage(const String &message, const String &senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label);
+			void addMessage(const String &message, const String &senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath);
 			void addSystemMessage(const String& message);
 			void addErrorMessage(const String& errorMessage);
 			void show();
diff --git a/Swiften/Application/Application.cpp b/Swiften/Application/Application.cpp
index e21d9e7..6ffb46f 100644
--- a/Swiften/Application/Application.cpp
+++ b/Swiften/Application/Application.cpp
@@ -17,6 +17,10 @@ boost::filesystem::path Application::getSettingsFileName() const {
 	return getSettingsDir() / "settings";
 }
 
+boost::filesystem::path Application::getAvatarDir() const {
+	return getSettingsDir() / "avatars";
+}
+
 boost::filesystem::path Application::getHomeDir() const {
 	// FIXME: Does this work on windows?
 	char* homeDirRaw = getenv("HOME");
diff --git a/Swiften/Application/Application.h b/Swiften/Application/Application.h
index 4fda7af..a65b1de 100644
--- a/Swiften/Application/Application.h
+++ b/Swiften/Application/Application.h
@@ -14,6 +14,7 @@ namespace Swift {
 			virtual ~Application();
 
 			boost::filesystem::path getSettingsFileName() const;
+			boost::filesystem::path getAvatarDir() const;
 			boost::filesystem::path getHomeDir() const;
 			virtual boost::filesystem::path getSettingsDir() const = 0;
 			boost::filesystem::path getProfileDir(const String& profile) const;
diff --git a/Swiften/Avatars/AvatarFileStorage.cpp b/Swiften/Avatars/AvatarFileStorage.cpp
new file mode 100644
index 0000000..1348018
--- /dev/null
+++ b/Swiften/Avatars/AvatarFileStorage.cpp
@@ -0,0 +1,26 @@
+#include "Swiften/Avatars/AvatarFileStorage.h"
+
+#include <iostream>
+#include <boost/filesystem/fstream.hpp>
+
+namespace Swift {
+
+AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& path) : path_(path) {
+	boost::filesystem::create_directory(path_);
+}
+
+bool AvatarFileStorage::hasAvatar(const String& hash) const { 
+	return boost::filesystem::exists(getAvatarPath(hash));
+}
+
+void AvatarFileStorage::addAvatar(const String& hash, const ByteArray& avatar) {
+	boost::filesystem::ofstream file(getAvatarPath(hash), boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out);
+	file.write(avatar.getData(), avatar.getSize());
+	file.close();
+}
+
+boost::filesystem::path AvatarFileStorage::getAvatarPath(const String& hash) const {
+	return path_ / hash.getUTF8String();
+}
+
+}
diff --git a/Swiften/Avatars/AvatarFileStorage.h b/Swiften/Avatars/AvatarFileStorage.h
new file mode 100644
index 0000000..1afa703
--- /dev/null
+++ b/Swiften/Avatars/AvatarFileStorage.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <map>
+#include <boost/filesystem.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/Base/ByteArray.h"
+#include "Swiften/Avatars/AvatarStorage.h"
+
+namespace Swift {
+	class AvatarFileStorage : public AvatarStorage {
+		public:
+			AvatarFileStorage(const boost::filesystem::path& path);
+
+			virtual bool hasAvatar(const String& hash) const;
+			virtual void addAvatar(const String& hash, const ByteArray& avatar);
+
+			virtual boost::filesystem::path getAvatarPath(const String& hash) const;
+
+		private:
+			boost::filesystem::path path_;
+	};
+
+}
diff --git a/Swiften/Avatars/AvatarManager.cpp b/Swiften/Avatars/AvatarManager.cpp
new file mode 100644
index 0000000..f0b04b9
--- /dev/null
+++ b/Swiften/Avatars/AvatarManager.cpp
@@ -0,0 +1,65 @@
+#include "Swiften/Avatars/AvatarManager.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/Avatars/AvatarStorage.h"
+
+namespace Swift {
+
+AvatarManager::AvatarManager(StanzaChannel* stanzaChannel, IQRouter* iqRouter, AvatarStorage* avatarStorage) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), avatarStorage_(avatarStorage) {
+	stanzaChannel->onPresenceReceived.connect(boost::bind(&AvatarManager::handlePresenceReceived, this, _1));
+}
+
+void AvatarManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
+	boost::shared_ptr<VCardUpdate> update = presence->getPayload<VCardUpdate>();
+	if (!update) {
+		return;
+	}
+	JID from = presence->getFrom().toBare();
+	String& hash = avatarHashes_[from];
+	if (hash != update->getPhotoHash()) {
+		hash = update->getPhotoHash();
+		if (!avatarStorage_->hasAvatar(hash)) {
+			boost::shared_ptr<GetVCardRequest> request(new GetVCardRequest(from, iqRouter_));
+			request->onResponse.connect(boost::bind(&AvatarManager::handleVCardReceived, this, from, _1, _2));
+			request->send();
+		}
+		else {
+			onAvatarChanged(from, hash);
+		}
+	}
+}
+
+void AvatarManager::handleVCardReceived(JID from, boost::shared_ptr<VCard> vCard, const boost::optional<Error>& error) {
+	if (error) {
+		// FIXME: What to do here?
+		return;
+	}
+	String hash = SHA1::getHexHash(vCard->getPhoto());
+	avatarStorage_->addAvatar(hash, vCard->getPhoto());
+	onAvatarChanged(from, hash);
+}
+
+String AvatarManager::getAvatarHash(const JID& jid) const {
+	std::map<JID, String>::const_iterator i = avatarHashes_.find(jid.toBare());
+	if (i != avatarHashes_.end()) {
+		return i->second;
+	}
+	else {
+		return "";
+	}
+}
+
+boost::filesystem::path AvatarManager::getAvatarPath(const JID& jid) const {
+	String hash = getAvatarHash(jid);
+	if (!hash.isEmpty()) {
+		return avatarStorage_->getAvatarPath(hash);
+	}
+	return boost::filesystem::path();
+}
+
+}
diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h
new file mode 100644
index 0000000..0085405
--- /dev/null
+++ b/Swiften/Avatars/AvatarManager.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <boost/filesystem.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+#include <boost/signal.hpp>
+#include <map>
+
+#include "Swiften/JID/JID.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/Elements/VCard.h"
+#include "Swiften/Elements/Error.h"
+
+namespace Swift {
+	class AvatarStorage;
+	class StanzaChannel;
+	class IQRouter;
+
+	class AvatarManager {
+		public:
+			AvatarManager(StanzaChannel*, IQRouter*, AvatarStorage*);
+
+			String getAvatarHash(const JID&) const;
+			boost::filesystem::path getAvatarPath(const JID&) const;
+
+		public:
+			boost::signal<void (const JID&, const String&)> onAvatarChanged;
+
+		private:
+			void handlePresenceReceived(boost::shared_ptr<Presence>);
+			void handleVCardReceived(JID from, boost::shared_ptr<VCard>, const boost::optional<Error>&);
+
+		private:
+			StanzaChannel* stanzaChannel_;
+			IQRouter* iqRouter_;
+			AvatarStorage* avatarStorage_;
+			std::map<JID, String> avatarHashes_;
+	};
+}
diff --git a/Swiften/Avatars/AvatarStorage.cpp b/Swiften/Avatars/AvatarStorage.cpp
new file mode 100644
index 0000000..4c98314
--- /dev/null
+++ b/Swiften/Avatars/AvatarStorage.cpp
@@ -0,0 +1,8 @@
+#include "Swiften/Avatars/AvatarStorage.h"
+
+namespace Swift {
+
+AvatarStorage::~AvatarStorage() {
+}
+
+}
diff --git a/Swiften/Avatars/AvatarStorage.h b/Swiften/Avatars/AvatarStorage.h
new file mode 100644
index 0000000..b5c0f32
--- /dev/null
+++ b/Swiften/Avatars/AvatarStorage.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <boost/filesystem.hpp>
+
+namespace Swift {
+	class String;
+	class ByteArray;
+
+	class AvatarStorage {
+		public:
+			virtual ~AvatarStorage();
+
+			virtual bool hasAvatar(const String& hash) const = 0;
+			virtual void addAvatar(const String& hash, const ByteArray& avatar) = 0;
+			virtual boost::filesystem::path getAvatarPath(const String& hash) const = 0;
+	};
+
+}
diff --git a/Swiften/Avatars/Makefile.inc b/Swiften/Avatars/Makefile.inc
new file mode 100644
index 0000000..4887a49
--- /dev/null
+++ b/Swiften/Avatars/Makefile.inc
@@ -0,0 +1,6 @@
+SWIFTEN_SOURCES += \
+	Swiften/Avatars/AvatarManager.cpp \
+	Swiften/Avatars/AvatarStorage.cpp \
+	Swiften/Avatars/AvatarFileStorage.cpp
+
+include Swiften/Avatars/UnitTest/Makefile.inc
diff --git a/Swiften/Avatars/UnitTest/Makefile.inc b/Swiften/Avatars/UnitTest/Makefile.inc
new file mode 100644
index 0000000..e69de29
diff --git a/Swiften/Makefile.inc b/Swiften/Makefile.inc
index ce14110..4b09bf3 100644
--- a/Swiften/Makefile.inc
+++ b/Swiften/Makefile.inc
@@ -20,6 +20,7 @@ include Swiften/Disco/Makefile.inc
 include Swiften/Presence/Makefile.inc
 include Swiften/Notifier/Makefile.inc
 include Swiften/History/Makefile.inc
+include Swiften/Avatars/Makefile.inc
 
 CPPFLAGS += $(SQLITE_CPPFLAGS)
 
diff --git a/Swiften/Queries/Requests/GetVCardRequest.h b/Swiften/Queries/Requests/GetVCardRequest.h
new file mode 100644
index 0000000..e403096
--- /dev/null
+++ b/Swiften/Queries/Requests/GetVCardRequest.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <cassert>
+
+#include "Swiften/Queries/GenericRequest.h"
+#include "Swiften/Elements/VCard.h"
+
+namespace Swift {
+	class GetVCardRequest : public GenericRequest<VCard> {
+		public:
+			GetVCardRequest(const JID& jid, IQRouter* router) :
+					GenericRequest<VCard>(IQ::Get, jid, boost::shared_ptr<Payload>(new VCard()), router) {
+				assert(jid.isBare());
+			}
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 9799802..f9a0789 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -15,6 +15,7 @@
 #include "Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h"
+#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h"
 
 namespace Swift {
 
@@ -33,6 +34,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new StartSessionSerializer());
 	serializers_.push_back(new SecurityLabelSerializer());
 	serializers_.push_back(new SecurityLabelsCatalogSerializer());
+	serializers_.push_back(new VCardSerializer());
 	foreach(PayloadSerializer* serializer, serializers_) {
 		addSerializer(serializer);
 	}
diff --git a/Swiften/Serializer/PayloadSerializers/Makefile.inc b/Swiften/Serializer/PayloadSerializers/Makefile.inc
index 61f603a..893da6c 100644
--- a/Swiften/Serializer/PayloadSerializers/Makefile.inc
+++ b/Swiften/Serializer/PayloadSerializers/Makefile.inc
@@ -8,6 +8,7 @@ SWIFTEN_SOURCES += \
 	Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp \
 	Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp \
 	Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp \
-	Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp
+	Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp \
+	Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp
 
 include Swiften/Serializer/PayloadSerializers/UnitTest/Makefile.inc
diff --git a/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp b/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp
new file mode 100644
index 0000000..ce4e399
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp
@@ -0,0 +1,18 @@
+#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Serializer/XML/XMLElement.h"
+
+namespace Swift {
+
+VCardSerializer::VCardSerializer() : GenericPayloadSerializer<VCard>() {
+}
+
+String VCardSerializer::serializePayload(boost::shared_ptr<VCard> discoInfo)  const {
+	XMLElement queryElement("vCard", "vcard-temp");
+	// TODO
+	return queryElement.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/VCardSerializer.h b/Swiften/Serializer/PayloadSerializers/VCardSerializer.h
new file mode 100644
index 0000000..baf5947
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/VCardSerializer.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "Swiften/Serializer/GenericPayloadSerializer.h"
+#include "Swiften/Elements/VCard.h"
+
+namespace Swift {
+	class VCardSerializer : public GenericPayloadSerializer<VCard> {
+		public:
+			VCardSerializer();
+
+			virtual String serializePayload(boost::shared_ptr<VCard>)  const;
+	};
+}
-- 
cgit v0.10.2-6-g49f6