From 9d9fb66aefef85a1c5ad432391014d15011747d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sat, 23 Oct 2010 23:08:19 +0200
Subject: Added ClientDiscoManager.


diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index f3b9977..5556a00 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -48,9 +48,9 @@
 #include "Swiften/Elements/VCardUpdate.h"
 #include "Swift/Controllers/Settings/SettingsProvider.h"
 #include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/Disco/DiscoInfoResponder.h"
 #include "Swiften/Disco/CapsInfoGenerator.h"
 #include "Swiften/Disco/GetDiscoInfoRequest.h"
+#include "Swiften/Disco/ClientDiscoManager.h"
 #include "Swiften/VCards/GetVCardRequest.h"
 #include "Swiften/StringCodecs/SHA1.h"
 #include "Swiften/StringCodecs/Hexify.h"
@@ -99,7 +99,6 @@ MainController::MainController(
 	rosterController_ = NULL;
 	chatsManager_ = NULL;
 	eventWindowController_ = NULL;
-	discoResponder_ = NULL;
 	mucSearchController_ = NULL;
 	quitRequested_ = false;
 
@@ -173,11 +172,6 @@ void MainController::resetClient() {
 	resetPendingReconnects();
 	delete mucSearchController_;
 	mucSearchController_ = NULL;
-	if (discoResponder_) {
-		discoResponder_->stop();
-		delete discoResponder_;
-		discoResponder_ = NULL;
-	}
 	delete eventWindowController_;
 	eventWindowController_ = NULL;
 	delete chatsManager_;
@@ -247,12 +241,8 @@ void MainController::handleConnected() {
 		discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc"));
 		discoInfo.addFeature("urn:xmpp:sec-label:0");
 		discoInfo.addFeature(ChatState::getFeatureNamespace());
-		capsInfo_ = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator(CLIENT_NODE).generateCapsInfo(discoInfo)));
-
-		discoResponder_ = new DiscoInfoResponder(client_->getIQRouter());
-		discoResponder_->setDiscoInfo(discoInfo);
-		discoResponder_->setDiscoInfo(capsInfo_->getNode() + "#" + capsInfo_->getVersion(), discoInfo);
-		discoResponder_->start();
+		client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
+		client_->getDiscoManager()->setDiscoInfo(discoInfo);
 
 		mucSearchController_ = new MUCSearchController(jid_, uiEventStream_, mucSearchWindowFactory_, client_->getIQRouter());
 	}
@@ -317,7 +307,6 @@ void MainController::sendPresence(boost::shared_ptr<Presence> presence) {
 	if (!vCardPhotoHash_.isEmpty()) {
 		presence->updatePayload(boost::shared_ptr<VCardUpdate>(new VCardUpdate(vCardPhotoHash_)));
 	}
-	presence->updatePayload(capsInfo_);
 	client_->getPresenceSender()->sendPresence(presence);
 	if (presence->getType() == Presence::Unavailable) {
 		logout();
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index c36c136..c05d098 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -17,8 +17,8 @@
 #include "Swiften/Base/String.h"
 #include "Swiften/Client/ClientError.h"
 #include "Swiften/JID/JID.h"
-#include "Swiften/Elements/VCard.h"
 #include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Elements/VCard.h"
 #include "Swiften/Elements/ErrorPayload.h"
 #include "Swiften/Elements/Presence.h"
 #include "Swift/Controllers/Settings/SettingsProvider.h"
@@ -38,7 +38,6 @@ namespace Swift {
 	class MainWindowFactory;
 	class MainWindow;
 	class RosterController;
-	class DiscoInfoResponder;
 	class LoginWindow;
 	class EventLoop;
 	class LoginWindowFactory;
@@ -132,11 +131,9 @@ namespace Swift {
 			EventController* eventController_;
 			EventWindowController* eventWindowController_;
 			LoginWindow* loginWindow_;
-			DiscoInfoResponder* discoResponder_;
 			UIEventStream* uiEventStream_;
 			XMLConsoleController* xmlConsoleController_;
 			ChatsManager* chatsManager_;
-			boost::shared_ptr<CapsInfo> capsInfo_;
 			JID jid_;
 			JID boundJID_;
 			SystemTrayController* systemTrayController_;
diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp
index 7dbcb70..fd01e25 100644
--- a/Swiften/Client/Client.cpp
+++ b/Swiften/Client/Client.cpp
@@ -20,6 +20,7 @@
 #include "Swiften/Avatars/AvatarManagerImpl.h"
 #include "Swiften/Disco/CapsManager.h"
 #include "Swiften/Disco/EntityCapsManager.h"
+#include "Swiften/Disco/ClientDiscoManager.h"
 #include "Swiften/Client/NickResolver.h"
 #include "Swiften/Presence/SubscriptionManager.h"
 
@@ -41,6 +42,7 @@ Client::Client(EventLoop* eventLoop, const JID& jid, const String& password, Sto
 
 	stanzaChannelPresenceSender = new StanzaChannelPresenceSender(getStanzaChannel());
 	directedPresenceSender = new DirectedPresenceSender(stanzaChannelPresenceSender);
+	discoManager = new ClientDiscoManager(getIQRouter(), directedPresenceSender);
 
 	mucRegistry = new MUCRegistry();
 	mucManager = new MUCManager(getStanzaChannel(), getIQRouter(), directedPresenceSender, mucRegistry);
@@ -64,6 +66,7 @@ Client::~Client() {
 	delete mucManager;
 	delete mucRegistry;
 
+	delete discoManager;
 	delete directedPresenceSender;
 	delete stanzaChannelPresenceSender;
 
@@ -106,7 +109,7 @@ Storages* Client::getStorages() const {
 }
 
 PresenceSender* Client::getPresenceSender() const {
-	return directedPresenceSender;
+	return discoManager->getPresenceSender();
 }
 
 }
diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h
index adfd549..a17fe24 100644
--- a/Swiften/Client/Client.h
+++ b/Swiften/Client/Client.h
@@ -27,6 +27,7 @@ namespace Swift {
 	class EntityCapsManager;
 	class NickResolver;
 	class SubscriptionManager;
+	class ClientDiscoManager;
 
 	/**
 	 * Provides the core functionality for writing XMPP client software.
@@ -122,6 +123,10 @@ namespace Swift {
 				return subscriptionManager;
 			}
 
+			ClientDiscoManager* getDiscoManager() const {
+				return discoManager;
+			}
+
 		public:
 			/**
 			 * This signal is emitted when a JID changes presence.
@@ -151,5 +156,6 @@ namespace Swift {
 			NickResolver* nickResolver;
 			SubscriptionManager* subscriptionManager;
 			MUCManager* mucManager;
+			ClientDiscoManager* discoManager;
 	};
 }
diff --git a/Swiften/Client/DummyStanzaChannel.h b/Swiften/Client/DummyStanzaChannel.h
index b8ae545..d9fb138 100644
--- a/Swiften/Client/DummyStanzaChannel.h
+++ b/Swiften/Client/DummyStanzaChannel.h
@@ -56,6 +56,10 @@ namespace Swift {
 				return iqStanza && iqStanza->getType() == type && iqStanza->getTo() == jid && iqStanza->getPayload<T>();
 			}
 
+			template<typename T> boost::shared_ptr<T> getStanzaAtIndex(size_t index) {
+				return boost::dynamic_pointer_cast<T>(sentStanzas[index]);
+			}
+
 			std::vector<boost::shared_ptr<Stanza> > sentStanzas;
 			bool available_;
 	};
diff --git a/Swiften/Disco/ClientDiscoManager.cpp b/Swiften/Disco/ClientDiscoManager.cpp
new file mode 100644
index 0000000..6753df2
--- /dev/null
+++ b/Swiften/Disco/ClientDiscoManager.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Disco/ClientDiscoManager.h"
+
+#include "Swiften/Disco/DiscoInfoResponder.h"
+#include "Swiften/Disco/CapsInfoGenerator.h"
+#include "Swiften/Presence/PayloadAddingPresenceSender.h"
+
+namespace Swift {
+
+ClientDiscoManager::ClientDiscoManager(IQRouter* iqRouter, PresenceSender* presenceSender) {
+	discoInfoResponder = new DiscoInfoResponder(iqRouter);
+	discoInfoResponder->start();
+	this->presenceSender = new PayloadAddingPresenceSender(presenceSender);
+}
+
+ClientDiscoManager::~ClientDiscoManager() {
+	delete presenceSender;
+	discoInfoResponder->stop();
+	delete discoInfoResponder;
+}
+
+void ClientDiscoManager::setCapsNode(const String& node) {
+	capsNode = node;
+}
+
+void ClientDiscoManager::setDiscoInfo(const DiscoInfo& discoInfo) {
+	capsInfo = CapsInfo::ref(new CapsInfo(CapsInfoGenerator(capsNode).generateCapsInfo(discoInfo)));
+	discoInfoResponder->clearDiscoInfo();
+	discoInfoResponder->setDiscoInfo(discoInfo);
+	discoInfoResponder->setDiscoInfo(capsInfo->getNode() + "#" + capsInfo->getVersion(), discoInfo);
+	presenceSender->setPayload(capsInfo);
+}
+
+}
diff --git a/Swiften/Disco/ClientDiscoManager.h b/Swiften/Disco/ClientDiscoManager.h
new file mode 100644
index 0000000..b997374
--- /dev/null
+++ b/Swiften/Disco/ClientDiscoManager.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Elements/CapsInfo.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Presence/PayloadAddingPresenceSender.h"
+
+namespace Swift {
+	class IQRouter;
+	class DiscoInfoResponder;
+	class PayloadAddingPresenceSender;
+	class PresenceSender;
+
+	/**
+	 * Class responsible for managing outgoing disco information for a client.
+	 *
+	 * The manager will respond to disco#info requests, and add entity capabilities information
+	 * to outgoing presence.
+	 *
+	 * To use this class, call setCapsNode() once with the caps URI of the client. After this,
+	 * call setDiscoInfo() with the capabilities for the client. This can be
+	 * called whenever the capabilities change.
+	 */
+	class ClientDiscoManager {
+		public:
+			/**
+			 * Constructs the manager
+			 *
+			 * \param iqRouter the router on which requests will be answered
+			 * \param presenceSender the presence sender to which all outgoing presence
+			 *   (with caps information) will be sent.
+			 */
+			ClientDiscoManager(IQRouter* iqRouter, PresenceSender* presenceSender);
+			~ClientDiscoManager();
+
+			/**
+			 * Needs to be called before calling setDiscoInfo().
+			 */
+			void setCapsNode(const String& node);
+
+			/**
+			 * Sets the capabilities of the client.
+			 */
+			void setDiscoInfo(const DiscoInfo& info);
+
+			/**
+			 * Returns the presence sender through which all outgoing presence
+			 * should be sent.
+			 * The manager will add the necessary caps information, and forward it to
+			 * the presence sender passed at construction time.
+			 */
+			PresenceSender* getPresenceSender() const {
+				return presenceSender;
+			}
+
+		private:
+			PayloadAddingPresenceSender* presenceSender;
+			DiscoInfoResponder* discoInfoResponder;
+			String capsNode;
+			CapsInfo::ref capsInfo;
+	};
+}
diff --git a/Swiften/Disco/DiscoInfoResponder.cpp b/Swiften/Disco/DiscoInfoResponder.cpp
index 154eded..2e686c7 100644
--- a/Swiften/Disco/DiscoInfoResponder.cpp
+++ b/Swiften/Disco/DiscoInfoResponder.cpp
@@ -13,6 +13,11 @@ namespace Swift {
 DiscoInfoResponder::DiscoInfoResponder(IQRouter* router) : GetResponder<DiscoInfo>(router) {
 }
 
+void DiscoInfoResponder::clearDiscoInfo() {
+	info_ = DiscoInfo();
+	nodeInfo_.clear();
+}
+
 void DiscoInfoResponder::setDiscoInfo(const DiscoInfo& info) {
 	info_ = info;
 }
diff --git a/Swiften/Disco/DiscoInfoResponder.h b/Swiften/Disco/DiscoInfoResponder.h
index 4a7d271..0dc1172 100644
--- a/Swiften/Disco/DiscoInfoResponder.h
+++ b/Swiften/Disco/DiscoInfoResponder.h
@@ -4,8 +4,7 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_DiscoInfoResponder_H
-#define SWIFTEN_DiscoInfoResponder_H
+#pragma once
 
 #include <map>
 
@@ -19,6 +18,7 @@ namespace Swift {
 		public:
 			DiscoInfoResponder(IQRouter* router);
 
+			void clearDiscoInfo();
 			void setDiscoInfo(const DiscoInfo& info);
 			void setDiscoInfo(const String& node, const DiscoInfo& info);
 
@@ -30,5 +30,3 @@ namespace Swift {
 			std::map<String, DiscoInfo> nodeInfo_;
 	};
 }
-
-#endif
diff --git a/Swiften/Disco/SConscript b/Swiften/Disco/SConscript
index 3838d0e..a791946 100644
--- a/Swiften/Disco/SConscript
+++ b/Swiften/Disco/SConscript
@@ -7,6 +7,7 @@ objects = swiften_env.StaticObject([
 			"EntityCapsProvider.cpp",
 			"CapsStorage.cpp",
 			"CapsFileStorage.cpp",
+			"ClientDiscoManager.cpp",
 			"DiscoInfoResponder.cpp",
 		])
 swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/Elements/Body.h b/Swiften/Elements/Body.h
index e3610c8..8262e09 100644
--- a/Swiften/Elements/Body.h
+++ b/Swiften/Elements/Body.h
@@ -4,8 +4,7 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_Body_H
-#define SWIFTEN_Body_H
+#pragma once
 
 #include "Swiften/Elements/Payload.h"
 #include "Swiften/Base/String.h"
@@ -13,6 +12,8 @@
 namespace Swift {
 	class Body : public Payload {
 		public:
+			typedef boost::shared_ptr<Body> ref;
+
 			Body(const String& text = "") : text_(text) {
 			}
 
@@ -28,5 +29,3 @@ namespace Swift {
 			String text_;
 	};
 }
-
-#endif
diff --git a/Swiften/Elements/CapsInfo.h b/Swiften/Elements/CapsInfo.h
index 1968a29..dc3cc2e 100644
--- a/Swiften/Elements/CapsInfo.h
+++ b/Swiften/Elements/CapsInfo.h
@@ -6,13 +6,16 @@
 
 #pragma once
 
-#include "Swiften/Base/String.h"
+#include <boost/shared_ptr.hpp>
 
+#include "Swiften/Base/String.h"
 #include "Swiften/Elements/Payload.h"
 
 namespace Swift {
 	class CapsInfo : public Payload {
 		public:
+			typedef boost::shared_ptr<CapsInfo> ref;
+
 			CapsInfo(const String& node = "", const String& version = "", const String& hash = "sha-1") : node_(node), version_(version), hash_(hash) {}
 
 			bool operator==(const CapsInfo& o) const {
diff --git a/Swiften/Elements/Payload.h b/Swiften/Elements/Payload.h
index 9d7ed16..c87b899 100644
--- a/Swiften/Elements/Payload.h
+++ b/Swiften/Elements/Payload.h
@@ -4,14 +4,15 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_PAYLOAD_H
-#define SWIFTEN_PAYLOAD_H
+#pragma once
+
+#include <boost/shared_ptr.hpp>
 
 namespace Swift {
 	class Payload {
 		public:
+			typedef boost::shared_ptr<Payload> ref;
+
 			virtual ~Payload();
 	};
 }
-
-#endif
diff --git a/Swiften/Presence/PayloadAddingPresenceSender.cpp b/Swiften/Presence/PayloadAddingPresenceSender.cpp
new file mode 100644
index 0000000..c3d1638
--- /dev/null
+++ b/Swiften/Presence/PayloadAddingPresenceSender.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Presence/PayloadAddingPresenceSender.h"
+
+namespace Swift {
+
+PayloadAddingPresenceSender::PayloadAddingPresenceSender(PresenceSender* sender) : sender(sender) {
+}
+
+void PayloadAddingPresenceSender::sendPresence(Presence::ref presence) {
+	if (presence->isAvailable()) {
+		if (!presence->getTo().isValid()) {
+			lastSentPresence = presence;
+		}
+	}
+	else {
+		lastSentPresence.reset();
+	}
+	if (payload) {
+		Presence::ref sentPresence = Presence::create(presence);
+		sentPresence->updatePayload(payload);
+		sender->sendPresence(sentPresence);
+	}
+	else {
+		sender->sendPresence(presence);
+	}
+}
+
+bool PayloadAddingPresenceSender::isAvailable() const {
+	return sender->isAvailable();
+}
+
+void PayloadAddingPresenceSender::setPayload(Payload::ref payload) {
+	this->payload = payload;
+	if (lastSentPresence) {
+		sendPresence(lastSentPresence);
+	}
+}
+
+}
diff --git a/Swiften/Presence/PayloadAddingPresenceSender.h b/Swiften/Presence/PayloadAddingPresenceSender.h
new file mode 100644
index 0000000..5602ebe
--- /dev/null
+++ b/Swiften/Presence/PayloadAddingPresenceSender.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Presence/PresenceSender.h"
+#include "Swiften/Elements/Payload.h"
+
+namespace Swift {
+	class StanzaChannel;
+
+	/**
+	 * This presence sender adds payloads to outgoing presences.
+	 *
+	 *
+	 */
+	class PayloadAddingPresenceSender : public PresenceSender {
+		public:
+			PayloadAddingPresenceSender(PresenceSender*);
+
+			void sendPresence(Presence::ref);
+			bool isAvailable() const;
+
+			void setPayload(Payload::ref);
+
+		private:
+			Presence::ref lastSentPresence;
+			PresenceSender* sender;
+			Payload::ref payload;
+	};
+}
diff --git a/Swiften/Presence/SConscript b/Swiften/Presence/SConscript
new file mode 100644
index 0000000..6911d45
--- /dev/null
+++ b/Swiften/Presence/SConscript
@@ -0,0 +1,11 @@
+Import("swiften_env")
+
+objects = swiften_env.StaticObject([
+			"PresenceOracle.cpp",
+			"PresenceSender.cpp",
+			"DirectedPresenceSender.cpp",
+			"PayloadAddingPresenceSender.cpp",
+			"StanzaChannelPresenceSender.cpp",
+			"SubscriptionManager.cpp",
+		])
+swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/Presence/UnitTest/PayloadAddingPresenceSenderTest.cpp b/Swiften/Presence/UnitTest/PayloadAddingPresenceSenderTest.cpp
new file mode 100644
index 0000000..3a6487a
--- /dev/null
+++ b/Swiften/Presence/UnitTest/PayloadAddingPresenceSenderTest.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <vector>
+#include <boost/bind.hpp>
+
+#include "Swiften/Presence/PayloadAddingPresenceSender.h"
+#include "Swiften/Presence/StanzaChannelPresenceSender.h"
+#include "Swiften/Elements/Body.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+
+using namespace Swift;
+
+class PayloadAddingPresenceSenderTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(PayloadAddingPresenceSenderTest);
+		CPPUNIT_TEST(testSetPayloadAddsPayloadOnPresenceSend);
+		CPPUNIT_TEST(testSetNullPayloadDoesNotAddPayloadOnPresenceSend);
+		CPPUNIT_TEST(testSendPresenceDoesNotAlterOriginalPayload);
+		CPPUNIT_TEST(testSetPayloadAfterInitialPresenceResendsPresence);
+		CPPUNIT_TEST(testSetPayloadAfterUnavailablePresenceDoesNotResendPresence);
+		CPPUNIT_TEST(testSendDirectedPresenceIsNotResent);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			stanzaChannel = new DummyStanzaChannel();
+			presenceSender = new StanzaChannelPresenceSender(stanzaChannel);
+		}
+
+		void tearDown() {
+			delete presenceSender;
+			delete stanzaChannel;
+		}
+
+		void testSetPayloadAddsPayloadOnPresenceSend() {
+			std::auto_ptr<PayloadAddingPresenceSender> testling(createSender());
+
+			testling->setPayload(MyPayload::create("foo"));
+			testling->sendPresence(Presence::create("bar"));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("bar"), stanzaChannel->getStanzaAtIndex<Presence>(0)->getStatus());
+			CPPUNIT_ASSERT(stanzaChannel->getStanzaAtIndex<Presence>(0)->getPayload<MyPayload>());
+		}
+
+		void testSetNullPayloadDoesNotAddPayloadOnPresenceSend() {
+			std::auto_ptr<PayloadAddingPresenceSender> testling(createSender());
+
+			testling->setPayload(MyPayload::ref());
+			testling->sendPresence(Presence::create("bar"));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("bar"), stanzaChannel->getStanzaAtIndex<Presence>(0)->getStatus());
+			CPPUNIT_ASSERT(!stanzaChannel->getStanzaAtIndex<Presence>(0)->getPayload<MyPayload>());
+		}
+
+		void testSendPresenceDoesNotAlterOriginalPayload() {
+			std::auto_ptr<PayloadAddingPresenceSender> testling(createSender());
+
+			testling->setPayload(MyPayload::create("foo"));
+			Presence::ref presence(Presence::create("bar"));
+			testling->sendPresence(presence);
+
+			CPPUNIT_ASSERT(!presence->getPayload<MyPayload>());
+		}
+
+		void testSetPayloadAfterInitialPresenceResendsPresence() {
+			std::auto_ptr<PayloadAddingPresenceSender> testling(createSender());
+
+			testling->sendPresence(Presence::create("bar"));
+			testling->setPayload(MyPayload::create("foo"));
+
+			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("bar"), stanzaChannel->getStanzaAtIndex<Presence>(1)->getStatus());
+			CPPUNIT_ASSERT(stanzaChannel->getStanzaAtIndex<Presence>(1)->getPayload<MyPayload>());
+		}
+
+		void testSetPayloadAfterUnavailablePresenceDoesNotResendPresence() {
+			std::auto_ptr<PayloadAddingPresenceSender> testling(createSender());
+
+			Presence::ref presence = Presence::create("bar");
+			presence->setType(Presence::Unavailable);
+			testling->sendPresence(presence);
+
+			testling->setPayload(MyPayload::create("foo"));
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+		}
+
+		void testSendDirectedPresenceIsNotResent() {
+			std::auto_ptr<PayloadAddingPresenceSender> testling(createSender());
+
+			testling->sendPresence(Presence::create("bar"));
+			Presence::ref directedPresence = Presence::create("baz");
+			directedPresence->setTo(JID("foo@bar.com"));
+			testling->sendPresence(directedPresence);
+			testling->setPayload(MyPayload::create("foo"));
+
+			CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(stanzaChannel->sentStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("bar"), stanzaChannel->getStanzaAtIndex<Presence>(2)->getStatus());
+		}
+
+	private:
+		std::auto_ptr<PayloadAddingPresenceSender> createSender() {
+			std::auto_ptr<PayloadAddingPresenceSender> sender(new PayloadAddingPresenceSender(presenceSender));
+			return sender;
+		}
+
+		struct MyPayload : public Payload {
+				typedef boost::shared_ptr<MyPayload> ref;
+
+				MyPayload(const String& body) : body(body) {}
+
+				static ref create(const String& body) {
+					return ref(new MyPayload(body));
+				}
+
+				String body;
+		};
+
+	private:
+		DummyStanzaChannel* stanzaChannel;
+		StanzaChannelPresenceSender* presenceSender;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PayloadAddingPresenceSenderTest);
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 5790672..9744f45 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -51,11 +51,6 @@ if env["SCONS_STAGE"] == "build" :
 			"MUC/MUCOccupant.cpp",
 			"MUC/MUCRegistry.cpp",
 			"MUC/MUCBookmarkManager.cpp",
-			"Presence/PresenceOracle.cpp",
-			"Presence/PresenceSender.cpp",
-			"Presence/DirectedPresenceSender.cpp",
-			"Presence/StanzaChannelPresenceSender.cpp",
-			"Presence/SubscriptionManager.cpp",
 			"Queries/IQChannel.cpp",
 			"Queries/IQHandler.cpp",
 			"Queries/IQRouter.cpp",
@@ -139,6 +134,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Disco",
 			"VCards",
 			"Network",
+			"Presence",
 			"FileTransfer",
 			"History",
 			"StreamStack",
@@ -223,6 +219,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Parser/UnitTest/XMPPParserTest.cpp"),
 			File("Presence/UnitTest/PresenceOracleTest.cpp"),
 			File("Presence/UnitTest/DirectedPresenceSenderTest.cpp"),
+			File("Presence/UnitTest/PayloadAddingPresenceSenderTest.cpp"),
 			File("Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp"),
 			File("Disco/UnitTest/DiscoInfoResponderTest.cpp"),
 			File("Queries/UnitTest/IQRouterTest.cpp"),
-- 
cgit v0.10.2-6-g49f6