diff options
34 files changed, 519 insertions, 32 deletions
| diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp index 7918c46..c6ada7c 100644 --- a/Swiften/Client/Client.cpp +++ b/Swiften/Client/Client.cpp @@ -25,6 +25,7 @@  #include "Swiften/Presence/SubscriptionManager.h"  #include "Swiften/TLS/BlindCertificateTrustChecker.h"  #include <Swiften/Client/NickManagerImpl.h> +#include <Swiften/Client/ClientSession.h>  namespace Swift { @@ -35,7 +36,7 @@ Client::Client(const JID& jid, const std::string& password, NetworkFactories* ne  	softwareVersionResponder->start();  	roster = new XMPPRosterImpl(); -	rosterController = new XMPPRosterController(getIQRouter(), roster); +	rosterController = new XMPPRosterController(getIQRouter(), roster, storages->getRosterStorage());  	subscriptionManager = new SubscriptionManager(getStanzaChannel()); @@ -98,6 +99,11 @@ void Client::setSoftwareVersion(const std::string& name, const std::string& vers  }  void Client::requestRoster() { +	// FIXME: We should set this once when the session is finished, but there +	// is currently no callback for this +	if (getSession()) { +		rosterController->setUseVersioning(getSession()->getRosterVersioningSupported()); +	}  	rosterController->requestRoster();  } diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp index e1c1d8e..cadc9a4 100644 --- a/Swiften/Client/ClientSession.cpp +++ b/Swiften/Client/ClientSession.cpp @@ -54,6 +54,7 @@ ClientSession::ClientSession(  			needSessionStart(false),  			needResourceBind(false),  			needAcking(false), +			rosterVersioningSupported(false),  			authenticator(NULL),  			certificateTrustChecker(NULL) {  } @@ -223,6 +224,7 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {  		}  		else {  			// Start the session +			rosterVersioningSupported = streamFeatures->hasRosterVersioning();  			stream->setWhitespacePingEnabled(true);  			needSessionStart = streamFeatures->hasSession();  			needResourceBind = streamFeatures->hasResourceBind(); diff --git a/Swiften/Client/ClientSession.h b/Swiften/Client/ClientSession.h index 25ee694..4447f57 100644 --- a/Swiften/Client/ClientSession.h +++ b/Swiften/Client/ClientSession.h @@ -89,6 +89,10 @@ namespace Swift {  				return stanzaAckRequester_;  			} +			bool getRosterVersioningSupported() const { +				return rosterVersioningSupported; +			} +  			const JID& getLocalJID() const {  				return localJID;  			} @@ -153,6 +157,7 @@ namespace Swift {  			bool needSessionStart;  			bool needResourceBind;  			bool needAcking; +			bool rosterVersioningSupported;  			ClientAuthenticator* authenticator;  			boost::shared_ptr<StanzaAckRequester> stanzaAckRequester_;  			boost::shared_ptr<StanzaAckResponder> stanzaAckResponder_; diff --git a/Swiften/Client/CoreClient.h b/Swiften/Client/CoreClient.h index 060c699..ceb0b0a 100644 --- a/Swiften/Client/CoreClient.h +++ b/Swiften/Client/CoreClient.h @@ -196,6 +196,11 @@ namespace Swift {  			 */  			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaAcked; +		protected: +			boost::shared_ptr<ClientSession> getSession() const { +				return session_; +			} +  		private:  			void handleConnectorFinished(boost::shared_ptr<Connection>);  			void handleStanzaChannelAvailableChanged(bool available); diff --git a/Swiften/Client/FileStorages.cpp b/Swiften/Client/FileStorages.cpp index 3c76c46..e6a93fd 100644 --- a/Swiften/Client/FileStorages.cpp +++ b/Swiften/Client/FileStorages.cpp @@ -8,6 +8,7 @@  #include "Swiften/VCards/VCardFileStorage.h"  #include "Swiften/Avatars/AvatarFileStorage.h"  #include "Swiften/Disco/CapsFileStorage.h" +#include "Swiften/Roster/RosterFileStorage.h"  namespace Swift { @@ -16,9 +17,11 @@ FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& ji  	vcardStorage = new VCardFileStorage(baseDir / profile / "vcards");  	capsStorage = new CapsFileStorage(baseDir / "caps");  	avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars"); +	rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml");  }  FileStorages::~FileStorages() { +	delete rosterStorage;  	delete avatarStorage;  	delete capsStorage;  	delete vcardStorage; @@ -36,4 +39,8 @@ AvatarStorage* FileStorages::getAvatarStorage() const {  	return avatarStorage;  } +RosterStorage* FileStorages::getRosterStorage() const { +	return rosterStorage; +} +  } diff --git a/Swiften/Client/FileStorages.h b/Swiften/Client/FileStorages.h index fc05c86..28df314 100644 --- a/Swiften/Client/FileStorages.h +++ b/Swiften/Client/FileStorages.h @@ -14,6 +14,7 @@ namespace Swift {  	class VCardFileStorage;  	class AvatarFileStorage;  	class CapsFileStorage; +	class RosterFileStorage;  	class JID;  	/** @@ -41,10 +42,12 @@ namespace Swift {  			virtual VCardStorage* getVCardStorage() const;  			virtual AvatarStorage* getAvatarStorage() const;  			virtual CapsStorage* getCapsStorage() const; +			virtual RosterStorage* getRosterStorage() const;  		private:  			VCardFileStorage* vcardStorage;  			AvatarFileStorage* avatarStorage;  			CapsFileStorage* capsStorage; +			RosterFileStorage* rosterStorage;  	};  } diff --git a/Swiften/Client/MemoryStorages.cpp b/Swiften/Client/MemoryStorages.cpp index 5f6371b..6941add 100644 --- a/Swiften/Client/MemoryStorages.cpp +++ b/Swiften/Client/MemoryStorages.cpp @@ -8,6 +8,7 @@  #include "Swiften/VCards/VCardMemoryStorage.h"  #include "Swiften/Avatars/AvatarMemoryStorage.h"  #include "Swiften/Disco/CapsMemoryStorage.h" +#include "Swiften/Roster/RosterMemoryStorage.h"  namespace Swift { @@ -15,9 +16,11 @@ MemoryStorages::MemoryStorages() {  	vcardStorage = new VCardMemoryStorage();  	capsStorage = new CapsMemoryStorage();  	avatarStorage = new AvatarMemoryStorage(); +	rosterStorage = new RosterMemoryStorage();  }  MemoryStorages::~MemoryStorages() { +	delete rosterStorage;  	delete avatarStorage;  	delete capsStorage;  	delete vcardStorage; @@ -35,4 +38,9 @@ AvatarStorage* MemoryStorages::getAvatarStorage() const {  	return avatarStorage;  } +RosterStorage* MemoryStorages::getRosterStorage() const { +	return rosterStorage; +} + +  } diff --git a/Swiften/Client/MemoryStorages.h b/Swiften/Client/MemoryStorages.h index 67025cd..1e1a596 100644 --- a/Swiften/Client/MemoryStorages.h +++ b/Swiften/Client/MemoryStorages.h @@ -23,10 +23,12 @@ namespace Swift {  			virtual VCardStorage* getVCardStorage() const;  			virtual AvatarStorage* getAvatarStorage() const;  			virtual CapsStorage* getCapsStorage() const; +			virtual RosterStorage* getRosterStorage() const;  		private:  			VCardMemoryStorage* vcardStorage;  			AvatarStorage* avatarStorage;  			CapsStorage* capsStorage; +			RosterStorage* rosterStorage;  	};  } diff --git a/Swiften/Client/Storages.cpp b/Swiften/Client/Storages.cpp new file mode 100644 index 0000000..3c2dbc5 --- /dev/null +++ b/Swiften/Client/Storages.cpp @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Client/Storages.h> + +using namespace Swift; + +Storages::~Storages() { +} diff --git a/Swiften/Client/Storages.h b/Swiften/Client/Storages.h index e62f0a9..1c5bbe9 100644 --- a/Swiften/Client/Storages.h +++ b/Swiften/Client/Storages.h @@ -10,6 +10,7 @@ namespace Swift {  	class VCardStorage;  	class AvatarStorage;  	class CapsStorage; +	class RosterStorage;  	/**  	 * An interface to hold storage classes for different @@ -17,10 +18,11 @@ namespace Swift {  	 */  	class Storages {  		public: -			virtual ~Storages() {} +			virtual ~Storages();  			virtual VCardStorage* getVCardStorage() const = 0;  			virtual AvatarStorage* getAvatarStorage() const = 0;  			virtual CapsStorage* getCapsStorage() const = 0; +			virtual RosterStorage* getRosterStorage() const = 0;  	};  } diff --git a/Swiften/Elements/RosterItemPayload.h b/Swiften/Elements/RosterItemPayload.h index b8a1b10..915ae31 100644 --- a/Swiften/Elements/RosterItemPayload.h +++ b/Swiften/Elements/RosterItemPayload.h @@ -4,22 +4,20 @@   * See Documentation/Licenses/GPLv3.txt for more information.   */ -#ifndef SWIFTEN_RosterItemPayloadPayload_H -#define SWIFTEN_RosterItemPayloadPayload_H +#pragma once  #include <vector> - -#include "Swiften/JID/JID.h"  #include <string> +#include <Swiften/JID/JID.h> +  namespace Swift { -	class RosterItemPayload -	{ +	class RosterItemPayload {  		public:  			enum Subscription { None, To, From, Both, Remove };  			RosterItemPayload() : subscription_(None), ask_(false) {} -			RosterItemPayload(const JID& jid, const std::string& name, Subscription subscription) : jid_(jid), name_(name), subscription_(subscription), ask_(false) { } +			RosterItemPayload(const JID& jid, const std::string& name, Subscription subscription, const std::vector<std::string>& groups = std::vector<std::string>()) : jid_(jid), name_(name), subscription_(subscription), groups_(groups), ask_(false) { }  			void setJID(const JID& jid) { jid_ = jid; }  			const JID& getJID() const { return jid_; } @@ -51,5 +49,3 @@ namespace Swift {  			std::string unknownContent_;  	};  } - -#endif diff --git a/Swiften/Elements/RosterPayload.h b/Swiften/Elements/RosterPayload.h index b46b384..3102f0e 100644 --- a/Swiften/Elements/RosterPayload.h +++ b/Swiften/Elements/RosterPayload.h @@ -33,7 +33,16 @@ namespace Swift {  				return items_;  			} +			const boost::optional<std::string>& getVersion() const { +				return version_; +			} + +			void setVersion(const std::string& version) { +				version_ = version; +			} +  		private:  			RosterItemPayloads items_; +			boost::optional<std::string> version_;  	};  } diff --git a/Swiften/Elements/StreamFeatures.h b/Swiften/Elements/StreamFeatures.h index 4bb21f0..5dc5a26 100644 --- a/Swiften/Elements/StreamFeatures.h +++ b/Swiften/Elements/StreamFeatures.h @@ -17,7 +17,7 @@ namespace Swift {  		public:  			typedef boost::shared_ptr<StreamFeatures> ref; -			StreamFeatures() : hasStartTLS_(false), hasResourceBind_(false), hasSession_(false), hasStreamManagement_(false) {} +			StreamFeatures() : hasStartTLS_(false), hasResourceBind_(false), hasSession_(false), hasStreamManagement_(false), hasRosterVersioning_(false) {}  			void setHasStartTLS() {  				hasStartTLS_ = true; @@ -75,6 +75,14 @@ namespace Swift {  				hasStreamManagement_ = true;  			} +			bool hasRosterVersioning() const { +				return hasRosterVersioning_; +			} + +			void setHasRosterVersioning() { +				hasRosterVersioning_ = true; +			} +  		private:  			bool hasStartTLS_;  			std::vector<std::string> compressionMethods_; @@ -82,5 +90,6 @@ namespace Swift {  			bool hasResourceBind_;  			bool hasSession_;  			bool hasStreamManagement_; +			bool hasRosterVersioning_;  	};  } diff --git a/Swiften/Parser/PayloadParsers/RosterParser.cpp b/Swiften/Parser/PayloadParsers/RosterParser.cpp index ba19fbf..0da9f48 100644 --- a/Swiften/Parser/PayloadParsers/RosterParser.cpp +++ b/Swiften/Parser/PayloadParsers/RosterParser.cpp @@ -13,7 +13,13 @@ RosterParser::RosterParser() : level_(TopLevel), inItem_(false), unknownContentP  }  void RosterParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) { -	if (level_ == PayloadLevel) { +	if (level_ == TopLevel) { +		AttributeMap::const_iterator i = attributes.find("ver"); +		if (i != attributes.end()) { +			getPayloadInternal()->setVersion(i->second); +		} +	} +	else if (level_ == PayloadLevel) {  		if (element == "item") {  			inItem_ = true;  			currentItem_ = RosterItemPayload(); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp index 1bcea0e..3102b74 100644 --- a/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp +++ b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp @@ -17,6 +17,8 @@ class RosterParserTest : public CppUnit::TestFixture  		CPPUNIT_TEST_SUITE(RosterParserTest);  		CPPUNIT_TEST(testParse);  		CPPUNIT_TEST(testParse_ItemWithUnknownContent); +		CPPUNIT_TEST(testParse_WithVersion); +		CPPUNIT_TEST(testParse_WithEmptyVersion);  		CPPUNIT_TEST_SUITE_END();  	public: @@ -32,6 +34,8 @@ class RosterParserTest : public CppUnit::TestFixture  				"</query>"));  			RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get()); + +			CPPUNIT_ASSERT(!payload->getVersion());  			const RosterPayload::RosterItemPayloads& items = payload->getItems();  			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size()); @@ -74,6 +78,24 @@ class RosterParserTest : public CppUnit::TestFixture  				"<baz xmlns=\"jabber:iq:roster\"><fum xmlns=\"jabber:iq:roster\">foo</fum></baz>"  				), items[0].getUnknownContent());  		} + +		void testParse_WithVersion() { +			PayloadsParserTester parser; +			CPPUNIT_ASSERT(parser.parse("<query xmlns='jabber:iq:roster' ver='ver10'/>")); + +			RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get()); +			CPPUNIT_ASSERT(payload->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string("ver10"), *payload->getVersion()); +		} + +		void testParse_WithEmptyVersion() { +			PayloadsParserTester parser; +			CPPUNIT_ASSERT(parser.parse("<query xmlns='jabber:iq:roster' ver=''/>")); + +			RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get()); +			CPPUNIT_ASSERT(payload->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string(""), *payload->getVersion()); +		}  };  CPPUNIT_TEST_SUITE_REGISTRATION(RosterParserTest); diff --git a/Swiften/Parser/StreamFeaturesParser.cpp b/Swiften/Parser/StreamFeaturesParser.cpp index 377f215..1b3ad54 100644 --- a/Swiften/Parser/StreamFeaturesParser.cpp +++ b/Swiften/Parser/StreamFeaturesParser.cpp @@ -31,6 +31,9 @@ void StreamFeaturesParser::handleStartElement(const std::string& element, const  		else if (element == "compression" && ns == "http://jabber.org/features/compress") {  			inCompression_ = true;  		} +		else if (element == "ver" && ns == "urn:xmpp:features:rosterver") { +			getElementGeneric()->setHasRosterVersioning(); +		}  	}  	else if (currentDepth_ == 2) {  		if (inCompression_ && element == "method") { diff --git a/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp index 1cdaf54..9fdea88 100644 --- a/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp +++ b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp @@ -37,6 +37,7 @@ class StreamFeaturesParserTest : public CppUnit::TestFixture {  					"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"  					"<sm xmlns='urn:xmpp:sm:2'/>"  					"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" +					"<ver xmlns=\"urn:xmpp:features:rosterver\"/>"  				"</stream:features>"));  			StreamFeatures::ref element = boost::dynamic_pointer_cast<StreamFeatures>(testling.getElement()); @@ -49,6 +50,7 @@ class StreamFeaturesParserTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT(element->hasAuthenticationMechanism("DIGEST-MD5"));  			CPPUNIT_ASSERT(element->hasAuthenticationMechanism("PLAIN"));  			CPPUNIT_ASSERT(element->hasStreamManagement()); +			CPPUNIT_ASSERT(element->hasRosterVersioning());  		}  		void testParse_Empty() { diff --git a/Swiften/Roster/GetRosterRequest.h b/Swiften/Roster/GetRosterRequest.h index 00cf77f..a3486f0 100644 --- a/Swiften/Roster/GetRosterRequest.h +++ b/Swiften/Roster/GetRosterRequest.h @@ -19,6 +19,12 @@ namespace Swift {  				return ref(new GetRosterRequest(router));  			} +			static ref create(IQRouter* router, const std::string& version) { +				ref result(new GetRosterRequest(router)); +				result->getPayloadGeneric()->setVersion(version); +				return result; +			} +  		private:  			GetRosterRequest(IQRouter* router) :  					GenericRequest<RosterPayload>(IQ::Get, JID(), boost::shared_ptr<Payload>(new RosterPayload()), router) { diff --git a/Swiften/Roster/RosterFileStorage.cpp b/Swiften/Roster/RosterFileStorage.cpp new file mode 100644 index 0000000..de7b442 --- /dev/null +++ b/Swiften/Roster/RosterFileStorage.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Roster/RosterFileStorage.h> + +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + +RosterFileStorage::RosterFileStorage(const boost::filesystem::path& path) : path(path) { +} + +// FIXME +void RosterFileStorage::setRoster(boost::shared_ptr<RosterPayload> r) { +	roster.reset(); +	if (r) { +		roster = boost::make_shared<RosterPayload>(*r); +	} +} + +} diff --git a/Swiften/Roster/RosterFileStorage.h b/Swiften/Roster/RosterFileStorage.h new file mode 100644 index 0000000..dc91c27 --- /dev/null +++ b/Swiften/Roster/RosterFileStorage.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/filesystem/path.hpp> + +#include <Swiften/Roster/RosterStorage.h> + +namespace Swift { +	class RosterFileStorage : public RosterStorage { +		public: +			RosterFileStorage(const boost::filesystem::path& path); + +			// FIXME +			virtual boost::shared_ptr<RosterPayload> getRoster() const { +				return roster; +			} + +			virtual void setRoster(boost::shared_ptr<RosterPayload>); + +		private: +			boost::filesystem::path path; +			boost::shared_ptr<RosterPayload> roster; +	}; +} diff --git a/Swiften/Roster/RosterMemoryStorage.cpp b/Swiften/Roster/RosterMemoryStorage.cpp new file mode 100644 index 0000000..cbf4563 --- /dev/null +++ b/Swiften/Roster/RosterMemoryStorage.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Roster/RosterMemoryStorage.h> + +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + +RosterMemoryStorage::RosterMemoryStorage() { +} + +void RosterMemoryStorage::setRoster(boost::shared_ptr<RosterPayload> r) { +	roster.reset(); +	if (r) { +		roster = boost::make_shared<RosterPayload>(*r); +	} +} + +} diff --git a/Swiften/Roster/RosterMemoryStorage.h b/Swiften/Roster/RosterMemoryStorage.h new file mode 100644 index 0000000..b659d77 --- /dev/null +++ b/Swiften/Roster/RosterMemoryStorage.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Roster/RosterStorage.h> + +namespace Swift { +	class RosterMemoryStorage : public RosterStorage { +		public: +			RosterMemoryStorage(); + +			virtual boost::shared_ptr<RosterPayload> getRoster() const { +				return roster; +			} + +			virtual void setRoster(boost::shared_ptr<RosterPayload>); + +		private: +			boost::shared_ptr<RosterPayload> roster; +	}; +} diff --git a/Swiften/Roster/RosterStorage.cpp b/Swiften/Roster/RosterStorage.cpp new file mode 100644 index 0000000..6bf58de --- /dev/null +++ b/Swiften/Roster/RosterStorage.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Roster/RosterStorage.h> + +namespace Swift { + +RosterStorage::~RosterStorage() { +} + +} diff --git a/Swiften/Roster/RosterStorage.h b/Swiften/Roster/RosterStorage.h new file mode 100644 index 0000000..ba24cb3 --- /dev/null +++ b/Swiften/Roster/RosterStorage.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2011 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 <Swiften/Elements/RosterPayload.h> + +namespace Swift { +	class RosterStorage { +		public: +			virtual ~RosterStorage(); + +			virtual boost::shared_ptr<RosterPayload> getRoster() const = 0; +			virtual void setRoster(boost::shared_ptr<RosterPayload>) = 0; +	}; +} diff --git a/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp b/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp index 4ef1cc1..4c98673 100644 --- a/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp +++ b/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp @@ -16,15 +16,24 @@  #include "Swiften/Client/DummyStanzaChannel.h"  #include "Swiften/Queries/IQRouter.h"  #include "Swiften/Roster/XMPPRosterImpl.h" +#include <Swiften/Roster/RosterMemoryStorage.h>  using namespace Swift;  class XMPPRosterControllerTest : public CppUnit::TestFixture {  		CPPUNIT_TEST_SUITE(XMPPRosterControllerTest); +		CPPUNIT_TEST(testGet_Response);  		CPPUNIT_TEST(testGet_EmptyResponse); +		CPPUNIT_TEST(testGet_NoRosterInStorage); +		CPPUNIT_TEST(testGet_NoVersionInStorage); +		CPPUNIT_TEST(testGet_VersionInStorage); +		CPPUNIT_TEST(testGet_ServerDoesNotSupportVersion); +		CPPUNIT_TEST(testGet_ResponseWithoutNewVersion); +		CPPUNIT_TEST(testGet_ResponseWithNewVersion);  		CPPUNIT_TEST(testAdd);  		CPPUNIT_TEST(testModify);  		CPPUNIT_TEST(testRemove); +		CPPUNIT_TEST(testRemove_RosterStorageUpdated);  		CPPUNIT_TEST(testMany);  		CPPUNIT_TEST_SUITE_END(); @@ -34,20 +43,36 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {  			router_ = new IQRouter(channel_);  			xmppRoster_ = new XMPPRosterImpl();  			handler_ = new XMPPRosterSignalHandler(xmppRoster_); +			rosterStorage_ = new RosterMemoryStorage();  			jid1_ = JID("foo@bar.com");  			jid2_ = JID("alice@wonderland.lit");  			jid3_ = JID("jane@austen.lit");  		}  		void tearDown() { +			delete rosterStorage_;  			delete handler_;  			delete xmppRoster_;  			delete router_;  			delete channel_;  		} +		void testGet_Response() { +			std::auto_ptr<XMPPRosterController> testling(createController()); + +			testling->requestRoster(); +			boost::shared_ptr<RosterPayload> payload = boost::make_shared<RosterPayload>(); +			payload->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both)); +			payload->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both)); +			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), payload)); + +			CPPUNIT_ASSERT_EQUAL(2, handler_->getEventCount()); +			CPPUNIT_ASSERT(xmppRoster_->getItem(jid1_)); +			CPPUNIT_ASSERT(xmppRoster_->getItem(jid2_)); +		} +  		void testGet_EmptyResponse() { -			XMPPRosterController controller(router_, xmppRoster_); +			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);  			controller.requestRoster(); @@ -55,7 +80,7 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {  		}  		void testAdd() { -			XMPPRosterController controller(router_, xmppRoster_); +			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);  			boost::shared_ptr<RosterPayload> payload(new RosterPayload());  			payload->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both)); @@ -68,8 +93,115 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(std::string("Bob"), xmppRoster_->getNameForJID(jid1_));  		} +		void testGet_NoRosterInStorage() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			testling->setUseVersioning(true); + +			testling->requestRoster(); + +			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>(); +			CPPUNIT_ASSERT(roster->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string(""), *roster->getVersion()); +		} + +		void testGet_NoVersionInStorage() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			testling->setUseVersioning(true); +			rosterStorage_->setRoster(boost::make_shared<RosterPayload>()); + +			testling->requestRoster(); + +			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>(); +			CPPUNIT_ASSERT(roster->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string(""), *roster->getVersion()); +		} + +		void testGet_VersionInStorage() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			testling->setUseVersioning(true); +			boost::shared_ptr<RosterPayload> payload(new RosterPayload()); +			payload->setVersion("foover"); +			rosterStorage_->setRoster(payload); + +			testling->requestRoster(); + +			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>(); +			CPPUNIT_ASSERT(roster->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string("foover"), *roster->getVersion()); +		} + +		void testGet_ServerDoesNotSupportVersion() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			boost::shared_ptr<RosterPayload> payload(new RosterPayload()); +			payload->setVersion("foover"); +			rosterStorage_->setRoster(payload); + +			testling->requestRoster(); + +			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>(); +			CPPUNIT_ASSERT(!roster->getVersion()); +		} + +		void testGet_ResponseWithoutNewVersion() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			testling->setUseVersioning(true); +			boost::shared_ptr<RosterPayload> storedRoster(new RosterPayload()); +			storedRoster->setVersion("version10"); +			storedRoster->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both)); +			storedRoster->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both)); +			rosterStorage_->setRoster(storedRoster); +			testling->requestRoster(); + +			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), boost::shared_ptr<RosterPayload>())); + +			CPPUNIT_ASSERT_EQUAL(2, handler_->getEventCount()); +			CPPUNIT_ASSERT(xmppRoster_->getItem(jid1_)); +			CPPUNIT_ASSERT(xmppRoster_->getItem(jid2_)); +			CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); +			CPPUNIT_ASSERT_EQUAL(jid2_, handler_->getLastJID()); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string("version10"), *rosterStorage_->getRoster()->getVersion()); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid1_)); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid2_)); +		} + +		void testGet_ResponseWithNewVersion() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			testling->setUseVersioning(true); +			boost::shared_ptr<RosterPayload> storedRoster(new RosterPayload()); +			storedRoster->setVersion("version10"); +			storedRoster->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both)); +			rosterStorage_->setRoster(storedRoster); +			testling->requestRoster(); + +			boost::shared_ptr<RosterPayload> serverRoster(new RosterPayload()); +			serverRoster->setVersion("version12"); +			serverRoster->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both)); +			std::vector<std::string> groups; +			groups.push_back("foo"); +			groups.push_back("bar"); +			serverRoster->addItem(RosterItemPayload(jid3_, "Rabbit", RosterItemPayload::Both, groups)); +			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), serverRoster)); + + +			CPPUNIT_ASSERT_EQUAL(2, handler_->getEventCount()); +			CPPUNIT_ASSERT(!xmppRoster_->getItem(jid1_)); +			CPPUNIT_ASSERT(xmppRoster_->getItem(jid2_)); +			CPPUNIT_ASSERT(xmppRoster_->getItem(jid3_)); +			CPPUNIT_ASSERT_EQUAL(jid3_, handler_->getLastJID()); +			CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string("version12"), *rosterStorage_->getRoster()->getVersion()); +			CPPUNIT_ASSERT(!rosterStorage_->getRoster()->getItem(jid1_)); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid2_)); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid3_)); +			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(rosterStorage_->getRoster()->getItem(jid3_)->getGroups().size())); +		} +  		void testModify() { -			XMPPRosterController controller(router_, xmppRoster_); +			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);  			boost::shared_ptr<RosterPayload> payload1(new RosterPayload());  			payload1->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));  			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id1", payload1)); @@ -87,9 +219,9 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(std::string("Bob2"), xmppRoster_->getNameForJID(jid1_));  		} - +		  		void testRemove() { -			XMPPRosterController controller(router_, xmppRoster_); +			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);  			boost::shared_ptr<RosterPayload> payload1(new RosterPayload());  			payload1->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));  			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id1", payload1)); @@ -107,8 +239,31 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {  		} +		void testRemove_RosterStorageUpdated() { +			std::auto_ptr<XMPPRosterController> testling(createController()); +			testling->setUseVersioning(true); +			boost::shared_ptr<RosterPayload> storedRoster(new RosterPayload()); +			storedRoster->setVersion("version10"); +			storedRoster->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both)); +			storedRoster->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both)); +			rosterStorage_->setRoster(storedRoster); +			testling->requestRoster(); +			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), boost::shared_ptr<RosterPayload>())); + +			boost::shared_ptr<RosterPayload> payload2(new RosterPayload()); +			payload2->setVersion("version15"); +			payload2->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Remove)); +			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id2", payload2)); + +			CPPUNIT_ASSERT(rosterStorage_->getRoster()); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getVersion()); +			CPPUNIT_ASSERT_EQUAL(std::string("version15"), *rosterStorage_->getRoster()->getVersion()); +			CPPUNIT_ASSERT(!rosterStorage_->getRoster()->getItem(jid1_)); +			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid2_)); +		} +  		void testMany() { -			XMPPRosterController controller(router_, xmppRoster_); +			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);  			boost::shared_ptr<RosterPayload> payload1(new RosterPayload());  			payload1->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));  			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id1", payload1)); @@ -171,12 +326,18 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {  			handler_->reset();  		} +	 +	private: +			XMPPRosterController* createController() { +				return new XMPPRosterController(router_, xmppRoster_, rosterStorage_); +			}  	private:  		DummyStanzaChannel* channel_;  		IQRouter* router_;  		XMPPRosterImpl* xmppRoster_;  		XMPPRosterSignalHandler* handler_; +		RosterMemoryStorage* rosterStorage_;  		JID jid1_;  		JID jid2_;  		JID jid3_; diff --git a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp index 983bc22..d89644a 100644 --- a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp +++ b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp @@ -11,7 +11,7 @@  using namespace Swift; -XMPPRosterSignalHandler::XMPPRosterSignalHandler(Swift::XMPPRoster* roster) { +XMPPRosterSignalHandler::XMPPRosterSignalHandler(Swift::XMPPRoster* roster) : eventCount(0) {  	lastEvent_ = None;  	roster->onJIDAdded.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDAdded, this, _1));  	roster->onJIDRemoved.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDRemoved, this, _1)); @@ -24,4 +24,5 @@ void XMPPRosterSignalHandler::handleJIDUpdated(const Swift::JID& jid, const std:  	lastOldName_ = oldName;  	lastOldGroups_ = oldGroups;  	lastEvent_ = Update; +	eventCount++;  } diff --git a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h index c59b4c6..2cf1159 100644 --- a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h +++ b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h @@ -37,15 +37,21 @@ public:  		lastEvent_ = None;  	} +	int getEventCount() const { +		return eventCount; +	} +  private:  	void handleJIDAdded(const Swift::JID& jid) {  		lastJID_ = jid;  		lastEvent_ = Add; +		eventCount++;  	}  	void handleJIDRemoved(const Swift::JID& jid) {  		lastJID_ = jid;  		lastEvent_ = Remove; +		eventCount++;  	}  	void handleJIDUpdated(const Swift::JID& jid, const std::string& oldName, const std::vector<std::string>& oldGroups); @@ -54,5 +60,5 @@ private:  	Swift::JID lastJID_;  	std::string lastOldName_;  	std::vector<std::string> lastOldGroups_; - +	int eventCount;  }; diff --git a/Swiften/Roster/XMPPRosterController.cpp b/Swiften/Roster/XMPPRosterController.cpp index a294d35..baa74ff 100644 --- a/Swiften/Roster/XMPPRosterController.cpp +++ b/Swiften/Roster/XMPPRosterController.cpp @@ -13,14 +13,15 @@  #include "Swiften/Queries/IQRouter.h"  #include "Swiften/Roster/GetRosterRequest.h"  #include "Swiften/Roster/XMPPRosterImpl.h" +#include <Swiften/Roster/RosterStorage.h>  namespace Swift {  /**   * The controller does not gain ownership of these parameters.   */ -XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, XMPPRosterImpl* xmppRoster) : iqRouter_(iqRouter), rosterPushResponder_(iqRouter), xmppRoster_(xmppRoster) { -	rosterPushResponder_.onRosterReceived.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, false)); +XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, XMPPRosterImpl* xmppRoster, RosterStorage* rosterStorage) : iqRouter_(iqRouter), rosterPushResponder_(iqRouter), xmppRoster_(xmppRoster), rosterStorage_(rosterStorage), useVersioning(false) { +	rosterPushResponder_.onRosterReceived.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, false, boost::shared_ptr<RosterPayload>()));  	rosterPushResponder_.start();  } @@ -30,12 +31,24 @@ XMPPRosterController::~XMPPRosterController() {  void XMPPRosterController::requestRoster() {  	xmppRoster_->clear(); -	GetRosterRequest::ref rosterRequest = GetRosterRequest::create(iqRouter_); -	rosterRequest->onResponse.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, true)); + +	boost::shared_ptr<RosterPayload> storedRoster = rosterStorage_->getRoster(); +	GetRosterRequest::ref rosterRequest; +	if (useVersioning) { +		std::string version = ""; +		if (storedRoster && storedRoster->getVersion()) { +			version = *storedRoster->getVersion(); +		} +		rosterRequest = GetRosterRequest::create(iqRouter_, version); +	} +	else { +		rosterRequest = GetRosterRequest::create(iqRouter_); +	} +	rosterRequest->onResponse.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, true, storedRoster));  	rosterRequest->send();  } -void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial) { +void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial, boost::shared_ptr<RosterPayload> previousRoster) {  	if (rosterPayload) {  		foreach(const RosterItemPayload& item, rosterPayload->getItems()) {  			//Don't worry about the updated case, the XMPPRoster sorts that out. @@ -46,9 +59,33 @@ void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload>  			}  		}  	} +	else if (previousRoster) { +		// The cached version hasn't changed; emit all items +		foreach(const RosterItemPayload& item, previousRoster->getItems()) { +			if (item.getSubscription() != RosterItemPayload::Remove) { +				xmppRoster_->addContact(item.getJID(), item.getName(), item.getGroups(), item.getSubscription()); +			} +			else { +				std::cerr << "ERROR: Stored invalid roster item" << std::endl; +			} +		} +	}  	if (initial) {  		xmppRoster_->onInitialRosterPopulated();  	} +	if (rosterPayload && rosterPayload->getVersion() && useVersioning) { +		saveRoster(*rosterPayload->getVersion()); +	} +} + +void XMPPRosterController::saveRoster(const std::string& version) { +	std::vector<XMPPRosterItem> items = xmppRoster_->getItems(); +	boost::shared_ptr<RosterPayload> roster(new RosterPayload()); +	roster->setVersion(version); +	foreach(const XMPPRosterItem& item, items) { +		roster->addItem(RosterItemPayload(item.getJID(), item.getName(), item.getSubscription(), item.getGroups())); +	} +	rosterStorage_->setRoster(roster);  }  } diff --git a/Swiften/Roster/XMPPRosterController.h b/Swiften/Roster/XMPPRosterController.h index eeb84f6..9313bb6 100644 --- a/Swiften/Roster/XMPPRosterController.h +++ b/Swiften/Roster/XMPPRosterController.h @@ -18,21 +18,29 @@  namespace Swift {  	class IQRouter;  	class XMPPRosterImpl; +	class RosterStorage;  	class XMPPRosterController {  		public: -			XMPPRosterController(IQRouter *iqRouter, XMPPRosterImpl* xmppRoster); +			XMPPRosterController(IQRouter *iqRouter, XMPPRosterImpl* xmppRoster, RosterStorage* storage);  			~XMPPRosterController();  			void requestRoster(); +			void setUseVersioning(bool b) { +				useVersioning = b; +			} +  		private: -			void handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial); +			void handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial, boost::shared_ptr<RosterPayload> previousRoster); +			void saveRoster(const std::string& version);  		private:  			IQRouter* iqRouter_;  			RosterPushResponder rosterPushResponder_;  			XMPPRosterImpl* xmppRoster_; +			RosterStorage* rosterStorage_; +			bool useVersioning;  	};  } diff --git a/Swiften/SConscript b/Swiften/SConscript index 166e2ef..7f27ea1 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -62,6 +62,7 @@ if env["SCONS_STAGE"] == "build" :  			"Client/NickResolver.cpp",  			"Client/NickManager.cpp",  			"Client/NickManagerImpl.cpp", +			"Client/Storages.cpp",  			"Compress/ZLibCodecompressor.cpp",  			"Compress/ZLibDecompressor.cpp",  			"Compress/ZLibCompressor.cpp", @@ -88,6 +89,9 @@ if env["SCONS_STAGE"] == "build" :  			"Queries/Requests/GetInBandRegistrationFormRequest.cpp",  			"Queries/Requests/SubmitInBandRegistrationFormRequest.cpp",  			"Queries/Responders/SoftwareVersionResponder.cpp", +			"Roster/RosterStorage.cpp", +			"Roster/RosterMemoryStorage.cpp", +			"Roster/RosterFileStorage.cpp",  			"Roster/XMPPRoster.cpp",  			"Roster/XMPPRosterImpl.cpp",  			"Roster/XMPPRosterController.cpp", diff --git a/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp index 40faf73..886676a 100644 --- a/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp @@ -20,6 +20,9 @@ RosterSerializer::RosterSerializer() : GenericPayloadSerializer<RosterPayload>()  std::string RosterSerializer::serializePayload(boost::shared_ptr<RosterPayload> roster)  const {  	XMLElement queryElement("query", "jabber:iq:roster"); +	if (roster->getVersion()) { +		queryElement.setAttribute("ver", *roster->getVersion()); +	}  	foreach(const RosterItemPayload& item, roster->getItems()) {  		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));  		itemElement->setAttribute("jid", item.getJID()); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp index b8ceac3..61316df 100644 --- a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp @@ -11,16 +11,15 @@  using namespace Swift; -class RosterSerializerTest : public CppUnit::TestFixture -{ +class RosterSerializerTest : public CppUnit::TestFixture {  		CPPUNIT_TEST_SUITE(RosterSerializerTest);  		CPPUNIT_TEST(testSerialize);  		CPPUNIT_TEST(testSerialize_ItemWithUnknownContent); +		CPPUNIT_TEST(testSerialize_WithVersion); +		CPPUNIT_TEST(testSerialize_WithEmptyVersion);  		CPPUNIT_TEST_SUITE_END();  	public: -		RosterSerializerTest() {} -  		void testSerialize() {  			RosterSerializer testling;  			boost::shared_ptr<RosterPayload> roster(new RosterPayload()); @@ -77,6 +76,26 @@ class RosterSerializerTest : public CppUnit::TestFixture  			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster));  		} + +		void testSerialize_WithVersion() { +			RosterSerializer testling; +			boost::shared_ptr<RosterPayload> roster(new RosterPayload()); +			roster->setVersion("ver20"); + +			std::string expectedResult = "<query ver=\"ver20\" xmlns=\"jabber:iq:roster\"/>"; + +			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster)); +		} + +		void testSerialize_WithEmptyVersion() { +			RosterSerializer testling; +			boost::shared_ptr<RosterPayload> roster(new RosterPayload()); +			roster->setVersion(""); + +			std::string expectedResult = "<query ver=\"\" xmlns=\"jabber:iq:roster\"/>"; + +			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster)); +		}  };  CPPUNIT_TEST_SUITE_REGISTRATION(RosterSerializerTest); diff --git a/Swiften/Serializer/StreamFeaturesSerializer.cpp b/Swiften/Serializer/StreamFeaturesSerializer.cpp index 915433c..11744b4 100644 --- a/Swiften/Serializer/StreamFeaturesSerializer.cpp +++ b/Swiften/Serializer/StreamFeaturesSerializer.cpp @@ -6,6 +6,8 @@  #include "Swiften/Serializer/StreamFeaturesSerializer.h" +#include <boost/smart_ptr/make_shared.hpp> +  #include "Swiften/Serializer/XML/XMLElement.h"  #include "Swiften/Serializer/XML/XMLTextNode.h"  #include "Swiften/Base/foreach.h" @@ -49,6 +51,9 @@ std::string StreamFeaturesSerializer::serialize(boost::shared_ptr<Element> eleme  	if (streamFeatures->hasStreamManagement()) {  		streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("sm", "urn:xmpp:sm:2")));  	} +	if (streamFeatures->hasRosterVersioning()) { +		streamFeaturesElement.addNode(boost::make_shared<XMLElement>("ver", "urn:xmpp:features:rosterver")); +	}  	return streamFeaturesElement.serialize();  } diff --git a/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp index 65caa81..aa896c2 100644 --- a/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp +++ b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp @@ -32,6 +32,7 @@ class StreamFeaturesSerializerTest : public CppUnit::TestFixture  			streamFeatures->setHasResourceBind();  			streamFeatures->setHasSession();  			streamFeatures->setHasStreamManagement(); +			streamFeatures->setHasRosterVersioning();  			CPPUNIT_ASSERT_EQUAL(std::string(  				"<stream:features>" @@ -47,6 +48,7 @@ class StreamFeaturesSerializerTest : public CppUnit::TestFixture  					"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"  					"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"  					"<sm xmlns=\"urn:xmpp:sm:2\"/>" +					"<ver xmlns=\"urn:xmpp:features:rosterver\"/>"  				"</stream:features>"), testling.serialize(streamFeatures));  		}  }; | 
 Swift
 Swift