From ef1052bbdb315aaa1c6254098ea05638d9a25b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remko=20Tron=C3=A7on?= Date: Sun, 12 Sep 2010 20:29:39 +0200 Subject: Added presence notifier. diff --git a/SwifTools/Notifier/GrowlNotifier.cpp b/SwifTools/Notifier/GrowlNotifier.cpp index 4c671ac..7ea7193 100644 --- a/SwifTools/Notifier/GrowlNotifier.cpp +++ b/SwifTools/Notifier/GrowlNotifier.cpp @@ -17,22 +17,30 @@ namespace { struct Context { Context() {} - Context(const boost::function& callback) : callback(callback) {} + Context(const boost::function& callback) : callback(new boost::function(callback)) {} - boost::function callback; + boost::function* 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 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 callback) : type(type), subject(subject), description(description), picture(picture), callback(callback) {} + Type type; + String subject; + String description; + ByteArray picture; + boost::function callback; + }; + + std::vector 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 presenc return ""; } -void ChatController::handlePresenceChange(boost::shared_ptr newPresence, boost::shared_ptr previousPresence) { +void ChatController::handlePresenceChange(boost::shared_ptr 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 newPresence, boost::shared_ptr previousPresence); + void handlePresenceChange(boost::shared_ptr newPresence); String getStatusChangeString(boost::shared_ptr presence); bool isIncomingMessageFromMe(boost::shared_ptr message); void postSendMessage(const String &body, boost::shared_ptr sentStanza); @@ -41,6 +41,7 @@ namespace Swift { ChatStateTracker* chatStateTracker_; bool isInMUC_; bool lastWasPresence_; + String lastStatusChangeString_; std::map, 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 event) { /** * If a resource goes offline, release bound chatdialog to that resource. */ -void ChatsManager::handlePresenceChange(boost::shared_ptr newPresence, boost::shared_ptr /*lastPresence*/) { +void ChatsManager::handlePresenceChange(boost::shared_ptr 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& nick); void rebindControllerJID(const JID& from, const JID& to); - void handlePresenceChange(boost::shared_ptr newPresence, boost::shared_ptr lastPresence); + void handlePresenceChange(boost::shared_ptr newPresence); void handleUIEvent(boost::shared_ptr 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(); - xmppRoster_ = boost::shared_ptr(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 jid1Offline(new Presence()); jid1Offline->setFrom(JID(fullJIDString1)); jid1Offline->setType(Presence::Unavailable); - presenceOracle_->onPresenceChange(jid1Offline, jid1Online); + presenceOracle_->onPresenceChange(jid1Offline); boost::shared_ptr message2(new Message()); message2->setFrom(JID(fullJIDString2)); @@ -273,14 +273,14 @@ public: boost::shared_ptr jid1Offline(new Presence()); jid1Offline->setFrom(JID(messageJID1)); jid1Offline->setType(Presence::Unavailable); - presenceOracle_->onPresenceChange(jid1Offline, jid1Online); + presenceOracle_->onPresenceChange(jid1Offline); boost::shared_ptr jid2Online(new Presence()); jid2Online->setFrom(JID(messageJID2)); boost::shared_ptr 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 serverDiscoInfo_; - boost::shared_ptr 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 #include -#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(); - xmppRoster_ = boost::shared_ptr(); + 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(new DiscoInfo()); - xmppRoster_ = boost::shared_ptr(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(new RequestChatUIEvent(jid))); +} + VCardStorage* MainController::getVCardStorageForProfile(const JID& jid) { String profile = jid.toBare().toString(); std::pair r = vcardStorages_.insert(std::make_pair(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_; boost::shared_ptr serverDiscoInfo_; - boost::shared_ptr 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, 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, 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 map_; - boost::shared_ptr 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 + +#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) { + 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::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 +#include + +#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 onNotificationActivated; + + private: + void handlePresenceReceived(boost::shared_ptr); + 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; + bool justInitialized; + bool inQuietPeriod; + std::set 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, 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_->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 event) { boost::shared_ptr 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 removeEvent = boost::dynamic_pointer_cast(event); @@ -185,13 +187,13 @@ void RosterController::handleRosterSetError(boost::optional error, eventController_->handleIncomingEvent(errorEvent); } -void RosterController::handleIncomingPresence(boost::shared_ptr newPresence, boost::shared_ptr /*oldPresence*/) { +void RosterController::handleIncomingPresence(boost::shared_ptr 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, 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 newPresence, boost::shared_ptr oldPresence); + void handleIncomingPresence(boost::shared_ptr 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 error, boost::shared_ptr rosterPayload); void handleOwnNickChanged(const String& nick); JID myJID_; - boost::shared_ptr 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(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 groups_; - boost::shared_ptr 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 +#include +#include +#include + +#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 testling = createNotifier(); + + sendPresence(user1, StatusShow::Online); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT_EQUAL(Notifier::ContactAvailable, notifier->notifications[0].type); + } + + void testReceiveSecondPresenceCreatesStatusChangeNotification() { + std::auto_ptr testling = createNotifier(); + sendPresence(user1, StatusShow::Away); + notifier->notifications.clear(); + + sendPresence(user1, StatusShow::Online); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT_EQUAL(Notifier::ContactStatusChange, notifier->notifications[0].type); + } + + void testReceiveUnavailablePresenceAfterAvailablePresenceCreatesUnavailableNotification() { + std::auto_ptr testling = createNotifier(); + sendPresence(user1, StatusShow::Away); + notifier->notifications.clear(); + + sendUnavailablePresence(user1); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT_EQUAL(Notifier::ContactUnavailable, notifier->notifications[0].type); + } + + void testReceiveUnavailablePresenceWithoutAvailableDoesNotCreateNotification() { + std::auto_ptr testling = createNotifier(); + + sendUnavailablePresence(user1); + + CPPUNIT_ASSERT_EQUAL(0, static_cast(notifier->notifications.size())); + } + + void testReceiveAvailablePresenceAfterUnavailableCreatesAvailableNotification() { + std::auto_ptr testling = createNotifier(); + sendPresence(user1, StatusShow::Away); + sendUnavailablePresence(user1); + notifier->notifications.clear(); + + sendPresence(user1, StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT_EQUAL(Notifier::ContactAvailable, notifier->notifications[0].type); + } + + void testReceiveAvailablePresenceAfterReconnectCreatesAvailableNotification() { + std::auto_ptr 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(notifier->notifications.size())); + CPPUNIT_ASSERT_EQUAL(Notifier::ContactAvailable, notifier->notifications[0].type); + } + + void testReceiveAvailablePresenceFromMUCDoesNotCreateNotification() { + std::auto_ptr testling = createNotifier(); + mucRegistry->addMUC(JID("teaparty@wonderland.lit")); + + sendPresence(JID("teaparty@wonderland.lit/Alice"), StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(0, static_cast(notifier->notifications.size())); + } + + void testNotificationPicture() { + std::auto_ptr testling = createNotifier(); + avatarManager->avatars[user1] = ByteArray("abcdef"); + + sendPresence(user1, StatusShow::Online); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT_EQUAL(ByteArray("abcdef"), notifier->notifications[0].picture); + } + + void testNotificationActivationEmitsSignal() { + std::auto_ptr testling = createNotifier(); + + sendPresence(user1, StatusShow::Online); + CPPUNIT_ASSERT(notifier->notifications[0].callback); + notifier->notifications[0].callback(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(activatedNotifications.size())); + CPPUNIT_ASSERT_EQUAL(user1, activatedNotifications[0]); + } + + void testNotificationSubjectContainsNameForJIDInRoster() { + std::auto_ptr testling = createNotifier(); + roster->addContact(user1.toBare(), "User 1", std::vector(), RosterItemPayload::Both); + + sendPresence(user1, StatusShow::Online); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT(notifier->notifications[0].subject.contains("User 1")); + } + + void testNotificationSubjectContainsJIDForJIDNotInRoster() { + std::auto_ptr testling = createNotifier(); + + sendPresence(user1, StatusShow::Online); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT(notifier->notifications[0].subject.contains(user1.toBare().toString())); + } + + void testNotificationSubjectContainsStatus() { + std::auto_ptr testling = createNotifier(); + + sendPresence(user1, StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT(notifier->notifications[0].subject.contains("Away")); + } + + void testNotificationMessageContainsStatusMessage() { + std::auto_ptr testling = createNotifier(); + + sendPresence(user1, StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + CPPUNIT_ASSERT(notifier->notifications[0].description.contains("Status Message")); + } + + void testReceiveFirstPresenceWithQuietPeriodDoesNotNotify() { + std::auto_ptr testling = createNotifier(); + testling->setInitialQuietPeriodMS(10); + + sendPresence(user1, StatusShow::Online); + + CPPUNIT_ASSERT_EQUAL(0, static_cast(notifier->notifications.size())); + } + + void testReceivePresenceDuringQuietPeriodDoesNotNotify() { + std::auto_ptr testling = createNotifier(); + testling->setInitialQuietPeriodMS(10); + + sendPresence(user1, StatusShow::Online); + timerFactory->setTime(1); + sendPresence(user2, StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(0, static_cast(notifier->notifications.size())); + } + + void testReceivePresenceDuringQuietPeriodResetsTimer() { + std::auto_ptr 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(notifier->notifications.size())); + } + + void testReceivePresenceAfterQuietPeriodNotifies() { + std::auto_ptr testling = createNotifier(); + testling->setInitialQuietPeriodMS(10); + + sendPresence(user1, StatusShow::Online); + timerFactory->setTime(11); + sendPresence(user2, StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(1, static_cast(notifier->notifications.size())); + } + + void testReceiveFirstPresenceWithQuietPeriodDoesNotCountAsQuietPeriod() { + std::auto_ptr testling = createNotifier(); + testling->setInitialQuietPeriodMS(10); + + timerFactory->setTime(11); + sendPresence(user1, StatusShow::Away); + + CPPUNIT_ASSERT_EQUAL(0, static_cast(notifier->notifications.size())); + } + + void testReceiveFirstPresenceAfterReconnectWithQuietPeriodDoesNotNotify() { + std::auto_ptr 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(notifier->notifications.size())); + } + + + private: + std::auto_ptr createNotifier() { + std::auto_ptr 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(new Presence()); + presence->setFrom(jid); + presence->setShow(type); + presence->setStatus("Status Message"); + stanzaChannel->onPresenceReceived(presence); + } + + void sendUnavailablePresence(const JID& jid) { + boost::shared_ptr 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 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(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_; 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(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_; }; 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) : 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); + XMPPRosterController(IQRouter *iqRouter, XMPPRoster* xmppRoster); void requestRoster(); - boost::shared_ptr getXMPPRoster() {return xmppRoster_;}; - void handleRosterReceived(boost::shared_ptr rosterPayload); private: IQRouter* iqRouter_; RosterPushResponder rosterPushResponder_; - boost::shared_ptr 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 #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 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 + +#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::const_iterator i = avatars.find(jid); + if (i != avatars.end()) { + return i->second; + } + else { + return ByteArray(); + } + } + + std::map 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) } } -void ChatStateTracker::handlePresenceChange(boost::shared_ptr newPresence, boost::shared_ptr) { +void ChatStateTracker::handlePresenceChange(boost::shared_ptr 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); - void handlePresenceChange(boost::shared_ptr newPresence, boost::shared_ptr oldPresence); + void handlePresenceChange(boost::shared_ptr newPresence); boost::signal 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 DummyTimerFactory::createTimer(int milliseconds) { - boost::shared_ptr timer(new DummyTimer(milliseconds)); + boost::shared_ptr timer(new DummyTimer(milliseconds, this)); timers.push_back(timer); return timer; } -static bool hasZeroTimeout(boost::shared_ptr timer) { - return timer->timeout == 0; -} - void DummyTimerFactory::setTime(int time) { assert(time > currentTime); - int increment = time - currentTime; - std::vector< boost::shared_ptr > notifyTimers(timers.begin(), timers.end()); - foreach(boost::shared_ptr timer, notifyTimers) { - if (increment >= timer->timeout) { - if (timer->isRunning) { - timer->onTick(); - } - timer->timeout = 0; - } - else { - timer->timeout -= increment; + foreach(boost::shared_ptr 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 + #include "Swiften/Client/StanzaChannel.h" -namespace Swift { -typedef std::pair > > JIDMapPair; -typedef std::pair > 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 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 stanza(new Presence()); - stanza->setType(Presence::Subscribed); - stanza->setTo(jid); - stanzaChannel_->sendPresence(stanza); -} - - -void PresenceOracle::requestSubscription(const JID& jid) { - boost::shared_ptr 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 if (presence->getType() == Presence::Subscribe) { onPresenceSubscriptionRequest(bareJID, presence->getStatus()); - } else { + } + else { std::map > jidMap = entries_[bareJID]; - boost::shared_ptr 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 + #include "Swiften/Base/String.h" #include "Swiften/Elements/Presence.h" -#include #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, boost::shared_ptr)> onPresenceChange; - boost::signal onPresenceSubscriptionRequest; - - private: - void handleIncomingPresence(boost::shared_ptr presence); - std::map > > entries_; - StanzaChannel* stanzaChannel_; -}; + class PresenceOracle { + public: + PresenceOracle(StanzaChannel* stanzaChannel); + ~PresenceOracle(); + + Presence::ref getLastPresence(const JID&) const; + + public: + boost::signal)> onPresenceChange; + boost::signal onPresenceSubscriptionRequest; + + private: + void handleIncomingPresence(boost::shared_ptr presence); + void handleStanzaChannelAvailableChanged(bool); + + private: + typedef std::map PresenceMap; + typedef std::map 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 stanza(new Presence()); + stanza->setType(Presence::Unsubscribed); + stanza->setTo(jid); + channel->sendPresence(stanza); +} + +void PresenceSender::confirmSubscription(const JID& jid) { + boost::shared_ptr stanza(new Presence()); + stanza->setType(Presence::Subscribed); + stanza->setTo(jid); + channel->sendPresence(stanza); +} + + +void PresenceSender::requestSubscription(const JID& jid) { + boost::shared_ptr 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); + void cancelSubscription(const JID& jid); + void confirmSubscription(const JID& jid); + void requestSubscription(const JID& jid); + private: boost::shared_ptr 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 #include #include -#include #include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Client/DummyStanzaChannel.h" using namespace Swift; -class PresencePointerPair { - public: - boost::shared_ptr one; - boost::shared_ptr two; -}; - -class SubscriptionRequestInfo { - public: - boost::optional jid; - boost::optional 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 newPresence, boost::shared_ptr 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 sentPresence(new Presence("blarb")); + void testReceivePresence() { + boost::shared_ptr 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(changes.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast(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 sentPresence1(new Presence("blarb")); + void testReceivePresenceFromDifferentResources() { + boost::shared_ptr sentPresence1(createPresence(user1)); + boost::shared_ptr sentPresence2(createPresence(user1alt)); stanzaChannel_->onPresenceReceived(sentPresence1); - CPPUNIT_ASSERT_EQUAL(sentPresence1, out.one); - out.one = boost::shared_ptr(); - - SubscriptionRequestInfo info; - oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info)); - - boost::shared_ptr 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(changes.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast(subscriptionRequests.size())); + CPPUNIT_ASSERT_EQUAL(sentJID, subscriptionRequests[0].jid); + CPPUNIT_ASSERT_EQUAL(reasonText, subscriptionRequests[0].reason); + } + + void testReconnectResetsPresences() { + boost::shared_ptr 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 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 createPresence(const JID& jid) { + boost::shared_ptr sentPresence(new Presence("blarb")); + sentPresence->setFrom(jid); + return sentPresence; + } + + private: + struct SubscriptionRequestInfo { + JID jid; + String reason; + }; + PresenceOracle* oracle_; + DummyStanzaChannel* stanzaChannel_; + std::vector changes; + std::vector 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& 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::const_iterator i = entries_.find(jid.toBare()); + if (i != entries_.end()) { + return i->second.name; + } + else { + return ""; + } } const std::vector& 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 groups; - RosterItemPayload::Subscription subscription; -}; - -class XMPPRoster { - public: - XMPPRoster() {}; - ~XMPPRoster() {}; - - void addContact(const JID& jid, const String& name, const std::vector& 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& getGroupsForJID(const JID& jid); - - boost::signal onJIDAdded; - boost::signal onJIDRemoved; - boost::signal&)> onJIDUpdated; - boost::signal onRosterCleared; - - private: - //std::map > > entries_; - std::map entries_; -}; + class XMPPRoster { + public: + XMPPRoster(); + + void addContact(const JID& jid, const String& name, const std::vector& 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& getGroupsForJID(const JID& jid); + + boost::signal onJIDAdded; + boost::signal onJIDRemoved; + boost::signal&)> onJIDUpdated; + boost::signal onRosterCleared; + + private: + struct XMPPRosterItem { + JID jid; + String name; + std::vector groups; + RosterItemPayload::Subscription subscription; + }; + std::map entries_; + }; } - -#endif - -- cgit v0.10.2-6-g49f6