summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2016-08-17 09:02:24 (GMT)
committerTobias Markmann <tm@ayena.de>2017-04-18 15:38:43 (GMT)
commit57a611f942f2387e497334888a4bc4f68b9275c9 (patch)
tree4785288a4aca2ac31f1de50c71a1d782b7d5ae6a
parent476e2b32106449dc52a0b237caeb0492fe6c9c32 (diff)
downloadswift-57a611f942f2387e497334888a4bc4f68b9275c9.zip
swift-57a611f942f2387e497334888a4bc4f68b9275c9.tar.bz2
Support Last Message Correction in multi client scenarios
Previously Last Message Correction edits are only applied if they came from the same resource. This makes sense in MUC scenarios but does not in 1-to-1 chats. This changes the Last Message Correction behaviour for MUC and 1-to-1 chats so that different clients from the same bare JID can edit each others messages. Test-Information: Added unit test to verify Last Message Corrections work as expected when coming from the same client and from different clients. Manually verified that the receiving client correctly shows a corrected message if the sending client reconnected between first message and edit. All unit tests pass on OS X 10.11.6 with Qt 5.5.1. Change-Id: If533ecc7032e59e324979c577726f2da739012e6
-rw-r--r--Swift/ChangeLog.md1
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp4
-rw-r--r--Swift/Controllers/Chat/ChatController.h1
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp6
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h5
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp4
-rw-r--r--Swift/Controllers/Chat/MUCController.h1
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp84
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h11
-rw-r--r--Swiften/Base/LogSerializers.h26
10 files changed, 122 insertions, 21 deletions
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md
index a174436..68d1554 100644
--- a/Swift/ChangeLog.md
+++ b/Swift/ChangeLog.md
@@ -1,35 +1,36 @@
4.0-in-progress
---------------
- Fix UI layout issue for translations that require right-to-left (RTL) layout
- macOS releases are now code-signed with a key from Apple, so they can be run without Gatekeeper trust warnings
- Handle sessions being closed by the server
+- Fix Last Message Correction in multi client scenarios
- Fix display of default avatar on Windows
4.0-beta2 ( 2016-07-20 )
------------------------
- Fix Swift bug introduced in 4.0-beta1 that results in the UI sometimes getting stuck during login
4.0-beta1 ( 2016-07-15 )
------------------------
- Support for message carbons (XEP-0280)
- Improved spell checker support on Linux
- Enabled trellis mode as a default feature, allowing several tiled chats windows to be shown at once
- New chat theme including a new font
- And assorted smaller features and usability enhancements
3.0 ( 2016-02-24 )
------------------
- File transfer and Mac Notification Center issues fixed
- Fix connection to servers with invalid or untrusted certificates on OS/X
- Support for the Notification Center on OS X
- Users can now authenticate using certificates (and smart cards on Windows) when using the 'BOSH' connection type.
- Encryption on OS X now uses the platform's native 'Secure Transport' mechanisms.
- Emoticons menu in chat dialogs
- Bookmark for rooms can now be edited directly from the ‘Recent Chats’ list
- Adds option to workaround servers that don’t interoperate well with Windows (schannel) encryption
- Rooms entered while offline will now get entered on reconnect
- Chats can now be seamlessly upgraded to multi-person chats by either inviting someone via the ‘cog’ menu, or dragging them from the roster. This relies on server-side support with an appropriate chatroom (MUC) service.
- Highlighting of keywords and messages from particular users can now be configured (Keyword Highlighting Blog post).
- Full profile vcards (contact information etc.) are now supported and can be configured for the user and queried for contacts.
- Simple Communication Blocking is now supported (subject to server support) to allow the blocking of nuisance users.
- Swift can now transfer files via the ‘Jingle File Transfer’ protocol.
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 3bc9d20..3e58e40 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -538,38 +538,42 @@ void ChatController::handlePresenceChange(std::shared_ptr<Presence> newPresence)
boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(std::shared_ptr<Message> message) const {
return message->getTimestamp();
}
void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) {
HistoryMessage::Type type;
if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) {
type = HistoryMessage::PrivateMessage;
}
else {
type = HistoryMessage::Chat;
}
if (historyController_) {
historyController_->addMessage(message, fromJID, toJID, type, timeStamp);
}
}
bool ChatController::shouldIgnoreMessage(std::shared_ptr<Message> message) {
if (!message->getID().empty()) {
if (message->getID() == lastHandledMessageID_) {
return true;
}
else {
lastHandledMessageID_ = message->getID();
}
}
return false;
}
+
+JID ChatController::messageCorrectionJID(const JID& fromJID) {
+ return fromJID.toBare();
+}
ChatWindow* ChatController::detachChatWindow() {
chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
return ChatControllerBase::detachChatWindow();
}
}
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index ffc6be9..1deb0bb 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -21,60 +21,61 @@ namespace Swift {
class ChatStateTracker;
class ClientBlockListManager;
class EntityCapsProvider;
class FileTransferController;
class HighlightManager;
class HistoryController;
class NickResolver;
class SettingsProvider;
class TimerFactory;
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, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
virtual ~ChatController();
virtual void setToJID(const JID& jid) SWIFTEN_OVERRIDE;
virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE;
virtual void setOnline(bool online) SWIFTEN_OVERRIDE;
virtual void handleNewFileTransferController(FileTransferController* ftc);
virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state);
virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) SWIFTEN_OVERRIDE;
virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE;
virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
protected:
virtual void cancelReplaces() SWIFTEN_OVERRIDE;
virtual JID getBaseJID() SWIFTEN_OVERRIDE;
virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE;
virtual bool shouldIgnoreMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
+ virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE;
private:
void handlePresenceChange(std::shared_ptr<Presence> newPresence);
std::string getStatusChangeString(std::shared_ptr<Presence> presence);
virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
virtual void postSendMessage(const std::string &body, std::shared_ptr<Stanza> sentStanza) SWIFTEN_OVERRIDE;
virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) SWIFTEN_OVERRIDE;
virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE;
virtual void preSendMessageRequest(std::shared_ptr<Message>) SWIFTEN_OVERRIDE;
virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE;
virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE;
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const SWIFTEN_OVERRIDE;
void handleStanzaAcked(std::shared_ptr<Stanza> stanza);
virtual void dayTicked() SWIFTEN_OVERRIDE { lastWasPresence_ = false; }
void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/);
virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE;
void handleFileTransferCancel(std::string /* id */);
void handleFileTransferStart(std::string /* id */, std::string /* description */);
void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
void handleSendFileRequest(std::string filename);
void handleWhiteboardSessionAccept();
void handleWhiteboardSessionCancel();
void handleWhiteboardWindowShow();
void handleSettingChanged(const std::string& settingPath);
void checkForDisplayingDisplayReceiptsAlert();
void handleBlockingStateChanged();
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index ecaf186..7ae7dbd 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -252,80 +252,80 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes
if (!messageEvent->isReadable()) {
return;
}
showChatWindow();
JID from = message->getFrom();
std::vector<std::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
if (!delayPayloads[i]->getFrom()) {
continue;
}
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
std::ostringstream s;
s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
}
std::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
// Determine the timestamp
boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time();
boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message);
if (messageTimeStamp) {
timeStamp = *messageTimeStamp;
}
onActivity(body);
std::shared_ptr<Replace> replace = message->getPayload<Replace>();
bool senderIsSelf = isIncomingMessageFromMe(message);
if (replace) {
// Should check if the user has a previous message
std::map<JID, std::string>::iterator lastMessage;
- lastMessage = lastMessagesUIID_.find(from);
+ lastMessage = lastMessagesUIID_.find(messageCorrectionJID(from));
if (lastMessage != lastMessagesUIID_.end()) {
chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf);
- replaceMessage(chatMessage, lastMessagesUIID_[from], timeStamp);
+ replaceMessage(chatMessage, lastMessagesUIID_[messageCorrectionJID(from)], timeStamp);
}
}
else {
chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf);
addMessageHandleIncomingMessage(from, chatMessage, senderIsSelf, label, timeStamp);
}
logMessage(body, from, selfJID_, timeStamp, true);
}
chatWindow_->show();
updateMessageCount();
postHandleIncomingMessage(messageEvent, chatMessage);
}
void ChatControllerBase::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) {
- lastMessagesUIID_[from] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp);
+ lastMessagesUIID_[messageCorrectionJID(from)] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp);
}
std::string ChatControllerBase::getErrorMessage(std::shared_ptr<ErrorPayload> error) {
std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message");
if (!error->getText().empty()) {
return error->getText();
}
else {
switch (error->getCondition()) {
case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request");
case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict");
case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented");
case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden");
case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted");
case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error");
case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found");
case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed");
case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected");
case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed");
case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized");
case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required");
case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable");
case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect");
case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required");
case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found");
case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout");
case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources");
case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable");
case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required");
case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition");
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index c84c4d4..d10df8f 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -74,60 +74,65 @@ namespace Swift {
protected:
ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
/**
* Pass the Message appended, and the stanza used to send it.
*/
virtual void postSendMessage(const std::string&, std::shared_ptr<Stanza>) {}
virtual std::string senderDisplayNameFromMessage(const JID& from) = 0;
virtual std::string senderHighlightNameFromMessage(const JID& from) = 0;
virtual bool isIncomingMessageFromMe(std::shared_ptr<Message>) = 0;
virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) {}
virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time);
virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage&) {}
virtual void preSendMessageRequest(std::shared_ptr<Message>) {}
virtual bool isFromContact(const JID& from);
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const = 0;
virtual void dayTicked() {}
virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
std::string getErrorMessage(std::shared_ptr<ErrorPayload>);
virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {}
virtual void cancelReplaces() = 0;
/** JID any iq for account should go to - bare except for PMs */
virtual JID getBaseJID();
virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf);
void updateMessageCount();
virtual bool shouldIgnoreMessage(std::shared_ptr<Message> /* message */) {
return false;
}
+ /**
+ * What JID should be used for last message correction (XEP-0308) tracking.
+ */
+ virtual JID messageCorrectionJID(const JID& fromJID) = 0;
+
private:
IDGenerator idGenerator_;
std::string lastSentMessageStanzaID_;
void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
void handleAllMessagesRead();
void handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
void handleMUCInvitation(Message::ref message);
void handleMediatedMUCInvitation(Message::ref message);
void handleGeneralMUCInvitation(MUCInviteEvent::ref event);
void handleContinuationsBroken();
protected:
JID selfJID_;
std::vector<std::shared_ptr<StanzaEvent> > unreadMessages_;
std::vector<std::shared_ptr<StanzaEvent> > targetedUnreadMessages_;
StanzaChannel* stanzaChannel_;
IQRouter* iqRouter_;
ChatWindowFactory* chatWindowFactory_;
ChatWindow* chatWindow_;
JID toJID_;
bool labelsEnabled_;
std::map<JID, std::string> lastMessagesUIID_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
bool useDelayForLatency_;
EventController* eventController_;
EntityCapsProvider* entityCapsProvider_;
SecurityLabelsCatalog::Item lastLabel_;
HistoryController* historyController_;
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 73cf748..b685e04 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -1051,60 +1051,64 @@ void MUCController::handleUnblockUserRequest() {
void MUCController::handleBlockingStateChanged() {
std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
if (blockList->getState() == BlockList::Available) {
if (blockList->isBlocked(toJID_)) {
if (!blockedContactAlert_) {
blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again"));
}
chatWindow_->setBlockingState(ChatWindow::IsBlocked);
} else {
if (blockedContactAlert_) {
chatWindow_->removeAlert(*blockedContactAlert_);
blockedContactAlert_.reset();
}
chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
}
}
}
void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) {
chatWindow_->setAffiliations(affiliation, jids);
}
void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) {
// log only incoming messages
if (isIncoming && historyController_) {
historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp);
}
}
+JID MUCController::messageCorrectionJID(const JID& fromJID) {
+ return fromJID;
+}
+
void MUCController::addRecentLogs() {
if (!historyController_) {
return;
}
joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_);
for (const auto& message : joinContext_) {
bool senderIsSelf = nick_ == message.getFromJID().getResource();
// the chatWindow uses utc timestamps
addMessage(chatMessageParser_->parseMessageBody(message.getMessage()), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, std::make_shared<SecurityLabel>(), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()));
}
}
void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) {
std::string body = newMessage->getBody().get_value_or("");
JID jid = newMessage->getFrom();
boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp();
for (const auto& message : boost::adaptors::reverse(joinContext_)) {
boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset());
if (time && time < messageTime) {
break;
}
if (time && time != messageTime) {
continue;
}
if (message.getFromJID() != jid) {
continue;
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 9d6428b..ba68ec6 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -58,60 +58,61 @@ namespace Swift {
virtual ~MUCController();
boost::signals2::signal<void ()> onUserLeft;
boost::signals2::signal<void ()> onUserJoined;
boost::signals2::signal<void ()> onImpromptuConfigCompleted;
boost::signals2::signal<void (const std::string&, const std::string& )> onUserNicknameChanged;
virtual void setOnline(bool online) SWIFTEN_OVERRIDE;
virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE;
void rejoin();
static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu);
static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts);
static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname);
bool isJoined();
const std::string& getNick();
const boost::optional<std::string> getPassword() const;
bool isImpromptu() const;
std::map<std::string, JID> getParticipantJIDs() const;
void sendInvites(const std::vector<JID>& jids, const std::string& reason) const;
protected:
virtual void preSendMessageRequest(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE;
virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE;
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const SWIFTEN_OVERRIDE;
virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) SWIFTEN_OVERRIDE;
virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE;
virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE;
virtual void cancelReplaces() SWIFTEN_OVERRIDE;
virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE;
+ virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE;
private:
void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role);
void clearPresenceQueue();
void addPresenceMessage(const std::string& message);
void handleWindowOccupantSelectionChanged(ContactRosterItem* item);
void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item);
void handleWindowClosed();
void handleAvatarChanged(const JID& jid);
void handleOccupantJoined(const MUCOccupant& occupant);
void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname);
void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason);
void handleOccupantPresenceChange(std::shared_ptr<Presence> presence);
void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole);
void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation);
void handleJoinComplete(const std::string& nick);
void handleJoinFailed(std::shared_ptr<ErrorPayload> error);
void handleJoinTimeoutTick();
void handleChangeSubjectRequest(const std::string&);
void handleBookmarkRequest();
std::string roleToGroupName(MUCOccupant::Role role);
std::string roleToSortName(MUCOccupant::Role role);
JID nickToJID(const std::string& nick);
std::string roleToFriendlyName(MUCOccupant::Role role);
void receivedActivity();
bool messageTargetsMe(std::shared_ptr<Message> message);
void updateJoinParts();
bool shouldUpdateJoinParts();
virtual void dayTicked() SWIFTEN_OVERRIDE { clearPresenceQueue(); }
void processUserPart();
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index d104fbd..0356c6a 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -110,60 +110,65 @@ class ChatsManagerTest : public CppUnit::TestFixture {
CPPUNIT_TEST(testNoDuplicateUnbind);
CPPUNIT_TEST(testThreeMUCWindows);
CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster);
CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster);
CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth);
CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom);
CPPUNIT_TEST(testChatControllerFullJIDBindingOnMessageAndNotReceipt);
CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive);
CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect);
CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite);
CPPUNIT_TEST(testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications);
// MUC PM Tests
CPPUNIT_TEST(testChatControllerPMPresenceHandling);
CPPUNIT_TEST(testChatControllerMucPmUnavailableErrorHandling);
// Highlighting tests
CPPUNIT_TEST(testChatControllerHighlightingNotificationTesting);
CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds);
CPPUNIT_TEST(testChatControllerHighlightingNotificationKeyword);
CPPUNIT_TEST(testChatControllerMeMessageHandling);
CPPUNIT_TEST(testRestartingMUCComponentCrash);
CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC);
// Carbons tests
CPPUNIT_TEST(testCarbonsForwardedIncomingMessageToSecondResource);
CPPUNIT_TEST(testCarbonsForwardedOutgoingMessageFromSecondResource);
CPPUNIT_TEST(testCarbonsForwardedIncomingDuplicates);
+
+ // Message correction tests
+ CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceBySameResource);
+ CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceByOtherResource);
+
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
mocks_ = new MockRepository();
notifier_ = std::unique_ptr<DummyNotifier>(new DummyNotifier());
jid_ = JID("test@test.com/resource");
stanzaChannel_ = new DummyStanzaChannel();
iqRouter_ = new IQRouter(stanzaChannel_);
eventController_ = new EventController();
chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>();
xmppRoster_ = new XMPPRosterImpl();
mucRegistry_ = new MUCRegistry();
nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_);
presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
serverDiscoInfo_ = std::make_shared<DiscoInfo>();
presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_);
uiEventStream_ = new UIEventStream();
entityCapsProvider_ = new DummyEntityCapsProvider();
chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
settings_ = new DummySettingsProvider();
profileSettings_ = new ProfileSettingsProvider("a", settings_);
chatListWindow_ = new MockChatListWindow();
ftManager_ = new DummyFileTransferManager();
ftOverview_ = new FileTransferOverview(ftManager_);
avatarManager_ = new NullAvatarManager();
@@ -190,61 +195,60 @@ public:
void tearDown() {
delete highlightManager_;
delete profileSettings_;
delete eventNotifier_;
delete avatarManager_;
delete manager_;
delete timerFactory_;
delete clientBlockListManager_;
delete vcardManager_;
delete vcardStorage_;
delete crypto_;
delete ftOverview_;
delete ftManager_;
delete wbSessionManager_;
delete wbManager_;
delete directedPresenceSender_;
delete presenceSender_;
delete presenceOracle_;
delete nickResolver_;
delete mucRegistry_;
delete iqRouter_;
delete stanzaChannel_;
delete eventController_;
delete uiEventStream_;
delete mucManager_;
delete xmppRoster_;
delete entityCapsProvider_;
delete chatListWindow_;
delete mocks_;
delete settings_;
-
}
void testFirstOpenWindowIncoming() {
JID messageJID("testling@test.com/resource1");
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("This is a legible message. >HEH@)oeueu");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
}
void testSecondOpenWindowIncoming() {
JID messageJID1("testling@test.com/resource1");
MockChatWindow* window1 = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1);
std::shared_ptr<Message> message1(new Message());
message1->setFrom(messageJID1);
std::string body1("This is a legible message. >HEH@)oeueu");
message1->setBody(body1);
manager_->handleIncomingMessage(message1);
CPPUNIT_ASSERT_EQUAL(body1, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_));
JID messageJID2("testling@test.com/resource2");
@@ -705,61 +709,61 @@ public:
CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(4)->getPayload<DeliveryReceiptRequest>());
}
void testChatControllerPMPresenceHandling() {
JID participantA = JID("test@rooms.test.com/participantA");
JID participantB = JID("test@rooms.test.com/participantB");
mucRegistry_->addMUC("test@rooms.test.com");
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window);
uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(participantA)));
Presence::ref presence = Presence::create();
presence->setFrom(participantA);
presence->setShow(StatusShow::Online);
stanzaChannel_->onPresenceReceived(presence);
CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
presence = Presence::create();
presence->setFrom(participantB);
presence->setShow(StatusShow::Away);
stanzaChannel_->onPresenceReceived(presence);
presence = Presence::create();
presence->setFrom(participantA);
presence->setShow(StatusShow::None);
presence->setType(Presence::Unavailable);
stanzaChannel_->onPresenceReceived(presence);
- CPPUNIT_ASSERT_EQUAL(std::string("participantA has gone offline."), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string("participantA has gone offline."), MockChatWindow::bodyFromMessage(window->lastReplacedLastMessage_));
}
void testChatControllerMucPmUnavailableErrorHandling() {
auto mucJID = JID("test@rooms.test.com");
auto participantA = mucJID.withResource("participantA");
auto participantB = mucJID.withResource("participantB");
auto mucWindow = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(mucWindow);
uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, participantB.getResource()));
CPPUNIT_ASSERT_EQUAL(true, mucWindow->mucType_.is_initialized());
auto window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window);
uiEventStream_->send(std::make_shared<RequestChatUIEvent>(participantA));
CPPUNIT_ASSERT_EQUAL(false, window->mucType_.is_initialized());
Presence::ref presence = Presence::create();
presence->setFrom(participantA);
presence->setShow(StatusShow::Online);
stanzaChannel_->onPresenceReceived(presence);
CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
// send message to participantA
auto messageBody = std::string("message body to send");
window->onSendMessageRequest(messageBody, false);
auto sendMessageStanza = stanzaChannel_->getStanzaAtIndex<Message>(2);
CPPUNIT_ASSERT_EQUAL(messageBody, *sendMessageStanza->getBody());
// receive reply with error
@@ -1070,129 +1074,129 @@ public:
};
stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available));
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("This is a legible message. >HEH@)oeueu");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
auto incomingMUCInvite = std::make_shared<Message>();
incomingMUCInvite->setFrom(messageJID);
auto invitePayload = std::make_shared<MUCInvitationPayload>();
invitePayload->setJID("room@muc.service.com");
incomingMUCInvite->addPayload(invitePayload);
stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable));
stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available));
window->resetLastMessages();
manager_->handleIncomingMessage(incomingMUCInvite);
CPPUNIT_ASSERT_EQUAL(JID("room@muc.service.com"), window->lastMUCInvitationJID_);
stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable));
- CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedLastMessage_));
CPPUNIT_ASSERT_EQUAL(std::string("testling@test.com has gone offline."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
}
void testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications() {
JID mucJID("mucroom@rooms.test.com");
std::string nickname = "toodles";
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname));
auto genRemoteMUCPresence = [=]() {
auto presence = Presence::create();
presence->setFrom(mucJID.withResource(nickname));
presence->setTo(jid_);
return presence;
};
{
auto presence = genRemoteMUCPresence();
auto userPayload = std::make_shared<MUCUserPayload>();
userPayload->addStatusCode(110);
userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator));
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
{
auto presence = genRemoteMUCPresence();
presence->setFrom(mucJID.withResource("someDifferentNickname"));
auto userPayload = std::make_shared<MUCUserPayload>();
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname has entered the room."), window->bodyFromMessage(window->lastAddedPresence_));
- CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastReplacedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastReplacedLastMessage_));
window->resetLastMessages();
{
auto presence = genRemoteMUCPresence();
presence->setFrom(mucJID.withResource("Romeo"));
auto userPayload = std::make_shared<MUCUserPayload>();
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastAddedPresence_));
- CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname and Romeo have entered the room"), window->bodyFromMessage(window->lastReplacedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname and Romeo have entered the room"), window->bodyFromMessage(window->lastReplacedLastMessage_));
window->resetLastMessages();
{
auto message = std::make_shared<Message>();
message->setFrom(mucJID.withResource("Romeo"));
message->setTo(mucJID);
message->setType(Message::Groupchat);
message->addPayload(std::make_shared<ChatState>(ChatState::Composing));
manager_->handleIncomingMessage(message);
}
{
auto presence = genRemoteMUCPresence();
presence->setFrom(mucJID.withResource("Juliet"));
auto userPayload = std::make_shared<MUCUserPayload>();
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastAddedPresence_));
- CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname, Romeo and Juliet have entered the room"), window->bodyFromMessage(window->lastReplacedMessage_));
+ CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname, Romeo and Juliet have entered the room"), window->bodyFromMessage(window->lastReplacedLastMessage_));
}
template <typename CarbonsType>
Message::ref createCarbonsMessage(std::shared_ptr<CarbonsType> carbons, std::shared_ptr<Message> forwardedMessage) {
auto messageWrapper = std::make_shared<Message>();
messageWrapper->setFrom(jid_.toBare());
messageWrapper->setTo(jid_);
messageWrapper->setType(Message::Chat);
messageWrapper->addPayload(carbons);
auto forwarded = std::make_shared<Forwarded>();
carbons->setForwarded(forwarded);
forwarded->setStanza(forwardedMessage);
return messageWrapper;
}
void testCarbonsForwardedIncomingMessageToSecondResource() {
JID messageJID("testling@test.com/resource1");
JID jid2 = jid_.toBare().withResource("someOtherResource");
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("This is a legible message. >HEH@)oeueu");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
@@ -1238,100 +1242,152 @@ public:
std::string forwardedBody = "Some text my other resource sent.";
originalMessage->setBody(forwardedBody);
originalMessage->addPayload(std::make_shared<DeliveryReceiptRequest>());
auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsSent>(), originalMessage);
manager_->handleIncomingMessage(messageWrapper);
CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
CPPUNIT_ASSERT_EQUAL(true, window->lastAddedMessageSenderIsSelf_);
CPPUNIT_ASSERT_EQUAL(size_t(1), window->receiptChanges_.size());
CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptRequested, window->receiptChanges_[0].second);
}
// incoming carbons message for the received delivery receipt to the other resource
{
auto originalMessage = std::make_shared<Message>();
originalMessage->setFrom(messageJID);
originalMessage->setTo(jid2);
originalMessage->setType(Message::Chat);
originalMessage->addPayload(std::make_shared<DeliveryReceipt>("abcdefg123456"));
auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
manager_->handleIncomingMessage(messageWrapper);
CPPUNIT_ASSERT_EQUAL(size_t(2), window->receiptChanges_.size());
CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptReceived, window->receiptChanges_[1].second);
}
}
-
+
void testCarbonsForwardedIncomingDuplicates() {
JID messageJID("testling@test.com/resource1");
JID jid2 = jid_.toBare().withResource("someOtherResource");
-
+
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
-
+
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("This is a legible message. >HEH@)oeueu");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
-
+
// incoming carbons message from another resource and duplicate of it
{
auto originalMessage = std::make_shared<Message>();
originalMessage->setFrom(messageJID);
originalMessage->setTo(jid2);
originalMessage->setID("BDD82F0B-2523-48BF-B8CA-17B23A314BC2");
originalMessage->setType(Message::Chat);
std::string forwardedBody = "Some further text.";
originalMessage->setBody(forwardedBody);
-
+
auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
-
+
manager_->handleIncomingMessage(messageWrapper);
-
+
CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_);
window->resetLastMessages();
-
+
messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
manager_->handleIncomingMessage(messageWrapper);
CPPUNIT_ASSERT_EQUAL(std::string(), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_);
}
}
+ void testChatControllerMessageCorrectionReplaceBySameResource() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ auto message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text before edit");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text after edit");
+ message->addPayload(std::make_shared<Replace>("someID"));
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ }
+
+ void testChatControllerMessageCorrectionReplaceByOtherResource() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+ auto message = std::make_shared<Message>();
+ message->setFrom(messageJID);
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text before edit");
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+ message = std::make_shared<Message>();
+ message->setFrom(messageJID.toBare().withResource("resource2"));
+ message->setTo(jid_);
+ message->setType(Message::Chat);
+ message->setBody("text after edit");
+ message->addPayload(std::make_shared<Replace>("someID"));
+ manager_->handleIncomingMessage(message);
+
+ CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+ }
+
private:
std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
std::shared_ptr<Message> message = std::make_shared<Message>();
message->setFrom(from);
message->setID(id);
message->setBody("This will cause the window to open");
message->addPayload(std::make_shared<DeliveryReceiptRequest>());
return message;
}
size_t st(int i) {
return static_cast<size_t>(i);
}
void handleHighlightAction(const HighlightAction& action) {
handledHighlightActions_++;
if (action.getSoundFilePath()) {
soundsPlayed_.insert(action.getSoundFilePath().get_value_or(""));
}
}
private:
JID jid_;
std::unique_ptr<DummyNotifier> notifier_;
ChatsManager* manager_;
DummyStanzaChannel* stanzaChannel_;
IQRouter* iqRouter_;
EventController* eventController_;
ChatWindowFactory* chatWindowFactory_;
JoinMUCWindowFactory* joinMUCWindowFactory_;
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 84c86fc..7682781 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -16,64 +16,68 @@ namespace Swift {
public:
MockChatWindow() {}
virtual ~MockChatWindow();
virtual std::string addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/) {
lastAddedMessage_ = message;
lastAddedMessageSenderName_ = senderName;
lastAddedMessageSenderIsSelf_ = senderIsSelf;
return "id";
}
virtual std::string addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/) {
lastAddedAction_ = message;
lastAddedActionSenderName_ = senderName;
lastAddedActionSenderIsSelf_ = senderIsSelf;
return "id";
}
virtual std::string addSystemMessage(const ChatMessage& message, Direction /*direction*/) {
lastAddedSystemMessage_ = message;
return "id";
}
virtual void addPresenceMessage(const ChatMessage& message, Direction /*direction*/) {
lastAddedPresence_ = message;
}
virtual void addErrorMessage(const ChatMessage& message) {
lastAddedErrorMessage_ = message;
}
- virtual void replaceMessage(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/) {}
+
+ virtual void replaceMessage(const ChatMessage& message, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/) {
+ lastReplacedMessage_ = message;
+ }
+
virtual void replaceWithAction(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/) {}
virtual void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour /*timestampBehaviour*/) {
- lastReplacedMessage_ = message;
+ lastReplacedLastMessage_ = message;
}
virtual void replaceSystemMessage(const ChatMessage& message, const std::string& /*id*/, const TimestampBehaviour /*timestampBehaviour*/) {
lastReplacedSystemMessage_ = message;
}
// File transfer related stuff
virtual std::string addFileTransfer(const std::string& /*senderName*/, const std::string& /*avatarPath*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/, const std::string& /*description*/) { return nullptr; }
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) {
receiptChanges_.emplace_back(id, state);
}
virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}
virtual void setName(const std::string& name) {name_ = name;}
virtual void show() {}
virtual bool isVisible() const { return true; }
virtual void activate() {}
virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;}
virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;}
virtual void setUnreadMessageCount(int /*count*/) {}
virtual void convertToMUC(MUCType mucType) {
mucType_ = mucType;
}
virtual void setSecurityLabelsError() {}
virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;}
virtual void setOnline(bool /*online*/) {}
@@ -97,58 +101,59 @@ namespace Swift {
virtual std::string addWhiteboardRequest(bool) {return "";}
virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){}
virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) {}
virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {}
virtual void setBlockingState(BlockingState) {}
virtual void setCanInitiateImpromptuChats(bool supportsImpromptu) {
impromptuMUCSupported_ = supportsImpromptu;
}
virtual void showBookmarkWindow(const MUCBookmark& /*bookmark*/) {}
virtual void setBookmarkState(RoomBookmarkState) {}
static std::string bodyFromMessage(const ChatMessage& message) {
std::string body;
std::shared_ptr<ChatTextMessagePart> text;
std::shared_ptr<ChatHighlightingMessagePart> highlight;
for (auto &&part : message.getParts()) {
if ((text = std::dynamic_pointer_cast<ChatTextMessagePart>(part))) {
body += text->text;
}
else if ((highlight = std::dynamic_pointer_cast<ChatHighlightingMessagePart>(part))) {
body += highlight->text;
}
}
return body;
}
void resetLastMessages() {
- lastAddedMessage_ = lastAddedAction_ = lastAddedPresence_ = lastReplacedMessage_ = lastAddedSystemMessage_ = lastReplacedSystemMessage_ = ChatMessage();
+ lastAddedMessage_ = lastAddedAction_ = lastAddedPresence_ = lastReplacedLastMessage_ = lastAddedSystemMessage_ = lastReplacedSystemMessage_ = ChatMessage();
lastAddedMessageSenderName_ = lastAddedActionSenderName_ = "";
lastAddedMessageSenderIsSelf_ = lastAddedActionSenderIsSelf_ = false;
}
std::string name_;
ChatMessage lastAddedMessage_;
std::string lastAddedMessageSenderName_;
bool lastAddedMessageSenderIsSelf_ = false;
ChatMessage lastAddedAction_;
std::string lastAddedActionSenderName_;
bool lastAddedActionSenderIsSelf_ = false;
ChatMessage lastAddedPresence_;
ChatMessage lastReplacedMessage_;
+ ChatMessage lastReplacedLastMessage_;
ChatMessage lastAddedSystemMessage_;
ChatMessage lastReplacedSystemMessage_;
ChatMessage lastAddedErrorMessage_;
JID lastMUCInvitationJID_;
std::vector<SecurityLabelsCatalog::Item> labels_;
bool labelsEnabled_ = false;
bool impromptuMUCSupported_ = false;
SecurityLabelsCatalog::Item label_;
Roster* roster_ = nullptr;
std::vector<std::pair<std::string, ReceiptState>> receiptChanges_;
boost::optional<MUCType> mucType_;
};
}
diff --git a/Swiften/Base/LogSerializers.h b/Swiften/Base/LogSerializers.h
index b804808..7f73686 100644
--- a/Swiften/Base/LogSerializers.h
+++ b/Swiften/Base/LogSerializers.h
@@ -1,55 +1,79 @@
/*
* Copyright (c) 2016-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
+#include <map>
#include <memory>
#include <ostream>
#include <string>
+#include <utility>
#include <vector>
#include <boost/optional.hpp>
namespace Swift {
class Presence;
class BOSHError;
template <typename T>
std::ostream& operator<<(std::ostream& stream, const std::shared_ptr<T>& ptr) {
if (ptr) {
stream << *ptr;
}
else {
stream << "nullptr";
}
return stream;
}
template <typename T>
std::ostream& operator<<(std::ostream& stream, const std::vector<T>& vec) {
stream << "[";
if (!vec.empty()) {
auto it = std::begin(vec);
stream << *it;
++it;
for (auto end = std::end(vec); it != end; ++it) {
stream << ", " << *it;
}
- }
+ }
stream << "]";
return stream;
}
+template <typename KEY, typename VALUE>
+std::ostream& operator<<(std::ostream& stream, const std::pair<KEY, VALUE>& pair) {
+ stream << pair.first << ":" << pair.second;
+ return stream;
+}
+
+template <typename KEY, typename VALUE>
+std::ostream& operator<<(std::ostream& stream, const std::map<KEY, VALUE>& map) {
+ stream << "{";
+ if (!map.empty()) {
+ auto it = std::begin(map);
+ stream << *it;
+
+ ++it;
+ for (auto end = std::end(map); it != end; ++it) {
+ stream << ", " << *it;
+ }
+ }
+ stream << "}";
+ return stream;
+}
+
std::ostream& operator<<(std::ostream& stream, const Presence& presence);
std::ostream& operator<<(std::ostream& stream, const BOSHError& boshError);
};
::std::ostream& operator<<(::std::ostream& os, const boost::optional<std::string>& optStr);