summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2011-11-11 15:01:32 (GMT)
committerKevin Smith <git@kismith.co.uk>2011-11-28 16:44:22 (GMT)
commit86aad702d1f2e831c8e27bbe4ca1402626e4c542 (patch)
tree3be5a8ed23aef3877c9b313d0ee0f58afb54f57a /Swift/Controllers
parent81a7776d5ab523894a7c4745baee3988ad9f1ef9 (diff)
downloadswift-contrib-86aad702d1f2e831c8e27bbe4ca1402626e4c542.zip
swift-contrib-86aad702d1f2e831c8e27bbe4ca1402626e4c542.tar.bz2
Message Receipts (XEP-0184) support for 1-to-1 conversations (including 1-to-1 MUC).
Warn icon from already existing theme. Check icon from Wikipedia. See Swift/resources/icons/license_info.txt for details. License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php
Diffstat (limited to 'Swift/Controllers')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp66
-rw-r--r--Swift/Controllers/Chat/ChatController.h13
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h2
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp57
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h11
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp107
-rw-r--r--Swift/Controllers/MainController.cpp13
-rw-r--r--Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h19
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h4
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h2
10 files changed, 282 insertions, 12 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index a3d9fb5..9a56300 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -16,7 +16,6 @@
#include <Swiften/Chat/ChatStateNotifier.h>
#include <Swiften/Chat/ChatStateTracker.h>
#include <Swiften/Client/StanzaChannel.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swiften/Client/NickResolver.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
@@ -26,15 +25,19 @@
#include <Swiften/Base/foreach.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h>
+#include <Swiften/Base/Log.h>
namespace Swift {
/**
* The controller does not gain ownership of the stanzaChannel, nor the factory.
*/
-ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider)
- : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream) {
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts)
+ : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts) {
isInMUC_ = isInMUC;
lastWasPresence_ = false;
chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -70,7 +73,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
handleBareJIDCapsChanged(toJID_);
-
+ eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1));
}
void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) {
@@ -80,6 +83,7 @@ void ChatController::handleContactNickChanged(const JID& jid, const std::string&
}
ChatController::~ChatController() {
+ eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1));
nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
delete chatStateNotifier_;
delete chatStateTracker_;
@@ -93,9 +97,17 @@ void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
} else {
chatWindow_->setCorrectionEnabled(ChatWindow::No);
}
+ if (disco->hasFeature(DiscoInfo::MessageDeliveryReceiptsFeature)) {
+ contactSupportsReceipts_ = ChatWindow::Yes;
+ } else {
+ contactSupportsReceipts_ = ChatWindow::No;
+ }
} else {
+ SWIFT_LOG(debug) << "No disco info :(" << std::endl;
chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
+ contactSupportsReceipts_ = ChatWindow::Maybe;
}
+ checkForDisplayingDisplayReceiptsAlert();
}
void ChatController::setToJID(const JID& jid) {
@@ -129,6 +141,21 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
}
chatStateTracker_->handleMessageReceived(message);
chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
+
+ if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) {
+ SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl;
+ if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) {
+ chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived);
+ requestedReceipts_.erase(receipt->getReceivedID());
+ }
+ } else if (message->getPayload<DeliveryReceiptRequest>()) {
+ if (receivingPresenceFromUs_) {
+ boost::shared_ptr<Message> receiptMessage = boost::make_shared<Message>();
+ receiptMessage->setTo(toJID_);
+ receiptMessage->addPayload(boost::make_shared<DeliveryReceipt>(message->getID()));
+ stanzaChannel_->sendMessage(receiptMessage);
+ }
+ }
}
void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
@@ -138,6 +165,30 @@ void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> m
void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
chatStateNotifier_->addChatStateRequest(message);
+ if (userWantsReceipts_ && (contactSupportsReceipts_ != ChatWindow::No) && message) {
+ message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
+ }
+}
+
+void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) {
+ receivingPresenceFromUs_ = isReceivingPresence;
+}
+
+void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+ if (boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> toggleAllowReceipts = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event)) {
+ userWantsReceipts_ = toggleAllowReceipts->getEnabled();
+ checkForDisplayingDisplayReceiptsAlert();
+ }
+}
+
+void ChatController::checkForDisplayingDisplayReceiptsAlert() {
+ if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) {
+ chatWindow_->setAlert("This chat doesn't support delivery receipts.");
+ } else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) {
+ chatWindow_->setAlert("This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.");
+ } else {
+ chatWindow_->cancelAlert();
+ }
}
void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
@@ -148,10 +199,17 @@ void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<
} else {
myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
}
+
if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
unackedStanzas_[sentStanza] = myLastMessageUIID_;
}
+
+ if (sentStanza->getPayload<DeliveryReceiptRequest>()) {
+ requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_;
+ chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested);
+ }
+
lastWasPresence_ = false;
chatStateNotifier_->userSentMessage();
}
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 2531adb..9c01923 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -11,6 +11,8 @@
#include <map>
#include <string>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+
namespace Swift {
class AvatarManager;
class ChatStateNotifier;
@@ -18,14 +20,16 @@ namespace Swift {
class NickResolver;
class EntityCapsProvider;
class FileTransferController;
+ class UIEvent;
class ChatController : public ChatControllerBase {
public:
- ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider);
+ ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts);
virtual ~ChatController();
virtual void setToJID(const JID& jid);
virtual void setOnline(bool online);
virtual void handleNewFileTransferController(FileTransferController* ftc);
+ virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/);
private:
void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
@@ -47,6 +51,9 @@ namespace Swift {
void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
void handleSendFileRequest(std::string filename);
+ void handleUIEvent(boost::shared_ptr<UIEvent> event);
+ void checkForDisplayingDisplayReceiptsAlert();
+
private:
NickResolver* nickResolver_;
ChatStateNotifier* chatStateNotifier_;
@@ -56,9 +63,13 @@ namespace Swift {
bool lastWasPresence_;
std::string lastStatusChangeString_;
std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_;
+ std::map<std::string, std::string> requestedReceipts_;
StatusShow::Type lastShownStatus_;
UIEventStream* eventStream_;
+ ChatWindow::Tristate contactSupportsReceipts_;
+ bool receivingPresenceFromUs_;
+ bool userWantsReceipts_;
std::map<std::string, FileTransferController*> ftControllers;
};
}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index a857f3d..f1ecfe8 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -54,6 +54,7 @@ namespace Swift {
int getUnreadCount();
const JID& getToJID() {return toJID_;}
void handleCapsChanged(const JID& jid);
+
protected:
ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider);
@@ -71,6 +72,7 @@ namespace Swift {
virtual void dayTicked() {};
virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
+ virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {}
private:
IDGenerator idGenerator_;
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index e648f20..8111c2b 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -21,6 +21,7 @@
#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h>
#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
@@ -28,12 +29,15 @@
#include <Swiften/Client/NickResolver.h>
#include <Swiften/MUC/MUCManager.h>
#include <Swiften/Elements/ChatState.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/MUC/MUCBookmarkManager.h>
#include <Swift/Controllers/FileTransfer/FileTransferController.h>
#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
#include <Swift/Controllers/ProfileSettingsProvider.h>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swiften/Roster/XMPPRoster.h>
namespace Swift {
@@ -61,6 +65,7 @@ ChatsManager::ChatsManager(
MUCSearchWindowFactory* mucSearchWindowFactory,
ProfileSettingsProvider* settings,
FileTransferOverview* ftOverview,
+ XMPPRoster* roster,
bool eagleMode) :
jid_(jid),
joinMUCWindowFactory_(joinMUCWindowFactory),
@@ -69,6 +74,7 @@ ChatsManager::ChatsManager(
entityCapsProvider_(entityCapsProvider),
mucManager(mucManager),
ftOverview_(ftOverview),
+ roster_(roster),
eagleMode_(eagleMode) {
timerFactory_ = timerFactory;
eventController_ = eventController;
@@ -95,11 +101,19 @@ ChatsManager::ChatsManager(
mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings);
mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
+ roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
+ roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
+ roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
+ roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this));
setupBookmarks();
loadRecents();
}
ChatsManager::~ChatsManager() {
+ roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
+ roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
+ roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
+ roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this));
delete joinMUCWindow_;
foreach (JIDChatControllerPair controllerPair, chatControllers_) {
delete controllerPair.second;
@@ -136,6 +150,39 @@ void ChatsManager::handleClearRecentsRequested() {
handleUnreadCountChanged(NULL);
}
+void ChatsManager::handleJIDAddedToRoster(const JID &jid) {
+ updatePresenceReceivingStateOnChatController(jid);
+}
+
+void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) {
+ updatePresenceReceivingStateOnChatController(jid);
+}
+
+void ChatsManager::handleJIDUpdatedInRoster(const JID &jid) {
+ updatePresenceReceivingStateOnChatController(jid);
+}
+
+void ChatsManager::handleRosterCleared() {
+ /* Setting that all chat controllers aren't receiving presence anymore;
+ including MUC 1-to-1 chats due to the assumtion that this handler
+ is only called on log out. */
+ foreach(JIDChatControllerPair pair, chatControllers_) {
+ pair.second->setContactIsReceivingPresence(false);
+ }
+}
+
+void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) {
+ ChatController* controller = getChatControllerIfExists(jid);
+ if (controller) {
+ if (!mucRegistry_->isMUC(jid.toBare())) {
+ RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid);
+ controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both);
+ } else {
+ controller->setContactIsReceivingPresence(true);
+ }
+ }
+}
+
void ChatsManager::loadRecents() {
std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
std::vector<std::string> recents;
@@ -316,6 +363,11 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark());
return;
}
+ boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> toggleRequestDeliveryReceipsEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event);
+ if (toggleRequestDeliveryReceipsEvent) {
+ userWantsReceipts_ = toggleRequestDeliveryReceipsEvent->getEnabled();
+ return;
+ }
boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event);
if (editMUCBookmarkEvent) {
@@ -436,11 +488,12 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)
ChatController* ChatsManager::createNewChatController(const JID& contact) {
assert(chatControllers_.find(contact) == chatControllers_.end());
- ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_);
+ ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_);
chatControllers_[contact] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+ updatePresenceReceivingStateOnChatController(contact);
return controller;
}
@@ -522,7 +575,7 @@ void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
JID jid = message->getFrom();
boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
bool isInvite = message->getPayload<MUCInvitationPayload>();
- if (!event->isReadable() && !message->getPayload<ChatState>() && !isInvite && !message->hasSubject()) {
+ if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !message->hasSubject()) {
return;
}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 5d8d555..0c7f492 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -45,10 +45,11 @@ namespace Swift {
class MUCSearchController;
class FileTransferOverview;
class FileTransferController;
+ class XMPPRoster;
class ChatsManager {
public:
- ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview, bool eagleMode);
+ ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode);
virtual ~ChatsManager();
void setAvatarManager(AvatarManager* avatarManager);
void setOnline(bool enabled);
@@ -81,6 +82,12 @@ namespace Swift {
void handleUnreadCountChanged(ChatControllerBase* controller);
void handleAvatarChanged(const JID& jid);
void handleClearRecentsRequested();
+ void handleJIDAddedToRoster(const JID&);
+ void handleJIDRemovedFromRoster(const JID&);
+ void handleJIDUpdatedInRoster(const JID&);
+ void handleRosterCleared();
+
+ void updatePresenceReceivingStateOnChatController(const JID&);
ChatController* getChatControllerOrFindAnother(const JID &contact);
ChatController* createNewChatController(const JID &contact);
@@ -115,6 +122,8 @@ namespace Swift {
std::list<ChatListWindow::Chat> recentChats_;
ProfileSettingsProvider* profileSettings_;
FileTransferOverview* ftOverview_;
+ XMPPRoster* roster_;
bool eagleMode_;
+ bool userWantsReceipts_;
};
}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index df519e8..edb431a 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -43,8 +43,11 @@
#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h"
#include <Swift/Controllers/ProfileSettingsProvider.h>
#include "Swift/Controllers/FileTransfer/FileTransferOverview.h"
+#include "Swiften/Elements/DeliveryReceiptRequest.h"
+#include "Swiften/Elements/DeliveryReceipt.h"
#include <Swiften/Base/Algorithm.h>
using namespace Swift;
@@ -63,6 +66,10 @@ class ChatsManagerTest : public CppUnit::TestFixture {
CPPUNIT_TEST(testUnbindRebind);
CPPUNIT_TEST(testNoDuplicateUnbind);
CPPUNIT_TEST(testThreeMUCWindows);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth);
+ CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom);
CPPUNIT_TEST_SUITE_END();
public:
@@ -93,8 +100,9 @@ public:
chatListWindow_ = new MockChatListWindow();
ftManager_ = new DummyFileTransferManager();
ftOverview_ = new FileTransferOverview(ftManager_);
+
mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
- manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, false);
+ manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false);
avatarManager_ = new NullAvatarManager();
manager_->setAvatarManager(avatarManager_);
@@ -335,11 +343,104 @@ public:
manager_->handleIncomingMessage(message2b);
CPPUNIT_ASSERT_EQUAL(body2b, window1->lastMessageBody_);
}
-
+
+ /**
+ * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster.
+ */
+ void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() {
+ JID messageJID("testling@test.com/resource1");
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+ uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true)));
+
+ boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+ manager_->handleIncomingMessage(message);
+ Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
+ CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+ CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
+
+ xmppRoster_->removeContact(messageJID);
+
+ message->setID("2");
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+ }
+
+ /**
+ * Test that ChatController sends receipts after the contact has been added to the roster.
+ */
+ void testChatControllerPresenceAccessUpdatedOnAddToRoster() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+ uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true)));
+
+ boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL((size_t)0, stanzaChannel_->sentStanzas.size());
+
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
+ message->setID("2");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+ Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
+ CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
+ }
+
+ /**
+ * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both.
+ */
+ void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() {
+ testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both);
+ }
+
+ /**
+ * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From.
+ */
+ void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() {
+ testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From);
+ }
+
+ void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) {
+ JID messageJID("testling@test.com/resource1");
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from);
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+ uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true)));
+
+ boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL((size_t)0, stanzaChannel_->sentStanzas.size());
+
+ xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to);
+ message->setID("2");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+ Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
+ CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
+ }
+
+private:
+ boost::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
+ boost::shared_ptr<Message> message = boost::make_shared<Message>();
+ message->setFrom(from);
+ message->setID(id);
+ message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
+ return message;
+ }
+
private:
JID jid_;
ChatsManager* manager_;
- StanzaChannel* stanzaChannel_;
+ DummyStanzaChannel* stanzaChannel_;
IQChannel* iqChannel_;
IQRouter* iqRouter_;
EventController* eventController_;
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index ce347ab..d485abc 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -60,6 +60,7 @@
#include "Swiften/StringCodecs/Hexify.h"
#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
#include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h"
+#include "Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
#include "Swift/Controllers/Storages/CertificateStorageFactory.h"
#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h"
@@ -79,6 +80,7 @@ static const std::string CLIENT_NAME = "Swift";
static const std::string CLIENT_NODE = "http://swift.im";
static const std::string SHOW_NOTIFICATIONS = "showNotifications";
+static const std::string REQUEST_DELIVERYRECEIPTS = "requestDeliveryReceipts";
MainController::MainController(
EventLoop* eventLoop,
@@ -249,6 +251,11 @@ void MainController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
notifier_->setPersistentEnabled(enabled);
settings_->storeBool(SHOW_NOTIFICATIONS, enabled);
}
+ boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> deliveryReceiptEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event);
+ if (deliveryReceiptEvent) {
+ settings_->storeBool(REQUEST_DELIVERYRECEIPTS, deliveryReceiptEvent->getEnabled());
+ }
+
}
void MainController::resetPendingReconnects() {
@@ -291,7 +298,7 @@ void MainController::handleConnected() {
contactEditController_ = new ContactEditController(rosterController_, uiFactory_, uiEventStream_);
- chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, eagleMode_);
+ chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), eagleMode_);
client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
chatsManager_->setAvatarManager(client_->getAvatarManager());
@@ -311,6 +318,7 @@ void MainController::handleConnected() {
discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
#endif
+ discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
client_->getDiscoManager()->setDiscoInfo(discoInfo);
@@ -335,6 +343,9 @@ void MainController::handleConnected() {
sendPresence(statusTracker_->getNextPresence());
/* Enable chats last of all, so rejoining MUCs has the right sent presence */
chatsManager_->setOnline(true);
+
+ // notify world about current delivey receipt request setting state.
+ uiEventStream_->send(boost::make_shared<ToggleRequestDeliveryReceiptsUIEvent>(settings_->getBoolSetting(REQUEST_DELIVERYRECEIPTS, false)));
}
void MainController::handleEventQueueLengthChange(int count) {
diff --git a/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h b/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h
new file mode 100644
index 0000000..1ea2db5
--- /dev/null
+++ b/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIEvents/UIEvent.h"
+
+namespace Swift {
+ class ToggleRequestDeliveryReceiptsUIEvent : public UIEvent {
+ public:
+ ToggleRequestDeliveryReceiptsUIEvent(bool enable) : enabled_(enable) {}
+ bool getEnabled() {return enabled_;}
+ private:
+ bool enabled_;
+ };
+}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index 6fce7a0..fd99514 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -32,6 +32,7 @@ namespace Swift {
class ChatWindow {
public:
enum AckState {Pending, Received, Failed};
+ enum ReceiptState {ReceiptRequested, ReceiptReceived};
enum Tristate {Yes, No, Maybe};
enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor};
enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed};
@@ -57,6 +58,9 @@ namespace Swift {
virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0;
virtual void addMUCInvitation(const JID& jid, const std::string& reason, const std::string& password) = 0;
+ // message receipts
+ virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0;
+
virtual void setContactChatState(ChatState::ChatStateType state) = 0;
virtual void setName(const std::string& name) = 0;
virtual void show() = 0;
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 7e31179..5e43549 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -25,6 +25,8 @@ namespace Swift {
virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { };
virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { };
+ virtual void setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { }
+
virtual void setContactChatState(ChatState::ChatStateType /*state*/) {};
virtual void setName(const std::string& name) {name_ = name;};
virtual void show() {};