From ef1052bbdb315aaa1c6254098ea05638d9a25b2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 12 Sep 2010 20:29:39 +0200
Subject: Added presence notifier.


diff --git a/SwifTools/Notifier/GrowlNotifier.cpp b/SwifTools/Notifier/GrowlNotifier.cpp
index 4c671ac..7ea7193 100644
--- a/SwifTools/Notifier/GrowlNotifier.cpp
+++ b/SwifTools/Notifier/GrowlNotifier.cpp
@@ -17,22 +17,30 @@
 namespace {
 	struct Context {
 		Context() {}
-		Context(const boost::function<void()>& callback) : callback(callback) {}
+		Context(const boost::function<void()>& callback) : callback(new boost::function<void()>(callback)) {}
 
-		boost::function<void()> callback;
+		boost::function<void()>* callback;
 	};
 
-	void notificationClicked(CFPropertyListRef growlContext) {
+	void processNotification(CFPropertyListRef growlContext, bool activateCallback) {
 		Context context;
 
 		CFDataRef growlContextData = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) growlContext, 0);
 		assert(CFDataGetLength(growlContextData) == sizeof(Context));
 		CFDataGetBytes(growlContextData, CFRangeMake(0, CFDataGetLength(growlContextData)), (UInt8*) &context);
 		
-		context.callback();
+		if (activateCallback) {
+			(*context.callback)();
+		}
+		delete context.callback;
+	}
+
+	void notificationClicked(CFPropertyListRef growlContext) {
+		processNotification(growlContext, true);
 	}
 
-	void notificationTimedout(CFPropertyListRef) {
+	void notificationTimedout(CFPropertyListRef growlContext) {
+		processNotification(growlContext, false);
 	}
 }
 
diff --git a/SwifTools/Notifier/LoggingNotifier.h b/SwifTools/Notifier/LoggingNotifier.h
new file mode 100644
index 0000000..93349d9
--- /dev/null
+++ b/SwifTools/Notifier/LoggingNotifier.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 "SwifTools/Notifier/Notifier.h"
+#include "Swiften/Base/ByteArray.h"
+
+namespace Swift {
+	class LoggingNotifier : public Notifier {
+		public:
+			virtual void showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) {
+				notifications.push_back(Notification(type, subject, description, picture, callback));
+			}
+
+			struct Notification {
+					Notification(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) : type(type), subject(subject), description(description), picture(picture), callback(callback) {}
+					Type type;
+					String subject;
+					String description;
+					ByteArray picture;
+					boost::function<void()> callback;
+			};
+
+			std::vector<Notification> notifications;
+	};
+}
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 9154b9a..e621a6d 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -31,7 +31,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	chatStateMessageSender_ = new ChatStateMessageSender(chatStateNotifier_, stanzaChannel, contact);
 	chatStateTracker_ = new ChatStateTracker();
 	nickResolver_ = nickResolver;
-	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1, _2));
+	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1));
 	chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1));
 	stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1));
 	String nick = nickResolver_->jidToNick(toJID_);
@@ -134,19 +134,20 @@ String ChatController::getStatusChangeString(boost::shared_ptr<Presence> presenc
 	return "";
 }
 
-void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence) {
+void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
 	if (!toJID_.equals(newPresence->getFrom(), toJID_.isBare() ? JID::WithoutResource : JID::WithResource)) {
 		return;
 	}
 
-	chatStateTracker_->handlePresenceChange(newPresence, previousPresence);
+	chatStateTracker_->handlePresenceChange(newPresence);
 	String newStatusChangeString = getStatusChangeString(newPresence);
-	if (!previousPresence || newStatusChangeString != getStatusChangeString(previousPresence)) {
+	if (newStatusChangeString != lastStatusChangeString_) {
 		if (lastWasPresence_) {
 			chatWindow_->replaceLastMessage(newStatusChangeString);
 		} else {
 			chatWindow_->addPresenceMessage(newStatusChangeString);
 		}
+		lastStatusChangeString_ = newStatusChangeString;
 		lastWasPresence_ = true;
 	}
 }
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 971fca9..c226ed8 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -23,7 +23,7 @@ namespace Swift {
 			virtual void setEnabled(bool enabled);
 
 		private:
-			void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence);
+			void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
 			String getStatusChangeString(boost::shared_ptr<Presence> presence);
 			bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
 			void postSendMessage(const String &body, boost::shared_ptr<Stanza> sentStanza);
@@ -41,6 +41,7 @@ namespace Swift {
 			ChatStateTracker* chatStateTracker_;
 			bool isInMUC_;
 			bool lastWasPresence_;
+			String lastStatusChangeString_;
 			std::map<boost::shared_ptr<Stanza>, String> unackedStanzas_;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index e1a53b4..08b1453 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -42,7 +42,7 @@ ChatsManager::ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRo
 	mucBookmarkManager_->onBookmarksReady.connect(boost::bind(&ChatsManager::handleBookmarksReady, this));
 	mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&ChatsManager::handleMUCBookmarkAdded, this, _1));
 	mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&ChatsManager::handleMUCBookmarkRemoved, this, _1));
-	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1, _2));
+	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
 	uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
 	chatListWindow_ = chatListWindowFactory->createWindow(uiEventStream_);
 	if (chatListWindow_) {
@@ -122,7 +122,7 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 /**
  * If a resource goes offline, release bound chatdialog to that resource.
  */
-void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> /*lastPresence*/) {
+void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
 	if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return;
 	if (newPresence->getType() != Presence::Unavailable) return;
 	JID fullJID(newPresence->getFrom());
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 752acff..17a5d94 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -47,7 +47,7 @@ namespace Swift {
 			void handleChatRequest(const String& contact);
 			void handleJoinMUCRequest(const JID& muc, const boost::optional<String>& nick);
 			void rebindControllerJID(const JID& from, const JID& to);
-			void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> lastPresence);
+			void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
 			void handleUIEvent(boost::shared_ptr<UIEvent> event);
 			void handleMUCBookmarkAdded(const MUCBookmark& bookmark);
 			void handleMUCBookmarkRemoved(const MUCBookmark& bookmark);
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index ffd5185..bf27dd5 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -59,7 +59,7 @@ public:
 		iqRouter_ = new IQRouter(iqChannel_);
 		eventController_ = new EventController();
 		chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
-		xmppRoster_ = boost::shared_ptr<XMPPRoster>(new XMPPRoster());
+		xmppRoster_ = new XMPPRoster();
 		mucRegistry_ = new MUCRegistry();
 		nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, NULL, mucRegistry_);
 		presenceOracle_ = new PresenceOracle(stanzaChannel_);
@@ -195,7 +195,7 @@ public:
 		boost::shared_ptr<Presence> jid1Offline(new Presence());
 		jid1Offline->setFrom(JID(fullJIDString1));
 		jid1Offline->setType(Presence::Unavailable);
-		presenceOracle_->onPresenceChange(jid1Offline, jid1Online);
+		presenceOracle_->onPresenceChange(jid1Offline);
 
 		boost::shared_ptr<Message> message2(new Message());
 		message2->setFrom(JID(fullJIDString2));
@@ -273,14 +273,14 @@ public:
 		boost::shared_ptr<Presence> jid1Offline(new Presence());
 		jid1Offline->setFrom(JID(messageJID1));
 		jid1Offline->setType(Presence::Unavailable);
-		presenceOracle_->onPresenceChange(jid1Offline, jid1Online);
+		presenceOracle_->onPresenceChange(jid1Offline);
  
 		boost::shared_ptr<Presence> jid2Online(new Presence());
 		jid2Online->setFrom(JID(messageJID2));
 		boost::shared_ptr<Presence> jid2Offline(new Presence());
 		jid2Offline->setFrom(JID(messageJID2));
 		jid2Offline->setType(Presence::Unavailable);
-		presenceOracle_->onPresenceChange(jid2Offline, jid2Online);
+		presenceOracle_->onPresenceChange(jid2Offline);
 
 		JID messageJID3("testling@test.com/resource3");
 
@@ -311,7 +311,7 @@ private:
 	PresenceOracle* presenceOracle_;
 	AvatarManager* avatarManager_;
 	boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
-	boost::shared_ptr<XMPPRoster> xmppRoster_;
+	XMPPRoster* xmppRoster_;
 	PresenceSender* presenceSender_;
 	MockRepository* mocks_;
 	UIEventStream* uiEventStream_;
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 834dacd..6221f21 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -12,11 +12,10 @@
 #include <stdlib.h>
 #include <sstream>
 
-#include "Swiften/Network/TimerFactory.h"
+#include "Swiften/Network/BoostTimerFactory.h"
 #include "Swiften/Network/BoostIOServiceThread.h"
 #include "Swiften/Network/MainBoostIOServiceThread.h"
 #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"
@@ -38,6 +37,7 @@
 #include "Swift/Controllers/XMLConsoleController.h"
 #include "Swift/Controllers/XMPPRosterController.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "Swift/Controllers/PresenceNotifier.h"
 #include "SwifTools/Dock/Dock.h"
 #include "Swiften/Base/foreach.h"
 #include "Swiften/Base/String.h"
@@ -59,6 +59,7 @@
 #include "Swiften/Disco/EntityCapsManager.h"
 #include "Swiften/StringCodecs/SHA1.h"
 #include "Swiften/StringCodecs/Hexify.h"
+#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
 
 namespace Swift {
 
@@ -94,22 +95,27 @@ MainController::MainController(
 			vcardStorageFactory_(vcardStorageFactory),
 			loginWindow_(NULL) ,
 			useDelayForLatency_(useDelayForLatency) {
+
+	statusTracker_ = NULL;
+	client_ = NULL;
+	presenceSender_ = NULL;
 	presenceOracle_ = NULL;
-	chatsManager_ = NULL;
-	eventController_ = NULL;
-	eventWindowController_ = NULL;
-	nickResolver_ = NULL;
 	mucRegistry_ = NULL;
-	avatarManager_ = NULL;
+	xmppRoster_ = NULL;
 	vcardManager_ = NULL;
+	avatarManager_ = NULL;
+	capsManager_ = NULL;
+	entityCapsManager_ = NULL;
+	presenceNotifier_ = NULL;
+	nickResolver_ = NULL;
 	rosterController_ = NULL;
 	xmppRosterController_ = NULL;
+	chatsManager_ = NULL;
+	eventWindowController_ = NULL;
 	clientVersionResponder_ = NULL;
 	discoResponder_ = NULL;
-	presenceSender_ = NULL;
-	client_ = NULL;
 	mucSearchController_ = NULL;
-	statusTracker_ = NULL;
+
 
 	timeBeforeNextReconnect_ = -1;
 	mucSearchWindowFactory_ = mucSearchWindowFactory;
@@ -177,13 +183,24 @@ void MainController::resetClient() {
 	resetCurrentError();
 	resetPendingReconnects();
 	serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>();
-	xmppRoster_ = boost::shared_ptr<XMPPRoster>();
+	delete mucSearchController_;
+	mucSearchController_ = NULL;
+	delete discoResponder_;
+	discoResponder_ = NULL;
+	delete clientVersionResponder_;
+	clientVersionResponder_ = NULL;
+	delete eventWindowController_;
+	eventWindowController_ = NULL;
+	delete xmppRosterController_;
+	xmppRosterController_ = NULL;
 	delete chatsManager_;
 	chatsManager_ = NULL;
-	delete presenceOracle_;
-	presenceOracle_ = NULL;
+	delete rosterController_;
+	rosterController_ = NULL;
 	delete nickResolver_;
 	nickResolver_ = NULL;
+	delete presenceNotifier_;
+	presenceNotifier_ = NULL;
 	delete entityCapsManager_;
 	entityCapsManager_ = NULL;
 	delete capsManager_;
@@ -192,28 +209,20 @@ void MainController::resetClient() {
 	avatarManager_ = NULL;
 	delete vcardManager_;
 	vcardManager_ = NULL;
-	delete eventWindowController_;
-	eventWindowController_ = NULL;
-	delete rosterController_;
-	rosterController_ = NULL;
-	delete xmppRosterController_;
-	xmppRosterController_ = NULL;
-	delete clientVersionResponder_;
-	clientVersionResponder_ = NULL;
-	delete discoResponder_;
-	discoResponder_ = NULL;
+	delete xmppRoster_;
+	xmppRoster_ = NULL;
+	delete mucRegistry_;
+	mucRegistry_ = NULL;
+	delete presenceOracle_;
+	presenceOracle_ = NULL;
 	delete presenceSender_;
 	presenceSender_ = NULL;
 	delete client_;
 	client_ = NULL;
-	delete mucSearchController_;
-	mucSearchController_ = NULL;
 	delete statusTracker_;
 	statusTracker_ = NULL;
 	delete profileSettings_;
 	profileSettings_ = NULL;
-	delete mucRegistry_;
-	mucRegistry_ = NULL;
 }
 
 void MainController::resetPendingReconnects() {
@@ -239,18 +248,10 @@ void MainController::handleConnected() {
 	bool freshLogin = rosterController_ == NULL;
 	if (freshLogin) {
 		serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
-		xmppRoster_ = boost::shared_ptr<XMPPRoster>(new XMPPRoster());
-		presenceOracle_ = new PresenceOracle(client_);
-		mucRegistry_ = new MUCRegistry();
-		vcardManager_ = new VCardManager(jid_, client_->getIQRouter(), getVCardStorageForProfile(jid_));
-		vcardManager_->onVCardChanged.connect(boost::bind(&MainController::handleVCardReceived, this, _1, _2));
-		avatarManager_ = new AvatarManagerImpl(vcardManager_, client_, avatarStorage_, mucRegistry_);
-		capsManager_ = new CapsManager(capsStorage_, client_, client_->getIQRouter());
-		entityCapsManager_ = new EntityCapsManager(capsManager_, client_);
 
 		nickResolver_ = new NickResolver(this->jid_.toBare(), xmppRoster_, vcardManager_, mucRegistry_);
 
-		rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, eventController_, uiEventStream_, client_->getIQRouter());
+		rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, presenceSender_, eventController_, uiEventStream_, client_->getIQRouter());
 		rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));
 		rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this));
 
@@ -384,6 +385,16 @@ void MainController::performLoginFromCachedCredentials() {
 	if (!client_) {
 		client_ = new Swift::Client(jid_, password_);
 		presenceSender_ = new PresenceSender(client_);
+		presenceOracle_ = new PresenceOracle(client_);
+		mucRegistry_ = new MUCRegistry();
+		xmppRoster_ = new XMPPRoster();
+		vcardManager_ = new VCardManager(jid_, client_->getIQRouter(), getVCardStorageForProfile(jid_));
+		vcardManager_->onVCardChanged.connect(boost::bind(&MainController::handleVCardReceived, this, _1, _2));
+		avatarManager_ = new AvatarManagerImpl(vcardManager_, client_, avatarStorage_, mucRegistry_);
+		capsManager_ = new CapsManager(capsStorage_, client_, client_->getIQRouter());
+		entityCapsManager_ = new EntityCapsManager(capsManager_, client_);
+		presenceNotifier_ = new PresenceNotifier(client_, notifier_, mucRegistry_, avatarManager_, xmppRoster_, presenceOracle_, &timerFactory_);
+		presenceNotifier_->onNotificationActivated.connect(boost::bind(&MainController::handleNotificationClicked, this, _1));
 		client_->onDataRead.connect(boost::bind(
 				&XMLConsoleController::handleDataRead, xmlConsoleController_, _1));
 		client_->onDataWritten.connect(boost::bind(
@@ -510,6 +521,11 @@ void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) {
 	}
 }
 
+void MainController::handleNotificationClicked(const JID& jid) {
+	assert(chatsManager_);
+	uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(jid)));
+}
+
 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));
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index 7fbf54f..df12a17 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -56,6 +56,7 @@ namespace Swift {
 	class MUCController;
 	class Notifier;
 	class PresenceOracle;
+	class PresenceNotifier;
 	class SystemTray;
 	class SystemTrayController;
 	class SoundEventController;
@@ -115,6 +116,7 @@ namespace Swift {
 			void performLoginFromCachedCredentials();
 			void reconnectAfterError();
 			void setManagersEnabled(bool enabled);
+			void handleNotificationClicked(const JID& jid);
 
 			VCardStorage* getVCardStorageForProfile(const JID& jid);
 
@@ -137,7 +139,7 @@ namespace Swift {
 			VCardManager* vcardManager_;
 			Dock* dock_;
 			Notifier* notifier_;
-			ChatController* chatController_;
+			PresenceNotifier* presenceNotifier_;
 			XMPPRosterController* xmppRosterController_;
 			RosterController* rosterController_;
 			EventController* eventController_;
@@ -151,7 +153,7 @@ namespace Swift {
 			ChatsManager* chatsManager_;
 			boost::shared_ptr<CapsInfo> capsInfo_;
 			boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
-			boost::shared_ptr<XMPPRoster> xmppRoster_;;
+			XMPPRoster* xmppRoster_;;
 			JID jid_;
 			PresenceOracle* presenceOracle_;
 			SystemTrayController* systemTrayController_;
diff --git a/Swift/Controllers/NickResolver.cpp b/Swift/Controllers/NickResolver.cpp
index b6fefe3..8faada9 100644
--- a/Swift/Controllers/NickResolver.cpp
+++ b/Swift/Controllers/NickResolver.cpp
@@ -15,7 +15,7 @@
 
 namespace Swift {
 
-NickResolver::NickResolver(const JID& ownJID, boost::shared_ptr<XMPPRoster> xmppRoster, VCardManager* vcardManager, MUCRegistry* mucRegistry) : ownJID_(ownJID) {
+NickResolver::NickResolver(const JID& ownJID, XMPPRoster* xmppRoster, VCardManager* vcardManager, MUCRegistry* mucRegistry) : ownJID_(ownJID) {
 	xmppRoster_ = xmppRoster;
 	vcardManager_ = vcardManager;
 	if (vcardManager_) {
diff --git a/Swift/Controllers/NickResolver.h b/Swift/Controllers/NickResolver.h
index 24081b2..b5ed76f 100644
--- a/Swift/Controllers/NickResolver.h
+++ b/Swift/Controllers/NickResolver.h
@@ -21,7 +21,7 @@ namespace Swift {
 	class VCardManager;
 	class NickResolver {
 		public:
-			NickResolver(const JID& ownJID, boost::shared_ptr<XMPPRoster> xmppRoster, VCardManager* vcardManager, MUCRegistry* mucRegistry);
+			NickResolver(const JID& ownJID, XMPPRoster* xmppRoster, VCardManager* vcardManager, MUCRegistry* mucRegistry);
 
 			String jidToNick(const JID& jid);
 			void setMUCRegistry(MUCRegistry* registry);
@@ -33,7 +33,7 @@ namespace Swift {
 			String ownNick_;
 			std::map<JID, String> map_;
 
-			boost::shared_ptr<XMPPRoster> xmppRoster_;
+			XMPPRoster* xmppRoster_;
 			MUCRegistry* mucRegistry_;
 			VCardManager* vcardManager_;
 	};
diff --git a/Swift/Controllers/PresenceNotifier.cpp b/Swift/Controllers/PresenceNotifier.cpp
new file mode 100644
index 0000000..ce7ae40
--- /dev/null
+++ b/Swift/Controllers/PresenceNotifier.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/PresenceNotifier.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Client/StanzaChannel.h"
+#include "Swiften/Base/ByteArray.h"
+#include "Swiften/MUC/MUCRegistry.h"
+#include "Swiften/Roster/XMPPRoster.h"
+#include "Swiften/Presence/PresenceOracle.h"
+#include "Swiften/Network/TimerFactory.h"
+
+namespace Swift {
+
+PresenceNotifier::PresenceNotifier(StanzaChannel* stanzaChannel, Notifier* notifier, const MUCRegistry* mucRegistry, AvatarManager* avatarManager, const XMPPRoster* roster, const PresenceOracle* presenceOracle, TimerFactory* timerFactory) : stanzaChannel(stanzaChannel), notifier(notifier), mucRegistry(mucRegistry), avatarManager(avatarManager), roster(roster), presenceOracle(presenceOracle), timerFactory(timerFactory) {
+	justInitialized = true;
+	inQuietPeriod = false;
+	stanzaChannel->onPresenceReceived.connect(boost::bind(&PresenceNotifier::handlePresenceReceived, this, _1));
+	stanzaChannel->onAvailableChanged.connect(boost::bind(&PresenceNotifier::handleStanzaChannelAvailableChanged, this, _1));
+	setInitialQuietPeriodMS(3000);
+}
+
+PresenceNotifier::~PresenceNotifier() {
+	if (timer) {
+		timer->stop();
+		timer->onTick.disconnect(boost::bind(&PresenceNotifier::handleTimerTick, this));
+		timer.reset();
+	}
+	stanzaChannel->onAvailableChanged.disconnect(boost::bind(&PresenceNotifier::handleStanzaChannelAvailableChanged, this, _1));
+	stanzaChannel->onPresenceReceived.disconnect(boost::bind(&PresenceNotifier::handlePresenceReceived, this, _1));
+}
+
+void PresenceNotifier::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
+	JID from = presence->getFrom();
+
+	if (mucRegistry->isMUC(from.toBare())) {
+		return;
+	}
+
+	if (justInitialized) {
+		justInitialized = false;
+		if (timer) {
+			inQuietPeriod = true;
+		}
+	}
+
+	if (inQuietPeriod) {
+		timer->stop();
+		timer->start();
+		return;
+	}
+
+	std::set<JID>::iterator i = availableUsers.find(from);
+	if (presence->isAvailable()) {
+		if (i != availableUsers.end()) {
+			showNotification(from, Notifier::ContactStatusChange);
+		}
+		else {
+			showNotification(from, Notifier::ContactAvailable);
+			availableUsers.insert(from);
+		}
+	}
+	else {
+		if (i != availableUsers.end()) {
+			showNotification(from, Notifier::ContactUnavailable);
+			availableUsers.erase(i);
+		}
+	}
+}
+
+void PresenceNotifier::handleStanzaChannelAvailableChanged(bool available) {
+	if (available) {
+		availableUsers.clear();
+		justInitialized = true;
+		if (timer) {
+			timer->stop();
+		}
+	}
+}
+
+void PresenceNotifier::showNotification(const JID& jid, Notifier::Type type) {
+	String name = roster->getNameForJID(jid);
+	if (name.isEmpty()) {
+		name = jid.toBare().toString();
+	}
+	String title = name + " (" + getStatusType(jid) + ")";
+	String message = getStatusMessage(jid);
+	notifier->showMessage(type, title, message, avatarManager->getAvatar(jid), boost::bind(&PresenceNotifier::handleNotificationActivated, this, jid));
+}
+
+void PresenceNotifier::handleNotificationActivated(JID jid) {
+	onNotificationActivated(jid);
+}
+
+String PresenceNotifier::getStatusType(const JID& jid) const {
+	Presence::ref presence = presenceOracle->getLastPresence(jid);
+	if (presence) {
+		return StatusShow::typeToFriendlyName(presence->getShow());
+	}
+	else {
+		return "Unavailable";
+	}
+}
+
+String PresenceNotifier::getStatusMessage(const JID& jid) const {
+	Presence::ref presence = presenceOracle->getLastPresence(jid);
+	if (presence) {
+		return presence->getStatus();
+	}
+	else {
+		return String();
+	}
+}
+
+void PresenceNotifier::setInitialQuietPeriodMS(int ms) {
+	if (timer) {
+		timer->stop();
+		timer->onTick.disconnect(boost::bind(&PresenceNotifier::handleTimerTick, this));
+		timer.reset();
+	}
+	if (ms > 0) {
+		timer = timerFactory->createTimer(ms);
+		timer->onTick.connect(boost::bind(&PresenceNotifier::handleTimerTick, this));
+	}
+}
+
+void PresenceNotifier::handleTimerTick() {
+	inQuietPeriod = false;
+	timer->stop();
+}
+
+
+}
diff --git a/Swift/Controllers/PresenceNotifier.h b/Swift/Controllers/PresenceNotifier.h
new file mode 100644
index 0000000..f5bf3d4
--- /dev/null
+++ b/Swift/Controllers/PresenceNotifier.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <set>
+
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/JID/JID.h"
+#include "SwifTools/Notifier/Notifier.h"
+#include "Swiften/Avatars/AvatarManager.h"
+#include "Swiften/Network/Timer.h"
+
+namespace Swift {
+	class TimerFactory;
+	class StanzaChannel;
+	class MUCRegistry;
+	class XMPPRoster;
+	class PresenceOracle;
+
+	class PresenceNotifier {
+		public:
+			PresenceNotifier(StanzaChannel* stanzaChannel, Notifier* notifier, const MUCRegistry* mucRegistry, AvatarManager* avatarManager, const XMPPRoster* roster, const PresenceOracle* presenceOracle, TimerFactory* timerFactory);
+			~PresenceNotifier();
+
+			void setInitialQuietPeriodMS(int ms);
+
+			boost::signal<void (const JID&)> onNotificationActivated;
+
+		private:
+			void handlePresenceReceived(boost::shared_ptr<Presence>);
+			void handleStanzaChannelAvailableChanged(bool);
+			void handleNotificationActivated(JID jid);
+			void handleTimerTick();
+			String getStatusType(const JID&) const;
+			String getStatusMessage(const JID&) const;
+
+		private:
+			void showNotification(const JID& jid, Notifier::Type type);
+
+		private:
+			StanzaChannel* stanzaChannel;
+			Notifier* notifier;
+			const MUCRegistry* mucRegistry;
+			AvatarManager* avatarManager;
+			const XMPPRoster* roster;
+			const PresenceOracle* presenceOracle;
+			TimerFactory* timerFactory;
+			boost::shared_ptr<Timer> timer;
+			bool justInitialized;
+			bool inQuietPeriod;
+			std::set<JID> availableUsers;
+	};
+}
+
diff --git a/Swift/Controllers/RosterController.cpp b/Swift/Controllers/RosterController.cpp
index 7285f38..da10e5b 100644
--- a/Swift/Controllers/RosterController.cpp
+++ b/Swift/Controllers/RosterController.cpp
@@ -17,6 +17,7 @@
 #include "Swiften/Events/SubscriptionRequestEvent.h"
 #include "Swiften/Events/ErrorEvent.h"
 #include "Swiften/Presence/PresenceOracle.h"
+#include "Swiften/Presence/PresenceSender.h"
 #include "Swift/Controllers/EventController.h"
 #include "Swiften/Queries/IQRouter.h"
 #include "Swiften/Roster/Roster.h"
@@ -35,10 +36,11 @@ namespace Swift {
 /**
  * The controller does not gain ownership of these parameters.
  */
-RosterController::RosterController(const JID& jid, boost::shared_ptr<XMPPRoster> xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter)
+RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter)
  : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()) {
 	iqRouter_ = iqRouter;
 	presenceOracle_ = presenceOracle;
+	presenceSender_ = presenceSender;
 	eventController_ = eventController;
 	roster_->addFilter(offlineFilter_);
 	mainWindow_->setRosterModel(roster_);
@@ -51,7 +53,7 @@ RosterController::RosterController(const JID& jid, boost::shared_ptr<XMPPRoster>
 	xmppRoster_->onJIDRemoved.connect(boost::bind(&RosterController::handleOnJIDRemoved, this, _1));
 	xmppRoster_->onRosterCleared.connect(boost::bind(&RosterController::handleRosterCleared, this));
 	presenceOracle_->onPresenceSubscriptionRequest.connect(boost::bind(&RosterController::handleSubscriptionRequest, this, _1, _2));
-	presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handleIncomingPresence, this, _1, _2));
+	presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handleIncomingPresence, this, _1));
 	uiEventConnection_ = uiEventStream->onUIEvent.connect(boost::bind(&RosterController::handleUIEvent, this, _1));
 	avatarManager_ = avatarManager;
 	avatarManager_->onAvatarChanged.connect(boost::bind(&RosterController::handleAvatarChanged, this, _1));
@@ -156,7 +158,7 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 		boost::shared_ptr<SetRosterRequest> request(new SetRosterRequest(roster, iqRouter_));
 		request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster));
 		request->send();
-		presenceOracle_->requestSubscription(addContactEvent->getJID());
+		presenceSender_->requestSubscription(addContactEvent->getJID());
 		return;
 	}
 	boost::shared_ptr<RemoveRosterItemUIEvent> removeEvent = boost::dynamic_pointer_cast<RemoveRosterItemUIEvent>(event);
@@ -185,13 +187,13 @@ void RosterController::handleRosterSetError(boost::optional<ErrorPayload> error,
 	eventController_->handleIncomingEvent(errorEvent);
 }
 
-void RosterController::handleIncomingPresence(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> /*oldPresence*/) {
+void RosterController::handleIncomingPresence(boost::shared_ptr<Presence> newPresence) {
 	roster_->applyOnItems(SetPresence(newPresence));
 }
 
 void RosterController::handleSubscriptionRequest(const JID& jid, const String& message) {
 	if (xmppRoster_->containsJID(jid) && (xmppRoster_->getSubscriptionStateForJID(jid) == RosterItemPayload::To || xmppRoster_->getSubscriptionStateForJID(jid) == RosterItemPayload::Both)) {
-		presenceOracle_->confirmSubscription(jid);
+		presenceSender_->confirmSubscription(jid);
 		return;
 	}
 	SubscriptionRequestEvent* eventPointer = new SubscriptionRequestEvent(jid, message);
@@ -202,14 +204,14 @@ void RosterController::handleSubscriptionRequest(const JID& jid, const String& m
 }
 
 void RosterController::handleSubscriptionRequestAccepted(SubscriptionRequestEvent* event) {
-	presenceOracle_->confirmSubscription(event->getJID());
+	presenceSender_->confirmSubscription(event->getJID());
 	if (!xmppRoster_->containsJID(event->getJID()) || xmppRoster_->getSubscriptionStateForJID(event->getJID()) == RosterItemPayload::None || xmppRoster_->getSubscriptionStateForJID(event->getJID()) == RosterItemPayload::From) {
-		presenceOracle_->requestSubscription(event->getJID());
+		presenceSender_->requestSubscription(event->getJID());
 	}
 }
 
 void RosterController::handleSubscriptionRequestDeclined(SubscriptionRequestEvent* event) {
-	presenceOracle_->cancelSubscription(event->getJID());
+	presenceSender_->cancelSubscription(event->getJID());
 }
 
 void RosterController::handleAvatarChanged(const JID& jid) {
diff --git a/Swift/Controllers/RosterController.h b/Swift/Controllers/RosterController.h
index 389df44..80e7e3e 100644
--- a/Swift/Controllers/RosterController.h
+++ b/Swift/Controllers/RosterController.h
@@ -27,6 +27,7 @@ namespace Swift {
 	class OfflineRosterFilter;
 	class NickResolver;
 	class PresenceOracle;
+	class PresenceSender;
 	class EventController;
 	class SubscriptionRequestEvent;
 	class UIEventStream;
@@ -34,7 +35,7 @@ namespace Swift {
 
 	class RosterController {
 		public:
-			RosterController(const JID& jid, boost::shared_ptr<XMPPRoster> xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_);
+			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_);
 			~RosterController();
 			void showRosterWindow();
 			MainWindow* getWindow() {return mainWindow_;};
@@ -51,7 +52,7 @@ namespace Swift {
 			void handleStartChatRequest(const JID& contact);
 			void handleChangeStatusRequest(StatusShow::Type show, const String &statusText);
 			void handleShowOfflineToggled(bool state);
-			void handleIncomingPresence(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> oldPresence);
+			void handleIncomingPresence(boost::shared_ptr<Presence> newPresence);
 			void handleSubscriptionRequest(const JID& jid, const String& message);
 			void handleSubscriptionRequestAccepted(SubscriptionRequestEvent* event);
 			void handleSubscriptionRequestDeclined(SubscriptionRequestEvent* event);
@@ -59,7 +60,7 @@ namespace Swift {
 			void handleRosterSetError(boost::optional<ErrorPayload> error, boost::shared_ptr<RosterPayload> rosterPayload);
 			void handleOwnNickChanged(const String& nick);
 			JID myJID_;
-			boost::shared_ptr<XMPPRoster> xmppRoster_;
+			XMPPRoster* xmppRoster_;
 			MainWindowFactory* mainWindowFactory_;
 			MainWindow* mainWindow_;
 			Roster* roster_;
@@ -67,6 +68,7 @@ namespace Swift {
 			AvatarManager* avatarManager_;
 			NickResolver* nickResolver_;
 			PresenceOracle* presenceOracle_;
+			PresenceSender* presenceSender_;
 			EventController* eventController_;
 			IQRouter* iqRouter_;
 			boost::bsignals::scoped_connection changeStatusConnection_;
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 1ccee64..30c9590 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -36,6 +36,7 @@ if env["SCONS_STAGE"] == "build" :
 			"SystemTrayController.cpp",
 			"XMLConsoleController.cpp",
 			"StatusTracker.cpp",
+			"PresenceNotifier.cpp",
 			"UIEvents/UIEvent.cpp",
 			"UIInterfaces/XMLConsoleWidget.cpp",
 			"UIInterfaces/ChatListWindow.cpp",
@@ -47,6 +48,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("UnitTest/RosterControllerTest.cpp"),
 			File("UnitTest/XMPPRosterControllerTest.cpp"),
 			File("UnitTest/PreviousStatusStoreTest.cpp"),
+			File("UnitTest/PresenceNotifierTest.cpp"),
 			File("Chat/UnitTest/ChatsManagerTest.cpp"),
 			File("Chat/UnitTest/MUCControllerTest.cpp"),
 			File("UnitTest/MockChatWindow.cpp"),
diff --git a/Swift/Controllers/UnitTest/NickResolverTest.cpp b/Swift/Controllers/UnitTest/NickResolverTest.cpp
index dfb459f..f42a28a 100644
--- a/Swift/Controllers/UnitTest/NickResolverTest.cpp
+++ b/Swift/Controllers/UnitTest/NickResolverTest.cpp
@@ -35,7 +35,7 @@ class NickResolverTest : public CppUnit::TestFixture {
 	public:
 		void setUp() {
 			ownJID_ = JID("kev@wonderland.lit");
-			xmppRoster_ = boost::shared_ptr<XMPPRoster>(new XMPPRoster());
+			xmppRoster_ = new XMPPRoster();
 			stanzaChannel_ = new DummyStanzaChannel();
 		  iqRouter_ = new IQRouter(stanzaChannel_);
 			vCardStorage_ = new VCardMemoryStorage();
@@ -135,7 +135,7 @@ class NickResolverTest : public CppUnit::TestFixture {
 	
 	private:
 		std::vector<String> groups_;
-		boost::shared_ptr<XMPPRoster> xmppRoster_;
+		XMPPRoster* xmppRoster_;
 		VCardStorage* vCardStorage_;
 		IQRouter* iqRouter_;
 		DummyStanzaChannel* stanzaChannel_;
diff --git a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
new file mode 100644
index 0000000..85433f3
--- /dev/null
+++ b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
@@ -0,0 +1,310 @@
+/*
+ * 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 "Swift/Controllers/PresenceNotifier.h"
+#include "SwifTools/Notifier/LoggingNotifier.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+#include "Swiften/MUC/MUCRegistry.h"
+#include "Swiften/Roster/XMPPRoster.h"
+#include "Swiften/Presence/PresenceOracle.h"
+#include "Swiften/Avatars/DummyAvatarManager.h"
+#include "Swiften/Network/DummyTimerFactory.h"
+
+using namespace Swift;
+
+class PresenceNotifierTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(PresenceNotifierTest);
+		CPPUNIT_TEST(testReceiveFirstPresenceCreatesAvailableNotification);
+		CPPUNIT_TEST(testReceiveSecondPresenceCreatesStatusChangeNotification);
+		CPPUNIT_TEST(testReceiveUnavailablePresenceAfterAvailablePresenceCreatesUnavailableNotification);
+		CPPUNIT_TEST(testReceiveUnavailablePresenceWithoutAvailableDoesNotCreateNotification);
+		CPPUNIT_TEST(testReceiveAvailablePresenceAfterUnavailableCreatesAvailableNotification);
+		CPPUNIT_TEST(testReceiveAvailablePresenceAfterReconnectCreatesAvailableNotification);
+		CPPUNIT_TEST(testReceiveAvailablePresenceFromMUCDoesNotCreateNotification);
+		CPPUNIT_TEST(testNotificationSubjectContainsNameForJIDInRoster);
+		CPPUNIT_TEST(testNotificationSubjectContainsJIDForJIDNotInRoster);
+		CPPUNIT_TEST(testNotificationSubjectContainsStatus);
+		CPPUNIT_TEST(testNotificationMessageContainsStatusMessage);
+		CPPUNIT_TEST(testNotificationPicture);
+		CPPUNIT_TEST(testNotificationActivationEmitsSignal);
+		CPPUNIT_TEST(testReceiveFirstPresenceWithQuietPeriodDoesNotNotify);
+		CPPUNIT_TEST(testReceiveFirstPresenceWithQuietPeriodDoesNotCountAsQuietPeriod);
+		CPPUNIT_TEST(testReceivePresenceDuringQuietPeriodDoesNotNotify);
+		CPPUNIT_TEST(testReceivePresenceDuringQuietPeriodResetsTimer);
+		CPPUNIT_TEST(testReceivePresenceAfterQuietPeriodNotifies);
+		CPPUNIT_TEST(testReceiveFirstPresenceAfterReconnectWithQuietPeriodDoesNotNotify);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			stanzaChannel = new DummyStanzaChannel();
+			notifier = new LoggingNotifier();
+			mucRegistry = new MUCRegistry();
+			user1 = JID("user1@bar.com/bla");
+			user2 = JID("user2@foo.com/baz");
+			avatarManager = new DummyAvatarManager();
+			roster = new XMPPRoster();
+			presenceOracle = new PresenceOracle(stanzaChannel);
+			timerFactory = new DummyTimerFactory();
+		}
+
+		void tearDown() {
+			delete presenceOracle;
+			delete roster;
+			delete avatarManager;
+			delete mucRegistry;
+			delete notifier;
+			delete stanzaChannel;
+		}
+
+		void testReceiveFirstPresenceCreatesAvailableNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+
+			sendPresence(user1, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT_EQUAL(Notifier::ContactAvailable, notifier->notifications[0].type);
+		}
+
+		void testReceiveSecondPresenceCreatesStatusChangeNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			sendPresence(user1, StatusShow::Away);
+			notifier->notifications.clear();
+
+			sendPresence(user1, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT_EQUAL(Notifier::ContactStatusChange, notifier->notifications[0].type);
+		}
+
+		void testReceiveUnavailablePresenceAfterAvailablePresenceCreatesUnavailableNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			sendPresence(user1, StatusShow::Away);
+			notifier->notifications.clear();
+
+			sendUnavailablePresence(user1);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT_EQUAL(Notifier::ContactUnavailable, notifier->notifications[0].type);
+		}
+
+		void testReceiveUnavailablePresenceWithoutAvailableDoesNotCreateNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+
+			sendUnavailablePresence(user1);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testReceiveAvailablePresenceAfterUnavailableCreatesAvailableNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			sendPresence(user1, StatusShow::Away);
+			sendUnavailablePresence(user1);
+			notifier->notifications.clear();
+
+			sendPresence(user1, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT_EQUAL(Notifier::ContactAvailable, notifier->notifications[0].type);
+		}
+
+		void testReceiveAvailablePresenceAfterReconnectCreatesAvailableNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			sendPresence(user1, StatusShow::Away);
+			stanzaChannel->setAvailable(false);
+			stanzaChannel->setAvailable(true);
+			notifier->notifications.clear();
+
+			sendPresence(user1, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT_EQUAL(Notifier::ContactAvailable, notifier->notifications[0].type);
+		}
+
+		void testReceiveAvailablePresenceFromMUCDoesNotCreateNotification() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			mucRegistry->addMUC(JID("teaparty@wonderland.lit"));
+
+			sendPresence(JID("teaparty@wonderland.lit/Alice"), StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testNotificationPicture() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			avatarManager->avatars[user1] = ByteArray("abcdef");
+
+			sendPresence(user1, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT_EQUAL(ByteArray("abcdef"), notifier->notifications[0].picture);
+		}
+
+		void testNotificationActivationEmitsSignal() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+
+			sendPresence(user1, StatusShow::Online);
+			CPPUNIT_ASSERT(notifier->notifications[0].callback);
+			notifier->notifications[0].callback();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(activatedNotifications.size()));
+			CPPUNIT_ASSERT_EQUAL(user1, activatedNotifications[0]);
+		}
+
+		void testNotificationSubjectContainsNameForJIDInRoster() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			roster->addContact(user1.toBare(), "User 1", std::vector<String>(), RosterItemPayload::Both);
+
+			sendPresence(user1, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT(notifier->notifications[0].subject.contains("User 1"));
+		}
+
+		void testNotificationSubjectContainsJIDForJIDNotInRoster() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+
+			sendPresence(user1, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT(notifier->notifications[0].subject.contains(user1.toBare().toString()));
+		}
+
+		void testNotificationSubjectContainsStatus() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+
+			sendPresence(user1, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT(notifier->notifications[0].subject.contains("Away"));
+		}
+
+		void testNotificationMessageContainsStatusMessage() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+
+			sendPresence(user1, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+			CPPUNIT_ASSERT(notifier->notifications[0].description.contains("Status Message"));
+		}
+
+		void testReceiveFirstPresenceWithQuietPeriodDoesNotNotify() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			testling->setInitialQuietPeriodMS(10);
+
+			sendPresence(user1, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testReceivePresenceDuringQuietPeriodDoesNotNotify() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			testling->setInitialQuietPeriodMS(10);
+
+			sendPresence(user1, StatusShow::Online);
+			timerFactory->setTime(1);
+			sendPresence(user2, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testReceivePresenceDuringQuietPeriodResetsTimer() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			testling->setInitialQuietPeriodMS(10);
+
+			sendPresence(user1, StatusShow::Online);
+			timerFactory->setTime(9);
+			sendPresence(user2, StatusShow::Away);
+			timerFactory->setTime(18);
+			sendPresence(user1, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testReceivePresenceAfterQuietPeriodNotifies() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			testling->setInitialQuietPeriodMS(10);
+
+			sendPresence(user1, StatusShow::Online);
+			timerFactory->setTime(11);
+			sendPresence(user2, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testReceiveFirstPresenceWithQuietPeriodDoesNotCountAsQuietPeriod() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			testling->setInitialQuietPeriodMS(10);
+
+			timerFactory->setTime(11);
+			sendPresence(user1, StatusShow::Away);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+		void testReceiveFirstPresenceAfterReconnectWithQuietPeriodDoesNotNotify() {
+			std::auto_ptr<PresenceNotifier> testling = createNotifier();
+			testling->setInitialQuietPeriodMS(10);
+			sendPresence(user1, StatusShow::Online);
+			timerFactory->setTime(15);
+			notifier->notifications.clear();
+
+			stanzaChannel->setAvailable(false);
+			stanzaChannel->setAvailable(true);
+			sendPresence(user1, StatusShow::Online);
+			timerFactory->setTime(21);
+			sendPresence(user2, StatusShow::Online);
+
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(notifier->notifications.size()));
+		}
+
+
+	private:
+		std::auto_ptr<PresenceNotifier> createNotifier() {
+			std::auto_ptr<PresenceNotifier> result(new PresenceNotifier(stanzaChannel, notifier, mucRegistry, avatarManager, roster, presenceOracle, timerFactory));
+			result->onNotificationActivated.connect(boost::bind(&PresenceNotifierTest::handleNotificationActivated, this, _1));
+			result->setInitialQuietPeriodMS(0);
+			return result;
+		}
+
+		void sendPresence(const JID& jid, StatusShow::Type type) {
+			boost::shared_ptr<Presence> presence(new Presence());
+			presence->setFrom(jid);
+			presence->setShow(type);
+			presence->setStatus("Status Message");
+			stanzaChannel->onPresenceReceived(presence);
+		}
+
+		void sendUnavailablePresence(const JID& jid) {
+			boost::shared_ptr<Presence> presence(new Presence());
+			presence->setType(Presence::Unavailable);
+			presence->setFrom(jid);
+			stanzaChannel->onPresenceReceived(presence);
+		}
+
+		void handleNotificationActivated(const JID& j) {
+			activatedNotifications.push_back(j);
+		}
+
+	private:
+		DummyStanzaChannel* stanzaChannel;
+		LoggingNotifier* notifier;
+		MUCRegistry* mucRegistry;
+		DummyAvatarManager* avatarManager;
+		XMPPRoster* roster;
+		PresenceOracle* presenceOracle;
+		DummyTimerFactory* timerFactory;
+		JID user1;
+		JID user2;
+		std::vector<JID> activatedNotifications;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PresenceNotifierTest);
diff --git a/Swift/Controllers/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/UnitTest/RosterControllerTest.cpp
index fdcc44f..174b682 100644
--- a/Swift/Controllers/UnitTest/RosterControllerTest.cpp
+++ b/Swift/Controllers/UnitTest/RosterControllerTest.cpp
@@ -23,6 +23,7 @@
 #include "Swiften/Avatars/NullAvatarManager.h"
 #include "Swift/Controllers/EventController.h"
 #include "Swiften/Presence/PresenceOracle.h"
+#include "Swiften/Presence/PresenceSender.h"
 #include "Swift/Controllers/NickResolver.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
 #include "Swiften/MUC/MUCRegistry.h"
@@ -30,6 +31,7 @@
 using namespace Swift;
 
 #define CHILDREN mainWindow_->roster->getRoot()->getChildren()
+
 class RosterControllerTest : public CppUnit::TestFixture
 {
 		CPPUNIT_TEST_SUITE(RosterControllerTest);
@@ -43,7 +45,7 @@ class RosterControllerTest : public CppUnit::TestFixture
 
 		void setUp() {
 			jid_ = JID("testjid@swift.im/swift");
-			xmppRoster_ = boost::shared_ptr<XMPPRoster>(new XMPPRoster());
+			xmppRoster_ = new XMPPRoster();
 			avatarManager_ = new NullAvatarManager();
 			mainWindowFactory_ = new MockMainWindowFactory();
 			mucRegistry_ = new MUCRegistry();
@@ -52,9 +54,10 @@ class RosterControllerTest : public CppUnit::TestFixture
 			router_ = new IQRouter(channel_);
 			stanzaChannel_ = new DummyStanzaChannel();
 			presenceOracle_ = new PresenceOracle(stanzaChannel_);
+			presenceSender_ = new PresenceSender(stanzaChannel_);
 			eventController_ = new EventController();
 			uiEventStream_ = new UIEventStream();
-			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, eventController_, uiEventStream_, router_);
+			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, presenceSender_, eventController_, uiEventStream_, router_);
 			mainWindow_ = mainWindowFactory_->last;
 		};
 
@@ -67,6 +70,7 @@ class RosterControllerTest : public CppUnit::TestFixture
 			delete channel_;
 			delete router_;
 			delete eventController_;
+			delete presenceSender_;
 			delete presenceOracle_;
 			delete stanzaChannel_;
 			delete uiEventStream_;
@@ -119,7 +123,7 @@ class RosterControllerTest : public CppUnit::TestFixture
 
 	private:
 		JID jid_;
-		boost::shared_ptr<XMPPRoster> xmppRoster_;
+		XMPPRoster* xmppRoster_;
 		MUCRegistry* mucRegistry_;
 		AvatarManager* avatarManager_;
 		MockMainWindowFactory* mainWindowFactory_;
@@ -129,10 +133,10 @@ class RosterControllerTest : public CppUnit::TestFixture
 		DummyStanzaChannel* stanzaChannel_;	
 		IQRouter* router_;
 		PresenceOracle* presenceOracle_;
+		PresenceSender* presenceSender_;
 		EventController* eventController_;
 		UIEventStream* uiEventStream_;
 		MockMainWindow* mainWindow_;
 };
-#undef children
 
 CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest);
diff --git a/Swift/Controllers/UnitTest/XMPPRosterControllerTest.cpp b/Swift/Controllers/UnitTest/XMPPRosterControllerTest.cpp
index 2bfd8ce..6787528 100644
--- a/Swift/Controllers/UnitTest/XMPPRosterControllerTest.cpp
+++ b/Swift/Controllers/UnitTest/XMPPRosterControllerTest.cpp
@@ -31,7 +31,7 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture
 		void setUp() {
 			channel_ = new DummyIQChannel();
 			router_ = new IQRouter(channel_);
-			xmppRoster_ = boost::shared_ptr<XMPPRoster>(new XMPPRoster());
+			xmppRoster_ = new XMPPRoster();
 		}
 
 		void tearDown() {
@@ -78,7 +78,7 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture
 	private:
 		DummyIQChannel* channel_;
 		IQRouter* router_;
-		boost::shared_ptr<XMPPRoster> xmppRoster_;
+		XMPPRoster* xmppRoster_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(XMPPRosterControllerTest);
diff --git a/Swift/Controllers/XMPPRosterController.cpp b/Swift/Controllers/XMPPRosterController.cpp
index c107315..c3144b7 100644
--- a/Swift/Controllers/XMPPRosterController.cpp
+++ b/Swift/Controllers/XMPPRosterController.cpp
@@ -23,7 +23,7 @@ namespace Swift {
 /**
  * The controller does not gain ownership of these parameters.
  */
-XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, boost::shared_ptr<XMPPRoster> xmppRoster) : iqRouter_(iqRouter), rosterPushResponder_(iqRouter), xmppRoster_(xmppRoster) {
+XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, XMPPRoster* xmppRoster) : iqRouter_(iqRouter), rosterPushResponder_(iqRouter), xmppRoster_(xmppRoster) {
 	rosterPushResponder_.onRosterReceived.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1));
 }
 
diff --git a/Swift/Controllers/XMPPRosterController.h b/Swift/Controllers/XMPPRosterController.h
index 9306051..14159c7 100644
--- a/Swift/Controllers/XMPPRosterController.h
+++ b/Swift/Controllers/XMPPRosterController.h
@@ -21,18 +21,16 @@ namespace Swift {
 
 	class XMPPRosterController {
 		public:
-			XMPPRosterController(IQRouter *iqRouter, boost::shared_ptr<XMPPRoster> xmppRoster);
+			XMPPRosterController(IQRouter *iqRouter, XMPPRoster* xmppRoster);
 
 			void requestRoster();
 
-			boost::shared_ptr<XMPPRoster> getXMPPRoster() {return xmppRoster_;};
-
 			void handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload);
 
 		private:
 			IQRouter* iqRouter_;
 			RosterPushResponder rosterPushResponder_;
-			boost::shared_ptr<XMPPRoster> xmppRoster_;
+			XMPPRoster* xmppRoster_;
 	};
 }
 
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index bf9e5cf..634207c 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -138,6 +138,8 @@ if env["PLATFORM"] == "darwin" :
   frameworks = []
   if env["HAVE_SPARKLE"] :
     frameworks.append(env["SPARKLE_FRAMEWORK"])
+  if env["HAVE_GROWL"] :
+    frameworks.append(env["GROWL_FRAMEWORK"])
   app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = ["../resources/MacOSX/Swift.icns"] + commonResources, frameworks = frameworks)
   if env["DIST"] :
     myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], [
diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h
index 74e58f7..d40c3c0 100644
--- a/Swiften/Avatars/AvatarManager.h
+++ b/Swiften/Avatars/AvatarManager.h
@@ -9,6 +9,7 @@
 #include <boost/filesystem.hpp>
 
 #include "Swiften/Base/boost_bsignals.h"
+#include "Swiften/Base/ByteArray.h"
 
 namespace Swift {
 	class JID;
@@ -17,6 +18,7 @@ namespace Swift {
 		public:
 			virtual ~AvatarManager();
 
+			virtual ByteArray getAvatar(const JID&) const = 0;
 			virtual boost::filesystem::path getAvatarPath(const JID&) const = 0;
 
 			boost::signal<void (const JID&)> onAvatarChanged;
diff --git a/Swiften/Avatars/AvatarManagerImpl.cpp b/Swiften/Avatars/AvatarManagerImpl.cpp
index 384994b..9813aed 100644
--- a/Swiften/Avatars/AvatarManagerImpl.cpp
+++ b/Swiften/Avatars/AvatarManagerImpl.cpp
@@ -11,6 +11,7 @@
 #include "Swiften/Avatars/VCardUpdateAvatarManager.h"
 #include "Swiften/Avatars/VCardAvatarManager.h"
 #include "Swiften/Avatars/AvatarStorage.h"
+#include "Swiften/Base/ByteArray.h"
 
 namespace Swift {
 
@@ -39,5 +40,12 @@ boost::filesystem::path AvatarManagerImpl::getAvatarPath(const JID& jid) const {
 	return boost::filesystem::path();
 }
 
+ByteArray AvatarManagerImpl::getAvatar(const JID& jid) const {
+	String hash = combinedAvatarProvider.getAvatarHash(jid);
+	if (!hash.isEmpty()) {
+		return avatarStorage->getAvatar(hash);
+	}
+	return ByteArray();
+}
 
 }
diff --git a/Swiften/Avatars/AvatarManagerImpl.h b/Swiften/Avatars/AvatarManagerImpl.h
index f533160..a28d490 100644
--- a/Swiften/Avatars/AvatarManagerImpl.h
+++ b/Swiften/Avatars/AvatarManagerImpl.h
@@ -33,6 +33,7 @@ namespace Swift {
 			virtual ~AvatarManagerImpl();
 
 			virtual boost::filesystem::path getAvatarPath(const JID&) const;
+			virtual ByteArray getAvatar(const JID&) const;
 
 		private:
 			CombinedAvatarProvider combinedAvatarProvider;
diff --git a/Swiften/Avatars/DummyAvatarManager.h b/Swiften/Avatars/DummyAvatarManager.h
new file mode 100644
index 0000000..db63b05
--- /dev/null
+++ b/Swiften/Avatars/DummyAvatarManager.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/Avatars/AvatarManager.h"
+
+namespace Swift {
+	class DummyAvatarManager : public AvatarManager {
+		public:
+			virtual boost::filesystem::path getAvatarPath(const JID&) const {
+				return boost::filesystem::path();
+			}
+
+			virtual ByteArray getAvatar(const JID& jid) const {
+				std::map<JID, ByteArray>::const_iterator i = avatars.find(jid);
+				if (i != avatars.end()) {
+					return i->second;
+				}
+				else {
+					return ByteArray();
+				}
+			}
+
+			std::map<JID, ByteArray> avatars;
+	};
+}
diff --git a/Swiften/Avatars/NullAvatarManager.h b/Swiften/Avatars/NullAvatarManager.h
index 7f3c646..e96ed7a 100644
--- a/Swiften/Avatars/NullAvatarManager.h
+++ b/Swiften/Avatars/NullAvatarManager.h
@@ -14,5 +14,9 @@ namespace Swift {
 			virtual boost::filesystem::path getAvatarPath(const JID&) const {
 				return boost::filesystem::path();
 			}
+
+			virtual ByteArray getAvatar(const JID&) const {
+				return ByteArray();
+			}
 	};
 }
diff --git a/Swiften/Chat/ChatStateTracker.cpp b/Swiften/Chat/ChatStateTracker.cpp
index b8d76da..985f04a 100644
--- a/Swiften/Chat/ChatStateTracker.cpp
+++ b/Swiften/Chat/ChatStateTracker.cpp
@@ -18,7 +18,7 @@ void ChatStateTracker::handleMessageReceived(boost::shared_ptr<Message> message)
 	}
 }
 
-void ChatStateTracker::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence>) {
+void ChatStateTracker::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
 	if (newPresence->getType() == Presence::Unavailable) {
 		onChatStateChange(ChatState::Gone);
 	}
diff --git a/Swiften/Chat/ChatStateTracker.h b/Swiften/Chat/ChatStateTracker.h
index c8e8cb5..343d828 100644
--- a/Swiften/Chat/ChatStateTracker.h
+++ b/Swiften/Chat/ChatStateTracker.h
@@ -18,7 +18,7 @@ namespace Swift {
 		public:
 			ChatStateTracker();
 			void handleMessageReceived(boost::shared_ptr<Message> message);
-			void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> oldPresence);
+			void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
 			boost::signal<void (ChatState::ChatStateType)> onChatStateChange;
 		private:
 			void changeState(ChatState::ChatStateType state);
diff --git a/Swiften/Network/DummyTimerFactory.cpp b/Swiften/Network/DummyTimerFactory.cpp
index 466dd38..105b103 100644
--- a/Swiften/Network/DummyTimerFactory.cpp
+++ b/Swiften/Network/DummyTimerFactory.cpp
@@ -15,19 +15,26 @@ namespace Swift {
 
 class DummyTimerFactory::DummyTimer : public Timer {
 	public:
-		DummyTimer(int timeout) : timeout(timeout), isRunning(false) {
+		DummyTimer(int timeout, DummyTimerFactory* factory) : timeout(timeout), factory(factory), isRunning(false), startTime(~0) {
 		}
 
 		virtual void start() {
 			isRunning = true;
+			startTime = factory->currentTime;
 		}
 
 		virtual void stop() {
 			isRunning = false;
 		}
+
+		int getAlarmTime() const {
+			return startTime + timeout;
+		}
 	
 		int timeout;
+		DummyTimerFactory* factory;
 		bool isRunning;
+		int startTime;
 };
 
 
@@ -35,31 +42,18 @@ DummyTimerFactory::DummyTimerFactory() : currentTime(0) {
 }
 
 boost::shared_ptr<Timer> DummyTimerFactory::createTimer(int milliseconds) {
-	boost::shared_ptr<DummyTimer> timer(new DummyTimer(milliseconds));
+	boost::shared_ptr<DummyTimer> timer(new DummyTimer(milliseconds, this));
 	timers.push_back(timer);
 	return timer;
 }
 
-static bool hasZeroTimeout(boost::shared_ptr<DummyTimerFactory::DummyTimer> timer) {
-	return timer->timeout == 0;
-}
-
 void DummyTimerFactory::setTime(int time) {
 	assert(time > currentTime);
-	int increment = time - currentTime;
-	std::vector< boost::shared_ptr<DummyTimer> > notifyTimers(timers.begin(), timers.end());
-	foreach(boost::shared_ptr<DummyTimer> timer, notifyTimers) {
-		if (increment >= timer->timeout) {
-			if (timer->isRunning) {
-				timer->onTick();
-			}
-			timer->timeout = 0;
-		}
-		else {
-			timer->timeout -= increment;
+	foreach(boost::shared_ptr<DummyTimer> timer, timers) {
+		if (timer->getAlarmTime() > currentTime && timer->getAlarmTime() <= time && timer->isRunning) {
+			timer->onTick();
 		}
 	}
-	timers.erase(std::remove_if(timers.begin(), timers.end(), hasZeroTimeout), timers.end());
 	currentTime = time;
 }
 
diff --git a/Swiften/Presence/PresenceOracle.cpp b/Swiften/Presence/PresenceOracle.cpp
index 44934e6..758ae7c 100644
--- a/Swiften/Presence/PresenceOracle.cpp
+++ b/Swiften/Presence/PresenceOracle.cpp
@@ -7,37 +7,26 @@
 #include "PresenceOracle.h"
 
 #include <boost/bind.hpp>
+
 #include "Swiften/Client/StanzaChannel.h"
-namespace Swift {
 
-typedef std::pair<JID, std::map<JID, boost::shared_ptr<Presence> > > JIDMapPair;
-typedef std::pair<JID, boost::shared_ptr<Presence> > JIDPresencePair;
+namespace Swift {
 
 PresenceOracle::PresenceOracle(StanzaChannel* stanzaChannel) {
 	stanzaChannel_ = stanzaChannel;
 	stanzaChannel_->onPresenceReceived.connect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1));
+	stanzaChannel_->onAvailableChanged.connect(boost::bind(&PresenceOracle::handleStanzaChannelAvailableChanged, this, _1));
 }
 
-void PresenceOracle::cancelSubscription(const JID& jid) {
-	boost::shared_ptr<Presence> stanza(new Presence());
-	stanza->setType(Presence::Unsubscribed);
-	stanza->setTo(jid);
-	stanzaChannel_->sendPresence(stanza);
+PresenceOracle::~PresenceOracle() {
+	stanzaChannel_->onPresenceReceived.disconnect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1));
+	stanzaChannel_->onAvailableChanged.disconnect(boost::bind(&PresenceOracle::handleStanzaChannelAvailableChanged, this, _1));
 }
 
-void PresenceOracle::confirmSubscription(const JID& jid) {
-	boost::shared_ptr<Presence> stanza(new Presence());
-	stanza->setType(Presence::Subscribed);
-	stanza->setTo(jid);
-	stanzaChannel_->sendPresence(stanza);
-}
-
-
-void PresenceOracle::requestSubscription(const JID& jid) {
-	boost::shared_ptr<Presence> stanza(new Presence());
-	stanza->setType(Presence::Subscribe);
-	stanza->setTo(jid);
-	stanzaChannel_->sendPresence(stanza);
+void PresenceOracle::handleStanzaChannelAvailableChanged(bool available) {
+	if (available) {
+		entries_.clear();
+	}
 }
 
 
@@ -46,19 +35,29 @@ void PresenceOracle::handleIncomingPresence(boost::shared_ptr<Presence> presence
 
 	if (presence->getType() == Presence::Subscribe) {
 		onPresenceSubscriptionRequest(bareJID, presence->getStatus());
-	} else {
+	} 
+	else {
 		std::map<JID, boost::shared_ptr<Presence> > jidMap = entries_[bareJID];
-		boost::shared_ptr<Presence> last;
-		foreach(JIDPresencePair pair, jidMap) {
-			if (pair.first == presence->getFrom()) {
-				last = pair.second;
-				break;
-			}
-		}
 		jidMap[presence->getFrom()] = presence;
 		entries_[bareJID] = jidMap;
-		onPresenceChange(presence, last);
+		onPresenceChange(presence);
 	}
 }
 
+Presence::ref PresenceOracle::getLastPresence(const JID& jid) const {
+	PresencesMap::const_iterator i = entries_.find(jid.toBare());
+	if (i == entries_.end()) {
+		return Presence::ref();
+	}
+	PresenceMap presenceMap = i->second;
+	PresenceMap::const_iterator j = presenceMap.find(jid);
+	if (j != presenceMap.end()) {
+		return j->second;
+	}
+	else {
+		return Presence::ref();
+	}
+}
+
+
 }
diff --git a/Swiften/Presence/PresenceOracle.h b/Swiften/Presence/PresenceOracle.h
index 9f64000..e5f0372 100644
--- a/Swiften/Presence/PresenceOracle.h
+++ b/Swiften/Presence/PresenceOracle.h
@@ -6,31 +6,35 @@
 
 #pragma once
 
+#include <map>
+
 #include "Swiften/Base/String.h"
 #include "Swiften/Elements/Presence.h"
 
-#include <map>
 #include "Swiften/Base/boost_bsignals.h"
 
 namespace Swift {
 class StanzaChannel;
-
-class PresenceOracle {
-	public:
-		PresenceOracle(StanzaChannel* stanzaChannel);
-		~PresenceOracle() {};
-
-		void cancelSubscription(const JID& jid);
-		void confirmSubscription(const JID& jid);
-		void requestSubscription(const JID& jid);
-
-		boost::signal<void (boost::shared_ptr<Presence>, boost::shared_ptr<Presence>)> onPresenceChange;
-		boost::signal<void (const JID&, const String&)> onPresenceSubscriptionRequest;
-
-	private:
-		void handleIncomingPresence(boost::shared_ptr<Presence> presence);
-		std::map<JID, std::map<JID, boost::shared_ptr<Presence> > > entries_;
-		StanzaChannel* stanzaChannel_;
-};
+	class PresenceOracle {
+		public:
+			PresenceOracle(StanzaChannel* stanzaChannel);
+			~PresenceOracle();
+
+			Presence::ref getLastPresence(const JID&) const;
+
+		public:
+			boost::signal<void (boost::shared_ptr<Presence>)> onPresenceChange;
+			boost::signal<void (const JID&, const String&)> onPresenceSubscriptionRequest;
+
+		private:
+			void handleIncomingPresence(boost::shared_ptr<Presence> presence);
+			void handleStanzaChannelAvailableChanged(bool);
+
+		private:
+			typedef std::map<JID, Presence::ref> PresenceMap;
+			typedef std::map<JID, PresenceMap> PresencesMap;
+			PresencesMap entries_;
+			StanzaChannel* stanzaChannel_;
+	};
 }
 
diff --git a/Swiften/Presence/PresenceSender.cpp b/Swiften/Presence/PresenceSender.cpp
index e4562a9..082c841 100644
--- a/Swiften/Presence/PresenceSender.cpp
+++ b/Swiften/Presence/PresenceSender.cpp
@@ -52,4 +52,26 @@ void PresenceSender::removeDirectedPresenceReceiver(const JID& jid) {
 	}
 }
 
+void PresenceSender::cancelSubscription(const JID& jid) {
+	boost::shared_ptr<Presence> stanza(new Presence());
+	stanza->setType(Presence::Unsubscribed);
+	stanza->setTo(jid);
+	channel->sendPresence(stanza);
+}
+
+void PresenceSender::confirmSubscription(const JID& jid) {
+	boost::shared_ptr<Presence> stanza(new Presence());
+	stanza->setType(Presence::Subscribed);
+	stanza->setTo(jid);
+	channel->sendPresence(stanza);
+}
+
+
+void PresenceSender::requestSubscription(const JID& jid) {
+	boost::shared_ptr<Presence> stanza(new Presence());
+	stanza->setType(Presence::Subscribe);
+	stanza->setTo(jid);
+	channel->sendPresence(stanza);
+}
+
 }
diff --git a/Swiften/Presence/PresenceSender.h b/Swiften/Presence/PresenceSender.h
index da4bc70..87be753 100644
--- a/Swiften/Presence/PresenceSender.h
+++ b/Swiften/Presence/PresenceSender.h
@@ -22,6 +22,10 @@ namespace Swift {
 
 			void sendPresence(boost::shared_ptr<Presence>);
 
+			void cancelSubscription(const JID& jid);
+			void confirmSubscription(const JID& jid);
+			void requestSubscription(const JID& jid);
+
 		private:
 			boost::shared_ptr<Presence> lastSentUndirectedPresence;
 			StanzaChannel* channel;
diff --git a/Swiften/Presence/UnitTest/PresenceOracleTest.cpp b/Swiften/Presence/UnitTest/PresenceOracleTest.cpp
index 691beb7..e96d8a4 100644
--- a/Swiften/Presence/UnitTest/PresenceOracleTest.cpp
+++ b/Swiften/Presence/UnitTest/PresenceOracleTest.cpp
@@ -8,58 +8,29 @@
 #include <cppunit/extensions/TestFactoryRegistry.h>
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
-#include <boost/optional.hpp>
 
 #include "Swiften/Presence/PresenceOracle.h"
 #include "Swiften/Client/DummyStanzaChannel.h"
 
 using namespace Swift;
 
-class PresencePointerPair {
-	public:
-		boost::shared_ptr<Presence> one;
-		boost::shared_ptr<Presence> two;
-};
-
-class SubscriptionRequestInfo {
-	public:
-		boost::optional<JID> jid;
-		boost::optional<String> reason;
-};
-
-class PresenceOracleTest : public CppUnit::TestFixture
-{
+class PresenceOracleTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(PresenceOracleTest);
-		CPPUNIT_TEST(testFirstPresence);
-		CPPUNIT_TEST(testSecondPresence);
+		CPPUNIT_TEST(testReceivePresence);
+		CPPUNIT_TEST(testReceivePresenceFromDifferentResources);
 		CPPUNIT_TEST(testSubscriptionRequest);
+		CPPUNIT_TEST(testReconnectResetsPresences);
 		CPPUNIT_TEST_SUITE_END();
 
-	private:
-		PresenceOracle* oracle_;
-		DummyStanzaChannel* stanzaChannel_;
-
 	public:
-
-		void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> lastPresence, PresencePointerPair* out) {
-			CPPUNIT_ASSERT(out->one.get() == NULL);
-			CPPUNIT_ASSERT(out->two.get() == NULL);
-			out->one = newPresence;
-			out->two = lastPresence;
-			CPPUNIT_ASSERT(newPresence.get());
-			CPPUNIT_ASSERT_EQUAL(newPresence, out->one);
-		}
-		
-		void handlePresenceSubscriptionRequest(const JID& jid, const String& reason, SubscriptionRequestInfo* info) {
-			CPPUNIT_ASSERT(!info->jid);
-			CPPUNIT_ASSERT(!info->reason);
-			info->jid = jid;
-			info->reason = reason;
-		}
-
 		void setUp() {
 			stanzaChannel_ = new DummyStanzaChannel();
 			oracle_ = new PresenceOracle(stanzaChannel_);
+			oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1));
+			oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2));
+			user1 = JID("user1@foo.com/Foo");
+			user1alt = JID("user1@foo.com/Bar");
+			user2 = JID("user2@bar.com/Bar");
 		}
 
 		void tearDown() {
@@ -67,49 +38,27 @@ class PresenceOracleTest : public CppUnit::TestFixture
 			delete stanzaChannel_;
 		}
 
-		void testFirstPresence() {
-			PresencePointerPair out;
-			oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1, _2, &out));
-
-			SubscriptionRequestInfo info;
-			oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info));
-
-			boost::shared_ptr<Presence> sentPresence(new Presence("blarb"));
+		void testReceivePresence() {
+			boost::shared_ptr<Presence> sentPresence(createPresence(user1));
 			stanzaChannel_->onPresenceReceived(sentPresence);
 
-			CPPUNIT_ASSERT(!info.jid);
-			CPPUNIT_ASSERT(!info.reason);
-			CPPUNIT_ASSERT(out.two.get() == NULL);
-			CPPUNIT_ASSERT_EQUAL(sentPresence, out.one);
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(subscriptionRequests.size()));
+			CPPUNIT_ASSERT_EQUAL(sentPresence, changes[0]);
+			CPPUNIT_ASSERT_EQUAL(sentPresence, oracle_->getLastPresence(user1));
 		}
-		
-		void testSecondPresence() {
-			PresencePointerPair out;
-			oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1, _2, &out));
 
-			boost::shared_ptr<Presence> sentPresence1(new Presence("blarb"));
+		void testReceivePresenceFromDifferentResources() {
+			boost::shared_ptr<Presence> sentPresence1(createPresence(user1));
+			boost::shared_ptr<Presence> sentPresence2(createPresence(user1alt));
 			stanzaChannel_->onPresenceReceived(sentPresence1);
-			CPPUNIT_ASSERT_EQUAL(sentPresence1, out.one);
-			out.one = boost::shared_ptr<Presence>();
-			
-			SubscriptionRequestInfo info;
-			oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info));
-
-			boost::shared_ptr<Presence> sentPresence2(new Presence("test2"));
 			stanzaChannel_->onPresenceReceived(sentPresence2);
 
-			CPPUNIT_ASSERT(!info.jid);
-			CPPUNIT_ASSERT(!info.reason);
-			CPPUNIT_ASSERT_EQUAL(sentPresence1, out.two);
-			CPPUNIT_ASSERT_EQUAL(sentPresence2, out.one);
+			CPPUNIT_ASSERT_EQUAL(sentPresence1, oracle_->getLastPresence(user1));
+			CPPUNIT_ASSERT_EQUAL(sentPresence2, oracle_->getLastPresence(user1alt));
 		}
-
+		
 		void testSubscriptionRequest() {
-			PresencePointerPair out;
-			oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1, _2, &out));
-			SubscriptionRequestInfo info;
-			oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info));
-
 			String reasonText = "Because I want to";
 			JID sentJID = JID("me@example.com");
 
@@ -119,14 +68,52 @@ class PresenceOracleTest : public CppUnit::TestFixture
 			sentPresence->setStatus(reasonText);
 			stanzaChannel_->onPresenceReceived(sentPresence);
 
-			CPPUNIT_ASSERT(info.jid);
-			CPPUNIT_ASSERT(info.reason);
-			CPPUNIT_ASSERT_EQUAL(sentJID, info.jid.get());
-			CPPUNIT_ASSERT_EQUAL(reasonText, info.reason.get());
+			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(subscriptionRequests.size()));
+			CPPUNIT_ASSERT_EQUAL(sentJID, subscriptionRequests[0].jid);
+			CPPUNIT_ASSERT_EQUAL(reasonText, subscriptionRequests[0].reason);
+		}
+
+		void testReconnectResetsPresences() {
+			boost::shared_ptr<Presence> sentPresence(createPresence(user1));
+			stanzaChannel_->onPresenceReceived(sentPresence);
+			stanzaChannel_->setAvailable(false);
+			stanzaChannel_->setAvailable(true);
 
-			CPPUNIT_ASSERT(!out.two);
-			CPPUNIT_ASSERT(!out.one);
+			CPPUNIT_ASSERT(!oracle_->getLastPresence(user1));
 		}
+	
+	private:
+		void handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
+			changes.push_back(newPresence);
+		}
+		
+		void handlePresenceSubscriptionRequest(const JID& jid, const String& reason) {
+			SubscriptionRequestInfo subscriptionRequest;
+			subscriptionRequest.jid = jid;
+			subscriptionRequest.reason = reason;
+			subscriptionRequests.push_back(subscriptionRequest);
+		}
+
+		boost::shared_ptr<Presence> createPresence(const JID& jid) {
+			boost::shared_ptr<Presence> sentPresence(new Presence("blarb"));
+			sentPresence->setFrom(jid);
+			return sentPresence;
+		}
+
+	private:
+		struct SubscriptionRequestInfo {
+			JID jid;
+			String reason;
+		};
+		PresenceOracle* oracle_;
+		DummyStanzaChannel* stanzaChannel_;
+		std::vector<Presence::ref> changes;
+		std::vector<SubscriptionRequestInfo> subscriptionRequests;
+		JID user1;
+		JID user1alt;
+		JID user2;
 };
+
 CPPUNIT_TEST_SUITE_REGISTRATION(PresenceOracleTest);
 
diff --git a/Swiften/Roster/XMPPRoster.cpp b/Swiften/Roster/XMPPRoster.cpp
index 28b04c6..56616c2 100644
--- a/Swiften/Roster/XMPPRoster.cpp
+++ b/Swiften/Roster/XMPPRoster.cpp
@@ -8,6 +8,9 @@
 
 namespace Swift {
 
+XMPPRoster::XMPPRoster() {
+}
+
 void XMPPRoster::addContact(const JID& jid, const String& name, const std::vector<String>& groups, RosterItemPayload::Subscription subscription) {
 	JID bareJID(jid.toBare());
 	bool exists = containsJID(bareJID);
@@ -43,8 +46,14 @@ bool XMPPRoster::containsJID(const JID& jid) {
 	return entries_.find(JID(jid.toBare())) != entries_.end();
 }
 
-const String& XMPPRoster::getNameForJID(const JID& jid) {
-	return entries_[JID(jid.toBare())].name;
+String XMPPRoster::getNameForJID(const JID& jid) const {
+	std::map<JID, XMPPRosterItem>::const_iterator i = entries_.find(jid.toBare());
+	if (i != entries_.end()) {
+		return i->second.name;
+	}
+	else {
+		return "";
+	}
 }
 
 const std::vector<String>& XMPPRoster::getGroupsForJID(const JID& jid) {
diff --git a/Swiften/Roster/XMPPRoster.h b/Swiften/Roster/XMPPRoster.h
index e449d28..abafdfe 100644
--- a/Swiften/Roster/XMPPRoster.h
+++ b/Swiften/Roster/XMPPRoster.h
@@ -4,8 +4,7 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_XMPPRoster_H
-#define SWIFTEN_XMPPRoster_H
+#pragma once
 
 #include "Swiften/Base/String.h"
 #include "Swiften/JID/JID.h"
@@ -16,37 +15,31 @@
 #include "Swiften/Base/boost_bsignals.h"
 
 namespace Swift {
-
-struct XMPPRosterItem {
-	JID jid;
-	String name;
-	std::vector<String> groups;
-	RosterItemPayload::Subscription subscription;
-};
-
-class XMPPRoster {
-	public:
-		XMPPRoster() {};
-		~XMPPRoster() {};
-
-		void addContact(const JID& jid, const String& name, const std::vector<String>& groups, const RosterItemPayload::Subscription subscription);
-		bool containsJID(const JID& jid); 
-		void removeContact(const JID& jid);
-		void clear();
-		RosterItemPayload::Subscription getSubscriptionStateForJID(const JID& jid);
-		const String& getNameForJID(const JID& jid);
-		const std::vector<String>& getGroupsForJID(const JID& jid);
-
-		boost::signal<void (const JID&)> onJIDAdded;
-		boost::signal<void (const JID&)> onJIDRemoved;
-		boost::signal<void (const JID&, const String&, const std::vector<String>&)> onJIDUpdated;
-		boost::signal<void ()> onRosterCleared;
-
-	private:
-		//std::map<JID, std::pair<String, std::vector<String> > > entries_;
-		std::map<JID, XMPPRosterItem> entries_;
-};
+	class XMPPRoster {
+		public:
+			XMPPRoster();
+
+			void addContact(const JID& jid, const String& name, const std::vector<String>& groups, const RosterItemPayload::Subscription subscription);
+			void removeContact(const JID& jid);
+			void clear();
+
+			bool containsJID(const JID& jid);
+			RosterItemPayload::Subscription getSubscriptionStateForJID(const JID& jid);
+			String getNameForJID(const JID& jid) const;
+			const std::vector<String>& getGroupsForJID(const JID& jid);
+
+			boost::signal<void (const JID&)> onJIDAdded;
+			boost::signal<void (const JID&)> onJIDRemoved;
+			boost::signal<void (const JID&, const String&, const std::vector<String>&)> onJIDUpdated;
+			boost::signal<void ()> onRosterCleared;
+
+		private:
+			struct XMPPRosterItem {
+				JID jid;
+				String name;
+				std::vector<String> groups;
+				RosterItemPayload::Subscription subscription;
+			};
+			std::map<JID, XMPPRosterItem> entries_;
+	};
 }
-
-#endif
-
-- 
cgit v0.10.2-6-g49f6