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