diff options
32 files changed, 1062 insertions, 15 deletions
| diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index d742656..f3bd6d5 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -53,8 +53,11 @@  #include "Swiften/Disco/CapsInfoGenerator.h"  #include "Swiften/Queries/Requests/GetDiscoInfoRequest.h"  #include "Swiften/Queries/Requests/GetVCardRequest.h" -#include "Swiften/Avatars/AvatarFileStorage.h" +#include "Swiften/Avatars/AvatarStorage.h"  #include "Swiften/Avatars/AvatarManager.h" +#include "Swiften/Disco/CapsFileStorage.h" +#include "Swiften/Disco/CapsManager.h" +#include "Swiften/Disco/EntityCapsManager.h"  #include "Swiften/StringCodecs/SHA1.h"  #include "Swiften/StringCodecs/Hexify.h" @@ -78,6 +81,7 @@ MainController::MainController(  		ChatListWindowFactory* chatListWindowFactory,  		MUCSearchWindowFactory* mucSearchWindowFactory,  		AvatarStorage* avatarStorage, +		CapsStorage* capsStorage,  		VCardStorageFactory* vcardStorageFactory,  		ApplicationMessageDisplay* applicationMessageDisplay,  		bool useDelayForLatency) : @@ -115,6 +119,7 @@ MainController::MainController(  	uiEventStream_ = new UIEventStream();  	avatarStorage_ = avatarStorage; +	capsStorage_ = capsStorage;  	eventController_ = new EventController();  	eventController_->onEventQueueLengthChange.connect(boost::bind(&MainController::handleEventQueueLengthChange, this, _1)); @@ -178,6 +183,10 @@ void MainController::resetClient() {  	presenceOracle_ = NULL;  	delete nickResolver_;  	nickResolver_ = NULL; +	delete entityCapsManager_; +	entityCapsManager_ = NULL; +	delete capsManager_; +	capsManager_ = NULL;  	delete avatarManager_;  	avatarManager_ = NULL;  	delete vcardManager_; @@ -235,6 +244,8 @@ void MainController::handleConnected() {  		vcardManager_ = new VCardManager(jid_, client_, getVCardStorageForProfile(jid_));  		vcardManager_->onVCardChanged.connect(boost::bind(&MainController::handleVCardReceived, this, _1, _2));  		avatarManager_ = new AvatarManager(vcardManager_, client_, avatarStorage_, mucRegistry_); +		capsManager_ = new CapsManager(capsStorage_, client_, client_); +		entityCapsManager_ = new EntityCapsManager(capsManager_, client_);  		nickResolver_ = new NickResolver(this->jid_.toBare(), xmppRoster_, vcardManager_, mucRegistry_); diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index ef9c79e..8596ace 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -29,6 +29,7 @@  namespace Swift {  	class AvatarStorage; +	class CapsStorage;  	class VCardStorage;  	class VCardManager;  	class Application; @@ -47,6 +48,8 @@ namespace Swift {  	class DiscoInfoResponder;  	class ApplicationMessageDisplay;  	class AvatarManager; +	class CapsManager; +	class EntityCapsManager;  	class LoginWindow;  	class EventLoop;  	class SoftwareVersionResponder; @@ -82,6 +85,7 @@ namespace Swift {  					ChatListWindowFactory* chatListWindowFactory_,  					MUCSearchWindowFactory* mucSearchWindowFactory,  					AvatarStorage* avatarStorage, +					CapsStorage* capsStorage,  					VCardStorageFactory* vcardStorageFactory,  					ApplicationMessageDisplay* applicationMessageDisplay,  					bool useDelayForLatency); @@ -126,6 +130,7 @@ namespace Swift {  			SettingsProvider *settings_;  			ProfileSettingsProvider* profileSettings_;  			AvatarStorage* avatarStorage_; +			CapsStorage* capsStorage_;  			VCardStorageFactory* vcardStorageFactory_;  			VCardManager* vcardManager_;  			ApplicationMessageDisplay* applicationMessageDisplay_; @@ -149,6 +154,8 @@ namespace Swift {  			SystemTrayController* systemTrayController_;  			SoundEventController* soundEventController_;  			AvatarManager* avatarManager_; +			CapsManager* capsManager_; +			EntityCapsManager* entityCapsManager_;  			String vCardPhotoHash_;  			String password_;  			String certificateFile_; diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index e1f5e89..e548e29 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -27,6 +27,7 @@  #include "Swiften/Application/PlatformApplication.h"  #include "Swiften/Application/PlatformApplicationPathProvider.h"  #include "Swiften/Avatars/AvatarFileStorage.h" +#include "Swiften/Disco/CapsFileStorage.h"  #include "Swiften/VCards/VCardFileStorageFactory.h"  #include "Swiften/Base/String.h"  #include "Swiften/Base/Platform.h" @@ -84,6 +85,7 @@ QtSwift::QtSwift(po::variables_map options) : autoUpdater_(NULL) {  	applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME);  	avatarStorage_ = new AvatarFileStorage(applicationPathProvider_->getAvatarDir());  	vcardStorageFactory_ = new VCardFileStorageFactory(applicationPathProvider_->getDataDir()); +	capsStorage_ = new CapsFileStorage(applicationPathProvider_->getDataDir() / "caps");  	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settings_, tabs_, "");  	soundPlayer_ = new QtSoundPlayer(applicationPathProvider_);  	if (splitter_) { @@ -117,6 +119,7 @@ QtSwift::QtSwift(po::variables_map options) : autoUpdater_(NULL) {  				chatListWindowFactory,  				mucSearchWindowFactory,  				avatarStorage_, +				capsStorage_,  				vcardStorageFactory_,  				application_->getApplicationMessageDisplay(),  				options.count("latency-debug") > 0); @@ -163,6 +166,7 @@ QtSwift::~QtSwift() {  	foreach (QtChatListWindowFactory* factory, chatListWindowFactories_) {  		delete factory;  	} +	delete capsStorage_;  	delete avatarStorage_;  	delete vcardStorageFactory_;  } diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index a0ea069..4c570ae 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -27,6 +27,7 @@ namespace Swift {  	class Application;  	class ApplicationPathProvider;  	class AvatarStorage; +	class CapsStorage;  	class MainController;  	class QtChatWindowFactory;  	class QtMainWindowFactory; @@ -62,6 +63,7 @@ namespace Swift {  			Application* application_;  			ApplicationPathProvider* applicationPathProvider_;  			AvatarStorage* avatarStorage_; +			CapsStorage* capsStorage_;  			VCardStorageFactory* vcardStorageFactory_;  			AutoUpdater* autoUpdater_; diff --git a/Swiften/Client/DummyStanzaChannel.h b/Swiften/Client/DummyStanzaChannel.h index 05066a8..f13e587 100644 --- a/Swiften/Client/DummyStanzaChannel.h +++ b/Swiften/Client/DummyStanzaChannel.h @@ -48,7 +48,10 @@ namespace Swift {  				return false;  			} -			template<typename T> bool isRequestAtIndex(int index, const JID& jid, IQ::Type type) { +			template<typename T> bool isRequestAtIndex(size_t index, const JID& jid, IQ::Type type) { +				if (index >= sentStanzas.size()) { +					return false; +				}  				boost::shared_ptr<IQ> iqStanza = boost::dynamic_pointer_cast<IQ>(sentStanzas[index]);  				return iqStanza && iqStanza->getType() == type && iqStanza->getTo() == jid && iqStanza->getPayload<T>();  			} diff --git a/Swiften/Disco/CapsFileStorage.cpp b/Swiften/Disco/CapsFileStorage.cpp new file mode 100644 index 0000000..c5326a7 --- /dev/null +++ b/Swiften/Disco/CapsFileStorage.cpp @@ -0,0 +1,61 @@ +/* + * 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/CapsFileStorage.h" + +#include <iostream> +#include <boost/filesystem/fstream.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +CapsFileStorage::CapsFileStorage(const boost::filesystem::path& path) : path(path) { +} + +DiscoInfo::ref CapsFileStorage::getDiscoInfo(const String& hash) const { +	boost::filesystem::path capsPath(getCapsPath(hash)); +	if (boost::filesystem::exists(capsPath)) { +		ByteArray data; +		data.readFromFile(capsPath.string()); + +		DiscoInfoParser parser; +		PayloadParserTester tester(&parser); +		tester.parse(String(data.getData(), data.getSize())); +		return DiscoInfo::cast(parser.getPayload()); +	} +	else { +		return DiscoInfo::ref(); +	} +} + +void CapsFileStorage::setDiscoInfo(const String& hash, DiscoInfo::ref discoInfo) { +	boost::filesystem::path capsPath(getCapsPath(hash)); +	if (!boost::filesystem::exists(capsPath.parent_path())) { +		try { +			boost::filesystem::create_directories(capsPath.parent_path()); +		} +		catch (const boost::filesystem::filesystem_error& e) { +			std::cerr << "ERROR: " << e.what() << std::endl; +		} +	} +	DiscoInfo::ref bareDiscoInfo(new DiscoInfo(*discoInfo.get())); +	bareDiscoInfo->setNode(""); +	boost::filesystem::ofstream file(capsPath); +	file << DiscoInfoSerializer().serializePayload(bareDiscoInfo); +	file.close(); +} + +boost::filesystem::path CapsFileStorage::getCapsPath(const String& hash) const { +	return path / (Hexify::hexify(Base64::decode(hash)) + ".xml").getUTF8String(); +} + +} diff --git a/Swiften/Disco/CapsFileStorage.h b/Swiften/Disco/CapsFileStorage.h new file mode 100644 index 0000000..ea1b1a2 --- /dev/null +++ b/Swiften/Disco/CapsFileStorage.h @@ -0,0 +1,28 @@ +/* + * 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/filesystem.hpp> + +#include "Swiften/Disco/CapsStorage.h" +#include "Swiften/Base/String.h" + +namespace Swift { +	class CapsFileStorage : public CapsStorage { +		public: +			CapsFileStorage(const boost::filesystem::path& path); + +			virtual DiscoInfo::ref getDiscoInfo(const String& hash) const; +			virtual void setDiscoInfo(const String& hash, DiscoInfo::ref discoInfo); + +		private: +			boost::filesystem::path getCapsPath(const String& hash) const; + +		private: +			boost::filesystem::path path; +	}; +} diff --git a/Swiften/Disco/CapsInfoGenerator.h b/Swiften/Disco/CapsInfoGenerator.h index d69c05e..cc32bbd 100644 --- a/Swiften/Disco/CapsInfoGenerator.h +++ b/Swiften/Disco/CapsInfoGenerator.h @@ -4,8 +4,7 @@   * See Documentation/Licenses/GPLv3.txt for more information.   */ -#ifndef SWIFTEN_CapsInfoGenerator_H -#define SWIFTEN_CapsInfoGenerator_H +#pragma once  #include "Swiften/Base/String.h"  #include "Swiften/Elements/CapsInfo.h" @@ -23,5 +22,3 @@ namespace Swift {  			String node_;  	};  } - -#endif diff --git a/Swiften/Disco/CapsManager.cpp b/Swiften/Disco/CapsManager.cpp new file mode 100644 index 0000000..185f8e6 --- /dev/null +++ b/Swiften/Disco/CapsManager.cpp @@ -0,0 +1,80 @@ +/* + * 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/CapsManager.h" + +#include <boost/bind.hpp> + +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Disco/CapsStorage.h" +#include "Swiften/Disco/CapsInfoGenerator.h" +#include "Swiften/Elements/CapsInfo.h" +#include "Swiften/Queries/Requests/GetDiscoInfoRequest.h" + +namespace Swift { + +CapsManager::CapsManager(CapsStorage* capsStorage, StanzaChannel* stanzaChannel, IQRouter* iqRouter) : iqRouter(iqRouter), capsStorage(capsStorage) { +	stanzaChannel->onPresenceReceived.connect(boost::bind(&CapsManager::handlePresenceReceived, this, _1)); +	stanzaChannel->onAvailableChanged.connect(boost::bind(&CapsManager::handleStanzaChannelAvailableChanged, this, _1)); +} + +void CapsManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) { +	boost::shared_ptr<CapsInfo> capsInfo = presence->getPayload<CapsInfo>(); +	if (!capsInfo || capsInfo->getHash() != "sha-1" || presence->getPayload<ErrorPayload>()) { +		return; +	} +	String hash = capsInfo->getVersion(); +	if (capsStorage->getDiscoInfo(hash)) { +		return; +	} +	if (failingCaps.find(std::make_pair(presence->getFrom(), hash)) != failingCaps.end()) { +		return; +	} +	if (requestedDiscoInfos.find(hash) != requestedDiscoInfos.end()) { +		fallbacks[hash].insert(std::make_pair(presence->getFrom(), capsInfo->getNode())); +		return; +	} +	requestDiscoInfo(presence->getFrom(), capsInfo->getNode(), hash); +} + +void CapsManager::handleStanzaChannelAvailableChanged(bool available) { +	if (available) { +		failingCaps.clear(); +		fallbacks.clear(); +		requestedDiscoInfos.clear(); +	} +} + +void CapsManager::handleDiscoInfoReceived(const JID& from, const String& hash, DiscoInfo::ref discoInfo, const boost::optional<ErrorPayload>& error) { +	requestedDiscoInfos.erase(hash); +	if (error || CapsInfoGenerator("").generateCapsInfo(*discoInfo.get()).getVersion() != hash) { +		failingCaps.insert(std::make_pair(from, hash)); +		std::map<String, std::set< std::pair<JID, String> > >::iterator i = fallbacks.find(hash); +		if (i != fallbacks.end() && !i->second.empty()) { +			std::pair<JID,String> fallbackAndNode = *i->second.begin(); +			i->second.erase(i->second.begin()); +			requestDiscoInfo(fallbackAndNode.first, fallbackAndNode.second, hash); +		} +		return; +	} +	fallbacks.erase(hash); +	capsStorage->setDiscoInfo(hash, discoInfo); +	onCapsAvailable(hash); +} + +void CapsManager::requestDiscoInfo(const JID& jid, const String& node, const String& hash) { +	boost::shared_ptr<GetDiscoInfoRequest> request(new GetDiscoInfoRequest(jid, node + "#" + hash, iqRouter)); +	request->onResponse.connect(boost::bind(&CapsManager::handleDiscoInfoReceived, this, jid, hash, _1, _2)); +	requestedDiscoInfos.insert(hash); +	request->send(); +} + +DiscoInfo::ref CapsManager::getCaps(const String& hash) const { +	return capsStorage->getDiscoInfo(hash); +} + + +} diff --git a/Swiften/Disco/CapsManager.h b/Swiften/Disco/CapsManager.h new file mode 100644 index 0000000..3188a07 --- /dev/null +++ b/Swiften/Disco/CapsManager.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <set> +#include <map> + +#include "Swiften/Base/boost_bsignals.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Elements/CapsInfo.h" +#include "Swiften/Elements/ErrorPayload.h" +#include "Swiften/Disco/CapsProvider.h" + +namespace Swift { +	class StanzaChannel; +	class IQRouter; +	class JID; +	class CapsStorage; + +	class CapsManager : public CapsProvider, public boost::bsignals::trackable {  +		public: +			CapsManager(CapsStorage*, StanzaChannel*, IQRouter*); + +			DiscoInfo::ref getCaps(const String&) const; + +		private: +			void handlePresenceReceived(boost::shared_ptr<Presence>); +			void handleStanzaChannelAvailableChanged(bool); +			void handleDiscoInfoReceived(const JID&, const String& hash, DiscoInfo::ref, const boost::optional<ErrorPayload>&); +			void requestDiscoInfo(const JID& jid, const String& node, const String& hash); + +		private: +			IQRouter* iqRouter; +			CapsStorage* capsStorage; +			std::set<String> requestedDiscoInfos; +			std::set< std::pair<JID, String> > failingCaps; +			std::map<String, std::set< std::pair<JID, String> > > fallbacks; +	}; +} diff --git a/Swiften/Disco/CapsMemoryStorage.h b/Swiften/Disco/CapsMemoryStorage.h new file mode 100644 index 0000000..71bd5d5 --- /dev/null +++ b/Swiften/Disco/CapsMemoryStorage.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> + +#include "Swiften/Base/String.h" +#include "Swiften/Disco/CapsStorage.h" + +namespace Swift { +	class CapsMemoryStorage : public CapsStorage { +		public: +			CapsMemoryStorage() {} + +			virtual DiscoInfo::ref getDiscoInfo(const String& hash) const { +				CapsMap::const_iterator i = caps.find(hash); +				if (i != caps.end()) { +					return i->second; +				} +				else { +					return DiscoInfo::ref(); +				} +			} + +			virtual void setDiscoInfo(const String& hash, DiscoInfo::ref discoInfo) { +				caps[hash] = discoInfo; +			} + +		private: +			typedef std::map<String, DiscoInfo::ref> CapsMap; +			CapsMap caps; +	}; +} diff --git a/Swiften/Disco/CapsProvider.h b/Swiften/Disco/CapsProvider.h new file mode 100644 index 0000000..2aaad79 --- /dev/null +++ b/Swiften/Disco/CapsProvider.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Base/boost_bsignals.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Elements/CapsInfo.h" + +namespace Swift { +	class String; + +	class CapsProvider {  +		public: +			virtual ~CapsProvider() {} + +			virtual DiscoInfo::ref getCaps(const String&) const = 0; + +			boost::signal<void (const String&)> onCapsAvailable; +	}; +} diff --git a/Swiften/Disco/CapsStorage.cpp b/Swiften/Disco/CapsStorage.cpp new file mode 100644 index 0000000..acb58fe --- /dev/null +++ b/Swiften/Disco/CapsStorage.cpp @@ -0,0 +1,14 @@ +/* + * 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/CapsStorage.h" + +namespace Swift { + +CapsStorage::~CapsStorage() { +} + +} diff --git a/Swiften/Disco/CapsStorage.h b/Swiften/Disco/CapsStorage.h new file mode 100644 index 0000000..e4ff945 --- /dev/null +++ b/Swiften/Disco/CapsStorage.h @@ -0,0 +1,23 @@ +/* + * 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 "Swiften/Elements/DiscoInfo.h" + +namespace Swift { +	class String; + +	class CapsStorage { +		public: +			virtual ~CapsStorage(); + +			virtual DiscoInfo::ref getDiscoInfo(const String&) const = 0; +			virtual void setDiscoInfo(const String&, DiscoInfo::ref) = 0; +	}; +} diff --git a/Swiften/Disco/EntityCapsManager.cpp b/Swiften/Disco/EntityCapsManager.cpp new file mode 100644 index 0000000..26e0594 --- /dev/null +++ b/Swiften/Disco/EntityCapsManager.cpp @@ -0,0 +1,79 @@ +/* + * 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/EntityCapsManager.h" + +#include <boost/bind.hpp> + +#include "Swiften/Disco/CapsProvider.h" +#include "Swiften/Client/StanzaChannel.h" + +namespace Swift { + +EntityCapsManager::EntityCapsManager(CapsProvider* capsProvider, StanzaChannel* stanzaChannel) : capsProvider(capsProvider) { +	stanzaChannel->onPresenceReceived.connect(boost::bind(&EntityCapsManager::handlePresenceReceived, this, _1)); +	stanzaChannel->onAvailableChanged.connect(boost::bind(&EntityCapsManager::handleStanzaChannelAvailableChanged, this, _1)); +	capsProvider->onCapsAvailable.connect(boost::bind(&EntityCapsManager::handleCapsAvailable, this, _1)); +} + +void EntityCapsManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) { +	JID from = presence->getFrom(); +	if (presence->isAvailable()) { +		boost::shared_ptr<CapsInfo> capsInfo = presence->getPayload<CapsInfo>(); +		if (!capsInfo || capsInfo->getHash() != "sha-1" || presence->getPayload<ErrorPayload>()) { +			return; +		} +		String hash = capsInfo->getVersion(); +		std::map<JID, String>::iterator i = caps.find(from); +		if (i == caps.end() || i->second != hash) { +			caps.insert(std::make_pair(from, hash)); +			DiscoInfo::ref disco = capsProvider->getCaps(hash); +			if (disco) { +				onCapsChanged(from); +			} +			else if (i != caps.end()) { +				caps.erase(i); +				onCapsChanged(from); +			} +		} +	} +	else { +		std::map<JID, String>::iterator i = caps.find(from); +		if (i != caps.end()) { +			caps.erase(i); +			onCapsChanged(from); +		} +	} +} + +void EntityCapsManager::handleStanzaChannelAvailableChanged(bool available) { +	if (available) { +		std::map<JID, String> capsCopy; +		capsCopy.swap(caps); +		for (std::map<JID,String>::const_iterator i = capsCopy.begin(); i != capsCopy.end(); ++i) { +			onCapsChanged(i->first); +		} +	} +} + +void EntityCapsManager::handleCapsAvailable(const String& hash) { +	// TODO: Use Boost.Bimap ? +	for (std::map<JID,String>::const_iterator i = caps.begin(); i != caps.end(); ++i) { +		if (i->second == hash) { +			onCapsChanged(i->first); +		} +	} +} + +DiscoInfo::ref EntityCapsManager::getCaps(const JID& jid) const { +	std::map<JID, String>::const_iterator i = caps.find(jid); +	if (i != caps.end()) { +		return capsProvider->getCaps(i->second); +	} +	return DiscoInfo::ref(); +} + +} diff --git a/Swiften/Disco/EntityCapsManager.h b/Swiften/Disco/EntityCapsManager.h new file mode 100644 index 0000000..810e260 --- /dev/null +++ b/Swiften/Disco/EntityCapsManager.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <map> + +#include "Swiften/Base/boost_bsignals.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { +	class StanzaChannel; +	class CapsProvider; + +	class EntityCapsManager : public boost::bsignals::trackable {  +		public: +			EntityCapsManager(CapsProvider*, StanzaChannel*); + +			DiscoInfo::ref getCaps(const JID&) const; + +			boost::signal<void (const JID&)> onCapsChanged; + +		private: +			void handlePresenceReceived(boost::shared_ptr<Presence>); +			void handleStanzaChannelAvailableChanged(bool); +			void handleCapsAvailable(const String&); + +		private: +			CapsProvider* capsProvider; +			std::map<JID, String> caps; +	}; +} diff --git a/Swiften/Disco/SConscript b/Swiften/Disco/SConscript new file mode 100644 index 0000000..9da3fb3 --- /dev/null +++ b/Swiften/Disco/SConscript @@ -0,0 +1,10 @@ +Import("swiften_env") + +objects = swiften_env.StaticObject([ +			"CapsInfoGenerator.cpp", +			"CapsManager.cpp", +			"EntityCapsManager.cpp", +			"CapsStorage.cpp", +			"CapsFileStorage.cpp", +		]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Disco/UnitTest/CapsManagerTest.cpp b/Swiften/Disco/UnitTest/CapsManagerTest.cpp new file mode 100644 index 0000000..2156c3e --- /dev/null +++ b/Swiften/Disco/UnitTest/CapsManagerTest.cpp @@ -0,0 +1,271 @@ +/* + * 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/Disco/CapsManager.h" +#include "Swiften/Disco/CapsMemoryStorage.h" +#include "Swiften/Disco/CapsInfoGenerator.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Elements/CapsInfo.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +using namespace Swift; + +class CapsManagerTest : public CppUnit::TestFixture { +		CPPUNIT_TEST_SUITE(CapsManagerTest); +		CPPUNIT_TEST(testReceiveNewHashRequestsDisco); +		CPPUNIT_TEST(testReceiveSameHashDoesNotRequestDisco); +		CPPUNIT_TEST(testReceiveLegacyCapsDoesNotRequestDisco); +		CPPUNIT_TEST(testReceiveSameHashFromSameUserAfterFailedDiscoDoesNotRequestDisco); +		CPPUNIT_TEST(testReceiveSameHashFromDifferentUserAfterFailedDiscoRequestsDisco); +		CPPUNIT_TEST(testReceiveSameHashFromDifferentUserAfterIncorrectVerificationRequestsDisco); +		CPPUNIT_TEST(testReceiveDifferentHashFromSameUserAfterFailedDiscoDoesNotRequestDisco); +		CPPUNIT_TEST(testReceiveSameHashAfterSuccesfulDiscoDoesNotRequestDisco); +		CPPUNIT_TEST(testReceiveSuccesfulDiscoStoresCaps); +		CPPUNIT_TEST(testReceiveIncorrectVerificationDiscoDoesNotStoreCaps); +		CPPUNIT_TEST(testReceiveFailingDiscoFallsBack); +		CPPUNIT_TEST(testReceiveFailingFallbackDiscoFallsBack); +		CPPUNIT_TEST(testReceiveSameHashFromFailingUserAfterReconnectRequestsDisco); +		CPPUNIT_TEST(testReconnectResetsFallback); +		CPPUNIT_TEST(testReconnectResetsRequests); +		CPPUNIT_TEST_SUITE_END(); + +	public: +		void setUp() { +			stanzaChannel = new DummyStanzaChannel(); +			iqRouter = new IQRouter(stanzaChannel); +			storage = new CapsMemoryStorage(); +			user1 = JID("user1@bar.com/bla"); +			discoInfo1 = boost::shared_ptr<DiscoInfo>(new DiscoInfo()); +			discoInfo1->addFeature("http://swift.im/feature1"); +			capsInfo1 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node1.im").generateCapsInfo(*discoInfo1.get()))); +			capsInfo1alt = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo1.get()))); +			user2 = JID("user2@foo.com/baz"); +			discoInfo2 = boost::shared_ptr<DiscoInfo>(new DiscoInfo()); +			discoInfo2->addFeature("http://swift.im/feature2"); +			capsInfo2 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo2.get()))); +			user3 = JID("user3@foo.com/baz"); +			legacyCapsInfo = boost::shared_ptr<CapsInfo>(new CapsInfo("http://swift.im", "ver1", "")); +		} + +		void tearDown() { +			delete iqRouter; +			delete stanzaChannel; +		} + +		void testReceiveNewHashRequestsDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get)); +			boost::shared_ptr<DiscoInfo> discoInfo(stanzaChannel->sentStanzas[0]->getPayload<DiscoInfo>()); +			CPPUNIT_ASSERT(discoInfo); +			CPPUNIT_ASSERT_EQUAL("http://node1.im#" + capsInfo1->getVersion(), discoInfo->getNode()); +		} + +		void testReceiveSameHashDoesNotRequestDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size())); +		} + +		void testReceiveLegacyCapsDoesNotRequestDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, legacyCapsInfo); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size())); +		} + +		void testReceiveSameHashAfterSuccesfulDiscoDoesNotRequestDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendDiscoInfoResult(discoInfo1); + +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size())); +		} + +		void testReceiveSameHashFromSameUserAfterFailedDiscoDoesNotRequestDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size())); +		} + +		void testReceiveSameHashFromSameUserAfterIncorrectVerificationDoesNotRequestDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendDiscoInfoResult(discoInfo2); + +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size())); +		} + +		void testReceiveSameHashFromDifferentUserAfterFailedDiscoRequestsDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user2, capsInfo1); +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user2, IQ::Get)); +		} + +		void testReceiveSameHashFromDifferentUserAfterIncorrectVerificationRequestsDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendDiscoInfoResult(discoInfo2); + +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user2, capsInfo1); +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user2, IQ::Get)); +		} + +		void testReceiveDifferentHashFromSameUserAfterFailedDiscoDoesNotRequestDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user1, capsInfo2); + +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get)); +		} + +		void testReceiveSuccesfulDiscoStoresCaps() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendDiscoInfoResult(discoInfo1); + +			boost::shared_ptr<DiscoInfo> discoInfo(storage->getDiscoInfo(capsInfo1->getVersion())); +			CPPUNIT_ASSERT(discoInfo); +			CPPUNIT_ASSERT(discoInfo->hasFeature("http://swift.im/feature1")); +		} + +		void testReceiveIncorrectVerificationDiscoDoesNotStoreCaps() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendDiscoInfoResult(discoInfo2); + +			boost::shared_ptr<DiscoInfo> discoInfo(storage->getDiscoInfo(capsInfo1->getVersion())); +			CPPUNIT_ASSERT(!discoInfo); +		} + +		void testReceiveFailingDiscoFallsBack() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendPresenceWithCaps(user2, capsInfo1alt); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(1, user2, IQ::Get)); +			boost::shared_ptr<DiscoInfo> discoInfo(stanzaChannel->sentStanzas[1]->getPayload<DiscoInfo>()); +			CPPUNIT_ASSERT(discoInfo); +			CPPUNIT_ASSERT_EQUAL("http://node2.im#" + capsInfo1alt->getVersion(), discoInfo->getNode()); +		} + +		void testReceiveFailingFallbackDiscoFallsBack() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendPresenceWithCaps(user2, capsInfo1alt); +			sendPresenceWithCaps(user3, capsInfo1); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[1]->getID())); + +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(2, user3, IQ::Get)); +		} + +		void testReceiveSameHashFromFailingUserAfterReconnectRequestsDisco() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); +			stanzaChannel->setAvailable(false); +			stanzaChannel->setAvailable(true); +			stanzaChannel->sentStanzas.clear(); + +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get)); +		} + +		void testReconnectResetsFallback() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			sendPresenceWithCaps(user2, capsInfo1alt); +			stanzaChannel->setAvailable(false); +			stanzaChannel->setAvailable(true); +			stanzaChannel->sentStanzas.clear(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size())); +		} + +		void testReconnectResetsRequests() { +			std::auto_ptr<CapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); +			stanzaChannel->sentStanzas.clear(); +			stanzaChannel->setAvailable(false); +			stanzaChannel->setAvailable(true); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get)); +		} + +	private: +		std::auto_ptr<CapsManager> createManager() { +			std::auto_ptr<CapsManager> manager(new CapsManager(storage, stanzaChannel, iqRouter)); +			//manager->onCapsChanged.connect(boost::bind(&CapsManagerTest::handleCapsChanged, this, _1)); +			return manager; +		} + +		void handleCapsChanged(const JID& jid) { +			changes.push_back(jid); +		} + +		void sendPresenceWithCaps(const JID& jid, boost::shared_ptr<CapsInfo> caps) { +			boost::shared_ptr<Presence> presence(new Presence()); +			presence->setFrom(jid); +			presence->addPayload(caps); +			stanzaChannel->onPresenceReceived(presence); +		} + +		void sendDiscoInfoResult(boost::shared_ptr<DiscoInfo> discoInfo) { +			stanzaChannel->onIQReceived(IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[0]->getID(), discoInfo)); +		} + +	private: +		DummyStanzaChannel* stanzaChannel; +		IQRouter* iqRouter; +		CapsStorage* storage; +		std::vector<JID> changes; +		JID user1; +		boost::shared_ptr<DiscoInfo> discoInfo1; +		boost::shared_ptr<CapsInfo> capsInfo1; +		boost::shared_ptr<CapsInfo> capsInfo1alt; +		JID user2; +		boost::shared_ptr<DiscoInfo> discoInfo2; +		boost::shared_ptr<CapsInfo> capsInfo2; +		boost::shared_ptr<CapsInfo> legacyCapsInfo; +		JID user3; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CapsManagerTest); diff --git a/Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp b/Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp new file mode 100644 index 0000000..0a498cf --- /dev/null +++ b/Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp @@ -0,0 +1,188 @@ +/* + * 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/Disco/EntityCapsManager.h" +#include "Swiften/Disco/CapsProvider.h" +#include "Swiften/Elements/CapsInfo.h" +#include "Swiften/Client/DummyStanzaChannel.h" +#include "Swiften/Disco/CapsInfoGenerator.h" + +using namespace Swift; + +class EntityCapsManagerTest : public CppUnit::TestFixture { +		CPPUNIT_TEST_SUITE(EntityCapsManagerTest); +		CPPUNIT_TEST(testReceiveKnownHash); +		CPPUNIT_TEST(testReceiveKnownHashTwiceDoesNotTriggerChange); +		CPPUNIT_TEST(testReceiveUnknownHashDoesNotTriggerChange); +		CPPUNIT_TEST(testReceiveUnknownHashAfterKnownHashTriggersChangeAndClearsCaps); +		CPPUNIT_TEST(testReceiveUnavailablePresenceAfterKnownHashTriggersChangeAndClearsCaps); +		CPPUNIT_TEST(testReconnectTriggersChangeAndClearsCaps); +		CPPUNIT_TEST(testHashAvailable); +		CPPUNIT_TEST_SUITE_END(); + +	public: +		void setUp() { +			stanzaChannel = new DummyStanzaChannel(); +			capsProvider = new DummyCapsProvider(); + +			user1 = JID("user1@bar.com/bla"); +			discoInfo1 = boost::shared_ptr<DiscoInfo>(new DiscoInfo()); +			discoInfo1->addFeature("http://swift.im/feature1"); +			capsInfo1 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node1.im").generateCapsInfo(*discoInfo1.get()))); +			capsInfo1alt = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo1.get()))); +			user2 = JID("user2@foo.com/baz"); +			discoInfo2 = boost::shared_ptr<DiscoInfo>(new DiscoInfo()); +			discoInfo2->addFeature("http://swift.im/feature2"); +			capsInfo2 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo2.get()))); +			user3 = JID("user3@foo.com/baz"); +			legacyCapsInfo = boost::shared_ptr<CapsInfo>(new CapsInfo("http://swift.im", "ver1", "")); +		} + +		void tearDown() { +			delete capsProvider; +			delete stanzaChannel; +		} + +		void testReceiveKnownHash() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			capsProvider->caps[capsInfo1->getVersion()] = discoInfo1; +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size())); +			CPPUNIT_ASSERT_EQUAL(user1, changes[0]); +			CPPUNIT_ASSERT_EQUAL(discoInfo1, testling->getCaps(user1)); +		} + +		void testReceiveKnownHashTwiceDoesNotTriggerChange() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			capsProvider->caps[capsInfo1->getVersion()] = discoInfo1; +			sendPresenceWithCaps(user1, capsInfo1); +			changes.clear(); + +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size())); +		} + +		void testReceiveUnknownHashDoesNotTriggerChange() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size())); +		} + +		void testHashAvailable() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			sendPresenceWithCaps(user1, capsInfo1); + +			capsProvider->caps[capsInfo1->getVersion()] = discoInfo1; +			capsProvider->onCapsAvailable(capsInfo1->getVersion()); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size())); +			CPPUNIT_ASSERT_EQUAL(user1, changes[0]); +			CPPUNIT_ASSERT_EQUAL(discoInfo1, testling->getCaps(user1)); +		} + +		void testReceiveUnknownHashAfterKnownHashTriggersChangeAndClearsCaps() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			capsProvider->caps[capsInfo1->getVersion()] = discoInfo1; +			sendPresenceWithCaps(user1, capsInfo1); +			changes.clear(); +			sendPresenceWithCaps(user1, capsInfo2); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size())); +			CPPUNIT_ASSERT_EQUAL(user1, changes[0]); +			CPPUNIT_ASSERT(!testling->getCaps(user1)); +		} + +		void testReceiveUnavailablePresenceAfterKnownHashTriggersChangeAndClearsCaps() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			capsProvider->caps[capsInfo1->getVersion()] = discoInfo1; +			sendPresenceWithCaps(user1, capsInfo1); +			changes.clear(); +			sendUnavailablePresence(user1); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size())); +			CPPUNIT_ASSERT_EQUAL(user1, changes[0]); +			CPPUNIT_ASSERT(!testling->getCaps(user1)); +		} + +		void testReconnectTriggersChangeAndClearsCaps() { +			std::auto_ptr<EntityCapsManager> testling = createManager(); +			capsProvider->caps[capsInfo1->getVersion()] = discoInfo1; +			capsProvider->caps[capsInfo2->getVersion()] = discoInfo2; +			sendPresenceWithCaps(user1, capsInfo1); +			sendPresenceWithCaps(user2, capsInfo2); +			changes.clear(); +			stanzaChannel->setAvailable(false); +			stanzaChannel->setAvailable(true); + +			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(changes.size())); +			CPPUNIT_ASSERT_EQUAL(user1, changes[0]); +			CPPUNIT_ASSERT(!testling->getCaps(user1)); +			CPPUNIT_ASSERT_EQUAL(user2, changes[1]); +			CPPUNIT_ASSERT(!testling->getCaps(user2)); +		} + +	private: +		std::auto_ptr<EntityCapsManager> createManager() { +			std::auto_ptr<EntityCapsManager> manager(new EntityCapsManager(capsProvider, stanzaChannel)); +			manager->onCapsChanged.connect(boost::bind(&EntityCapsManagerTest::handleCapsChanged, this, _1)); +			return manager; +		} + +		void handleCapsChanged(const JID& jid) { +			changes.push_back(jid); +		} + +		void sendPresenceWithCaps(const JID& jid, boost::shared_ptr<CapsInfo> caps) { +			boost::shared_ptr<Presence> presence(new Presence()); +			presence->setFrom(jid); +			presence->addPayload(caps); +			stanzaChannel->onPresenceReceived(presence); +		} + +		void sendUnavailablePresence(const JID& jid) { +			boost::shared_ptr<Presence> presence(new Presence()); +			presence->setFrom(jid); +			presence->setType(Presence::Unavailable); +			stanzaChannel->onPresenceReceived(presence); +		} +	 +	private: +		struct DummyCapsProvider : public CapsProvider { +			virtual DiscoInfo::ref getCaps(const String& hash) const { +				std::map<String, DiscoInfo::ref>::const_iterator i = caps.find(hash); +				if (i != caps.end()) { +					return i->second; +				} +				return DiscoInfo::ref(); +			} + +			std::map<String, DiscoInfo::ref> caps; +		}; + +	private: +		DummyStanzaChannel* stanzaChannel; +		DummyCapsProvider* capsProvider; +		JID user1; +		boost::shared_ptr<DiscoInfo> discoInfo1; +		boost::shared_ptr<CapsInfo> capsInfo1; +		boost::shared_ptr<CapsInfo> capsInfo1alt; +		JID user2; +		boost::shared_ptr<DiscoInfo> discoInfo2; +		boost::shared_ptr<CapsInfo> capsInfo2; +		boost::shared_ptr<CapsInfo> legacyCapsInfo; +		JID user3; +		std::vector<JID> changes; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(EntityCapsManagerTest); diff --git a/Swiften/Elements/CapsInfo.h b/Swiften/Elements/CapsInfo.h index 6fde13b..0fce90c 100644 --- a/Swiften/Elements/CapsInfo.h +++ b/Swiften/Elements/CapsInfo.h @@ -4,20 +4,49 @@   * See Documentation/Licenses/GPLv3.txt for more information.   */ -#ifndef SWIFTEN_CapsInfo_H -#define SWIFTEN_CapsInfo_H +#pragma once  #include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h"  #include "Swiften/Elements/Payload.h"  namespace Swift { -	class CapsInfo : public Payload { +	class CapsInfo : public Payload, public Shared<CapsInfo> {  		public: -			CapsInfo(const String& node, const String& version, const String& hash = "sha-1") : node_(node), version_(version), hash_(hash) {} +			CapsInfo(const String& node = "", const String& version = "", const String& hash = "sha-1") : node_(node), version_(version), hash_(hash) {} + +			bool operator==(const CapsInfo& o) const { +				return o.node_ == node_ && o.version_ == version_ && o.hash_ == hash_; +			} + +			bool operator<(const CapsInfo& o) const { +				if (o.node_ == node_) { +					if (o.version_ == version_) { +						return hash_ < o.hash_; +					} +					else { +						return version_ < o.version_; +					} +				} +				else { +					return node_ < o.node_; +				} +			}  			const String& getNode() const { return node_; } +			void setNode(const String& node) { +				node_ = node; +			} +  			const String& getVersion() const { return version_; } +			void setVersion(const String& version) { +				version_ = version; +			} +  			const String& getHash() const { return hash_; } +			void setHash(const String& hash) { +				hash_ = hash; +			}  		private:  			String node_; @@ -25,5 +54,3 @@ namespace Swift {  			String hash_;  	};  } - -#endif diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h index af4a5dc..cee9200 100644 --- a/Swiften/Elements/DiscoInfo.h +++ b/Swiften/Elements/DiscoInfo.h @@ -12,9 +12,10 @@  #include "Swiften/Elements/Payload.h"  #include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h"  namespace Swift { -	class DiscoInfo : public Payload { +	class DiscoInfo : public Payload, public Shared<DiscoInfo> {  		public:  			const static std::string SecurityLabels;  			class Identity { diff --git a/Swiften/Elements/Presence.h b/Swiften/Elements/Presence.h index 0789b18..7297339 100644 --- a/Swiften/Elements/Presence.h +++ b/Swiften/Elements/Presence.h @@ -62,6 +62,10 @@ namespace Swift {  				return boost::shared_ptr<Presence>(new Presence(*this));  			} +			bool isAvailable() const { +				return type_ == Available; +			} +  		private:  			Presence::Type type_;  	}; diff --git a/Swiften/Parser/PayloadParsers/CapsInfoParser.cpp b/Swiften/Parser/PayloadParsers/CapsInfoParser.cpp new file mode 100644 index 0000000..49c271c --- /dev/null +++ b/Swiften/Parser/PayloadParsers/CapsInfoParser.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Parser/PayloadParsers/CapsInfoParser.h" + +#include <locale> + +#include <boost/date_time/time_facet.hpp> + +namespace Swift { + +CapsInfoParser::CapsInfoParser() : level(0) { +} + +void CapsInfoParser::handleStartElement(const String&, const String& /*ns*/, const AttributeMap& attributes) { +	if (level == 0) { +		getPayloadInternal()->setHash(attributes.getAttribute("hash")); +		getPayloadInternal()->setNode(attributes.getAttribute("node")); +		getPayloadInternal()->setVersion(attributes.getAttribute("ver")); +	} +	++level; +} + +void CapsInfoParser::handleEndElement(const String&, const String&) { +	--level; +} + +void CapsInfoParser::handleCharacterData(const String&) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/CapsInfoParser.h b/Swiften/Parser/PayloadParsers/CapsInfoParser.h new file mode 100644 index 0000000..6058837 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/CapsInfoParser.h @@ -0,0 +1,24 @@ +/* + * 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/Parser/GenericPayloadParser.h" + +namespace Swift { +	class CapsInfoParser : public GenericPayloadParser<CapsInfo> { +		public: +			CapsInfoParser(); + +			virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); +			virtual void handleEndElement(const String& element, const String&); +			virtual void handleCharacterData(const String& data); + +		private: +			int level; +	}; +} diff --git a/Swiften/Parser/PayloadParsers/CapsInfoParserFactory.h b/Swiften/Parser/PayloadParsers/CapsInfoParserFactory.h new file mode 100644 index 0000000..6910ff8 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/CapsInfoParserFactory.h @@ -0,0 +1,17 @@ +/* + * 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/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/CapsInfoParser.h" + +namespace Swift { +	class CapsInfoParserFactory : public GenericPayloadParserFactory<CapsInfoParser> { +		public: +			CapsInfoParserFactory() : GenericPayloadParserFactory<CapsInfoParser>("c", "http://jabber.org/protocol/caps") {} +	}; +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index ce6c9f8..6294403 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -22,6 +22,7 @@  #include "Swiften/Parser/PayloadParsers/StorageParserFactory.h"  #include "Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h"  #include "Swiften/Parser/PayloadParsers/DiscoItemsParserFactory.h" +#include "Swiften/Parser/PayloadParsers/CapsInfoParserFactory.h"  #include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h"  #include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h"  #include "Swiften/Parser/PayloadParsers/FormParserFactory.h" @@ -49,6 +50,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {  	factories_.push_back(shared_ptr<PayloadParserFactory>(new RosterParserFactory()));  	factories_.push_back(shared_ptr<PayloadParserFactory>(new DiscoInfoParserFactory()));  	factories_.push_back(shared_ptr<PayloadParserFactory>(new DiscoItemsParserFactory())); +	factories_.push_back(shared_ptr<PayloadParserFactory>(new CapsInfoParserFactory()));  	factories_.push_back(shared_ptr<PayloadParserFactory>(new ResourceBindParserFactory()));  	factories_.push_back(shared_ptr<PayloadParserFactory>(new StartSessionParserFactory()));  	factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelParserFactory())); diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 4ab70a4..b9119d0 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -21,6 +21,7 @@ sources = [  		"PayloadParsers/BodyParser.cpp",  		"PayloadParsers/SubjectParser.cpp",  		"PayloadParsers/ChatStateParser.cpp", +		"PayloadParsers/CapsInfoParser.cpp",  		"PayloadParsers/DiscoInfoParser.cpp",  		"PayloadParsers/DiscoItemsParser.cpp",  		"PayloadParsers/ErrorParser.cpp", diff --git a/Swiften/Queries/GenericRequest.h b/Swiften/Queries/GenericRequest.h index be6a88d..72bbcbd 100644 --- a/Swiften/Queries/GenericRequest.h +++ b/Swiften/Queries/GenericRequest.h @@ -26,6 +26,11 @@ namespace Swift {  				onResponse(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(payload), error);  			} +		protected: +			boost::shared_ptr<PAYLOAD_TYPE> getPayloadGeneric() const { +				return boost::dynamic_pointer_cast<PAYLOAD_TYPE>(getPayload()); +			} +  		public:  			boost::signal<void (boost::shared_ptr<PAYLOAD_TYPE>, const boost::optional<ErrorPayload>&)> onResponse;  	}; diff --git a/Swiften/Queries/Request.h b/Swiften/Queries/Request.h index d0fd8c6..450e311 100644 --- a/Swiften/Queries/Request.h +++ b/Swiften/Queries/Request.h @@ -38,6 +38,10 @@ namespace Swift {  				payload_ = p;  			} +			boost::shared_ptr<Payload> getPayload() const { +				return payload_; +			} +  			virtual void handleResponse(boost::shared_ptr<Payload>, boost::optional<ErrorPayload>) = 0;  		private: diff --git a/Swiften/Queries/Requests/GetDiscoInfoRequest.h b/Swiften/Queries/Requests/GetDiscoInfoRequest.h index fd4d9f6..9ec1050 100644 --- a/Swiften/Queries/Requests/GetDiscoInfoRequest.h +++ b/Swiften/Queries/Requests/GetDiscoInfoRequest.h @@ -15,5 +15,10 @@ namespace Swift {  			GetDiscoInfoRequest(const JID& jid, IQRouter* router) :  					GenericRequest<DiscoInfo>(IQ::Get, jid, boost::shared_ptr<DiscoInfo>(new DiscoInfo()), router) {  			} + +			GetDiscoInfoRequest(const JID& jid, const String& node, IQRouter* router) : +					GenericRequest<DiscoInfo>(IQ::Get, jid, boost::shared_ptr<DiscoInfo>(new DiscoInfo()), router) { +				getPayloadGeneric()->setNode(node); +			}  	};  } diff --git a/Swiften/SConscript b/Swiften/SConscript index 3d82cfa..a9e3a01 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -36,7 +36,6 @@ if env["SCONS_STAGE"] == "build" :  			"Compress/ZLibCodecompressor.cpp",  			"Compress/ZLibDecompressor.cpp",  			"Compress/ZLibCompressor.cpp", -			"Disco/CapsInfoGenerator.cpp",  			"Elements/DiscoInfo.cpp",  			"Elements/Element.cpp",  			"Elements/IQ.cpp", @@ -125,6 +124,7 @@ if env["SCONS_STAGE"] == "build" :  			"EventLoop",  			"Parser",  			"JID", +			"Disco",  			"VCards",  			"Network",  			"History", @@ -153,6 +153,8 @@ if env["SCONS_STAGE"] == "build" :  			File("Compress/UnitTest/ZLibCompressorTest.cpp"),  			File("Compress/UnitTest/ZLibDecompressorTest.cpp"),  			File("Disco/UnitTest/CapsInfoGeneratorTest.cpp"), +			File("Disco/UnitTest/CapsManagerTest.cpp"), +			File("Disco/UnitTest/EntityCapsManagerTest.cpp"),  			File("Elements/UnitTest/IQTest.cpp"),  			File("Elements/UnitTest/StanzaTest.cpp"),  			File("Elements/UnitTest/StanzasTest.cpp"), diff --git a/Swiften/VCards/VCardFileStorage.cpp b/Swiften/VCards/VCardFileStorage.cpp index 66bae04..b4536f9 100644 --- a/Swiften/VCards/VCardFileStorage.cpp +++ b/Swiften/VCards/VCardFileStorage.cpp @@ -39,7 +39,12 @@ boost::shared_ptr<VCard> VCardFileStorage::getVCard(const JID& jid) const {  void VCardFileStorage::setVCard(const JID& jid, boost::shared_ptr<VCard> v) {  	boost::filesystem::path vcardPath(getVCardPath(jid));  	if (!boost::filesystem::exists(vcardPath.parent_path())) { -		boost::filesystem::create_directories(vcardPath.parent_path()); +		try { +			boost::filesystem::create_directories(vcardPath.parent_path()); +		} +		catch (const boost::filesystem::filesystem_error& e) { +			std::cerr << "ERROR: " << e.what() << std::endl; +		}  	}  	boost::filesystem::ofstream file(getVCardPath(jid));  	file << VCardSerializer().serializePayload(v); | 
 Swift
 Swift