mirror of https://github.com/qTox/qTox.git
10 changed files with 535 additions and 0 deletions
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
qTox is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include "friendmessagedispatcher.h" |
||||
#include "src/persistence/settings.h" |
||||
|
||||
|
||||
namespace { |
||||
|
||||
/**
|
||||
* @brief Sends message to friend using messageSender |
||||
* @param[in] messageSender |
||||
* @param[in] f |
||||
* @param[in] message |
||||
* @param[out] receipt |
||||
*/ |
||||
bool sendMessageToCore(ICoreFriendMessageSender& messageSender, const Friend& f, |
||||
const Message& message, ReceiptNum& receipt) |
||||
{ |
||||
uint32_t friendId = f.getId(); |
||||
|
||||
auto sendFn = message.isAction ? std::mem_fn(&ICoreFriendMessageSender::sendAction) |
||||
: std::mem_fn(&ICoreFriendMessageSender::sendMessage); |
||||
|
||||
return sendFn(messageSender, friendId, message.content, receipt); |
||||
} |
||||
} // namespace
|
||||
|
||||
FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, ICoreFriendMessageSender& messageSender_) |
||||
: f(f_) |
||||
, messageSender(messageSender_) |
||||
, offlineMsgEngine(&f_, &messageSender_) |
||||
{ |
||||
connect(&f, &Friend::statusChanged, this, &FriendMessageDispatcher::onFriendStatusChange); |
||||
} |
||||
|
||||
/**
|
||||
* @see IMessageSender::sendMessage |
||||
*/ |
||||
std::pair<DispatchedMessageId, DispatchedMessageId> |
||||
FriendMessageDispatcher::sendMessage(bool isAction, const QString& content) |
||||
{ |
||||
const auto firstId = nextMessageId; |
||||
auto lastId = nextMessageId; |
||||
for (const auto& message : processOutgoingMessage(isAction, content)) { |
||||
auto messageId = nextMessageId++; |
||||
lastId = messageId; |
||||
auto onOfflineMsgComplete = [this, messageId] { emit this->messageComplete(messageId); }; |
||||
|
||||
ReceiptNum receipt; |
||||
|
||||
bool messageSent = false; |
||||
|
||||
if (f.isOnline()) { |
||||
messageSent = sendMessageToCore(messageSender, f, message, receipt); |
||||
} |
||||
|
||||
if (!messageSent) { |
||||
offlineMsgEngine.addUnsentMessage(message, onOfflineMsgComplete); |
||||
} else { |
||||
offlineMsgEngine.addSentMessage(receipt, message, onOfflineMsgComplete); |
||||
} |
||||
|
||||
emit this->messageSent(messageId, message); |
||||
} |
||||
return std::make_pair(firstId, lastId); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Handles received message from toxcore |
||||
* @param[in] isAction True if action message |
||||
* @param[in] content Unprocessed toxcore message |
||||
*/ |
||||
void FriendMessageDispatcher::onMessageReceived(bool isAction, const QString& content) |
||||
{ |
||||
emit this->messageReceived(f.getPublicKey(), processIncomingMessage(isAction, content)); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Handles received receipt from toxcore |
||||
* @param[in] receipt receipt id |
||||
*/ |
||||
void FriendMessageDispatcher::onReceiptReceived(ReceiptNum receipt) |
||||
{ |
||||
offlineMsgEngine.onReceiptReceived(receipt); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Handles status change for friend |
||||
* @note Parameters just to fit slot api |
||||
*/ |
||||
void FriendMessageDispatcher::onFriendStatusChange(const ToxPk&, Status::Status) |
||||
{ |
||||
if (f.isOnline()) { |
||||
offlineMsgEngine.deliverOfflineMsgs(); |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief Clears all currently outgoing messages |
||||
*/ |
||||
void FriendMessageDispatcher::clearOutgoingMessages() |
||||
{ |
||||
offlineMsgEngine.removeAllMessages(); |
||||
} |
||||
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
qTox is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#ifndef FRIEND_MESSAGE_DISPATCHER_H |
||||
#define FRIEND_MESSAGE_DISPATCHER_H |
||||
|
||||
#include "src/core/icorefriendmessagesender.h" |
||||
#include "src/model/friend.h" |
||||
#include "src/model/imessagedispatcher.h" |
||||
#include "src/model/message.h" |
||||
#include "src/persistence/offlinemsgengine.h" |
||||
|
||||
#include <QObject> |
||||
#include <QString> |
||||
|
||||
#include <cstdint> |
||||
|
||||
class FriendMessageDispatcher : public IMessageDispatcher |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
FriendMessageDispatcher(Friend& f, ICoreFriendMessageSender& messageSender); |
||||
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction, |
||||
const QString& content) override; |
||||
void onMessageReceived(bool isAction, const QString& content); |
||||
void onReceiptReceived(ReceiptNum receipt); |
||||
void clearOutgoingMessages(); |
||||
private slots: |
||||
void onFriendStatusChange(const ToxPk& key, Status::Status status); |
||||
|
||||
private: |
||||
Friend& f; |
||||
DispatchedMessageId nextMessageId = DispatchedMessageId(0); |
||||
|
||||
ICoreFriendMessageSender& messageSender; |
||||
OfflineMsgEngine offlineMsgEngine; |
||||
}; |
||||
|
||||
|
||||
#endif /* IMESSAGE_DISPATCHER_H */ |
||||
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
qTox is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#ifndef IMESSAGE_DISPATCHER_H |
||||
#define IMESSAGE_DISPATCHER_H |
||||
|
||||
#include "src/model/friend.h" |
||||
#include "src/model/message.h" |
||||
|
||||
#include <QObject> |
||||
#include <QString> |
||||
|
||||
#include <cstdint> |
||||
|
||||
using DispatchedMessageId = NamedType<size_t, struct SentMessageIdTag, Orderable, Incrementable>; |
||||
Q_DECLARE_METATYPE(DispatchedMessageId); |
||||
|
||||
class IMessageDispatcher : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
virtual ~IMessageDispatcher() = default; |
||||
|
||||
/**
|
||||
* @brief Sends message to associated chat |
||||
* @param[in] isAction True if is action message |
||||
* @param[in] content Message content |
||||
* @return Pair of first and last dispatched message IDs |
||||
*/ |
||||
virtual std::pair<DispatchedMessageId, DispatchedMessageId> |
||||
sendMessage(bool isAction, const QString& content) = 0; |
||||
signals: |
||||
/**
|
||||
* @brief Emitted when a message is received and processed |
||||
*/ |
||||
void messageReceived(const ToxPk& sender, const Message& message); |
||||
|
||||
/**
|
||||
* @brief Emitted when a message is processed and sent |
||||
* @param id message id for completion |
||||
* @param message sent message |
||||
*/ |
||||
void messageSent(DispatchedMessageId id, const Message& message); |
||||
|
||||
/**
|
||||
* @brief Emitted when a receiver report is received from the associated chat |
||||
* @param id Id of message that is completed |
||||
*/ |
||||
void messageComplete(DispatchedMessageId id); |
||||
}; |
||||
|
||||
#endif /* IMESSAGE_DISPATCHER_H */ |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
qTox is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include "message.h" |
||||
#include "src/core/core.h" |
||||
|
||||
std::vector<Message> processOutgoingMessage(bool isAction, const QString& content) |
||||
{ |
||||
std::vector<Message> ret; |
||||
|
||||
QStringList splitMsgs = Core::splitMessage(content, tox_max_message_length()); |
||||
ret.reserve(splitMsgs.size()); |
||||
|
||||
QDateTime timestamp = QDateTime::currentDateTime(); |
||||
std::transform(splitMsgs.begin(), splitMsgs.end(), std::back_inserter(ret), |
||||
[&](const QString& part) { |
||||
Message message; |
||||
message.isAction = isAction; |
||||
message.content = part; |
||||
message.timestamp = timestamp; |
||||
return message; |
||||
}); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
Message processIncomingMessage(bool isAction, const QString& message) |
||||
{ |
||||
QDateTime timestamp = QDateTime::currentDateTime(); |
||||
auto ret = Message{}; |
||||
ret.isAction = isAction; |
||||
ret.content = message; |
||||
ret.timestamp = timestamp; |
||||
return ret; |
||||
} |
||||
@ -0,0 +1,207 @@
@@ -0,0 +1,207 @@
|
||||
#include "src/core/icorefriendmessagesender.h" |
||||
#include "src/model/friend.h" |
||||
#include "src/model/friendmessagedispatcher.h" |
||||
#include "src/model/message.h" |
||||
|
||||
#include <QObject> |
||||
#include <QtTest/QtTest> |
||||
|
||||
#include <deque> |
||||
|
||||
|
||||
class MockFriendMessageSender : public ICoreFriendMessageSender |
||||
{ |
||||
public: |
||||
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override |
||||
{ |
||||
if (canSend) { |
||||
numSentActions++; |
||||
receipt = receiptNum; |
||||
receiptNum.get() += 1; |
||||
} |
||||
return canSend; |
||||
} |
||||
|
||||
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override |
||||
{ |
||||
if (canSend) { |
||||
numSentMessages++; |
||||
receipt = receiptNum; |
||||
receiptNum.get() += 1; |
||||
} |
||||
return canSend; |
||||
} |
||||
|
||||
bool canSend = true; |
||||
ReceiptNum receiptNum{0}; |
||||
size_t numSentActions = 0; |
||||
size_t numSentMessages = 0; |
||||
}; |
||||
class TestFriendMessageDispatcher : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
TestFriendMessageDispatcher(); |
||||
|
||||
private slots: |
||||
void init(); |
||||
void testSignals(); |
||||
void testMessageSending(); |
||||
void testOfflineMessages(); |
||||
void testFailedMessage(); |
||||
|
||||
void onMessageSent(DispatchedMessageId id, Message message) |
||||
{ |
||||
auto it = outgoingMessages.find(id); |
||||
QVERIFY(it == outgoingMessages.end()); |
||||
outgoingMessages.emplace(id, std::move(message)); |
||||
} |
||||
|
||||
void onMessageComplete(DispatchedMessageId id) |
||||
{ |
||||
auto it = outgoingMessages.find(id); |
||||
QVERIFY(it != outgoingMessages.end()); |
||||
outgoingMessages.erase(it); |
||||
} |
||||
|
||||
void onMessageReceived(const ToxPk& sender, Message message) |
||||
{ |
||||
receivedMessages.push_back(std::move(message)); |
||||
} |
||||
|
||||
private: |
||||
// All unique_ptrs to make construction/init() easier to manage
|
||||
std::unique_ptr<Friend> f; |
||||
std::unique_ptr<MockFriendMessageSender> messageSender; |
||||
std::unique_ptr<FriendMessageDispatcher> friendMessageDispatcher; |
||||
std::map<DispatchedMessageId, Message> outgoingMessages; |
||||
std::deque<Message> receivedMessages; |
||||
}; |
||||
|
||||
TestFriendMessageDispatcher::TestFriendMessageDispatcher() {} |
||||
|
||||
/**
|
||||
* @brief Test initialization. Resets all member variables for a fresh test state |
||||
*/ |
||||
void TestFriendMessageDispatcher::init() |
||||
{ |
||||
f = std::unique_ptr<Friend>(new Friend(0, ToxPk())); |
||||
f->setStatus(Status::Status::Online); |
||||
messageSender = std::unique_ptr<MockFriendMessageSender>(new MockFriendMessageSender()); |
||||
friendMessageDispatcher = |
||||
std::unique_ptr<FriendMessageDispatcher>(new FriendMessageDispatcher(*f, *messageSender)); |
||||
|
||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this, |
||||
&TestFriendMessageDispatcher::onMessageSent); |
||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageComplete, this, |
||||
&TestFriendMessageDispatcher::onMessageComplete); |
||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageReceived, this, |
||||
&TestFriendMessageDispatcher::onMessageReceived); |
||||
|
||||
outgoingMessages = std::map<DispatchedMessageId, Message>(); |
||||
receivedMessages = std::deque<Message>(); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Tests that the signals emitted by the dispatcher are all emitted at the correct times |
||||
*/ |
||||
void TestFriendMessageDispatcher::testSignals() |
||||
{ |
||||
auto startReceiptNum = messageSender->receiptNum; |
||||
auto sentIds = friendMessageDispatcher->sendMessage(false, "test"); |
||||
auto endReceiptNum = messageSender->receiptNum; |
||||
|
||||
// We should have received some message ids in our callbacks
|
||||
QVERIFY(sentIds.first == sentIds.second); |
||||
QVERIFY(outgoingMessages.find(sentIds.first) != outgoingMessages.end()); |
||||
QVERIFY(startReceiptNum.get() != endReceiptNum.get()); |
||||
QVERIFY(outgoingMessages.size() == 1); |
||||
|
||||
QVERIFY(outgoingMessages.begin()->second.isAction == false); |
||||
QVERIFY(outgoingMessages.begin()->second.content == "test"); |
||||
|
||||
for (auto i = startReceiptNum; i < endReceiptNum; ++i.get()) { |
||||
friendMessageDispatcher->onReceiptReceived(i); |
||||
} |
||||
|
||||
// If our completion ids were hooked up right this should be empty
|
||||
QVERIFY(outgoingMessages.empty()); |
||||
|
||||
// If signals are emitted correctly we should have one message in our received message buffer
|
||||
QVERIFY(receivedMessages.empty()); |
||||
friendMessageDispatcher->onMessageReceived(false, "test2"); |
||||
|
||||
QVERIFY(!receivedMessages.empty()); |
||||
QVERIFY(receivedMessages.front().isAction == false); |
||||
QVERIFY(receivedMessages.front().content == "test2"); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Tests that sent messages actually go through to core |
||||
*/ |
||||
void TestFriendMessageDispatcher::testMessageSending() |
||||
{ |
||||
friendMessageDispatcher->sendMessage(false, "Test"); |
||||
|
||||
QVERIFY(messageSender->numSentMessages == 1); |
||||
QVERIFY(messageSender->numSentActions == 0); |
||||
|
||||
friendMessageDispatcher->sendMessage(true, "Test"); |
||||
|
||||
QVERIFY(messageSender->numSentMessages == 1); |
||||
QVERIFY(messageSender->numSentActions == 1); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Tests that messages dispatched while a friend is offline are sent later |
||||
*/ |
||||
void TestFriendMessageDispatcher::testOfflineMessages() |
||||
{ |
||||
f->setStatus(Status::Status::Offline); |
||||
auto firstReceipt = messageSender->receiptNum; |
||||
|
||||
friendMessageDispatcher->sendMessage(false, "test"); |
||||
friendMessageDispatcher->sendMessage(false, "test2"); |
||||
friendMessageDispatcher->sendMessage(true, "test3"); |
||||
|
||||
QVERIFY(messageSender->numSentActions == 0); |
||||
QVERIFY(messageSender->numSentMessages == 0); |
||||
QVERIFY(outgoingMessages.size() == 3); |
||||
|
||||
f->setStatus(Status::Status::Online); |
||||
|
||||
QVERIFY(messageSender->numSentActions == 1); |
||||
QVERIFY(messageSender->numSentMessages == 2); |
||||
QVERIFY(outgoingMessages.size() == 3); |
||||
|
||||
auto lastReceipt = messageSender->receiptNum; |
||||
for (auto i = firstReceipt; i < lastReceipt; ++i.get()) { |
||||
friendMessageDispatcher->onReceiptReceived(i); |
||||
} |
||||
|
||||
QVERIFY(messageSender->numSentActions == 1); |
||||
QVERIFY(messageSender->numSentMessages == 2); |
||||
QVERIFY(outgoingMessages.size() == 0); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Tests that messages that failed to send due to toxcore are resent later |
||||
*/ |
||||
void TestFriendMessageDispatcher::testFailedMessage() |
||||
{ |
||||
messageSender->canSend = false; |
||||
|
||||
friendMessageDispatcher->sendMessage(false, "test"); |
||||
|
||||
QVERIFY(messageSender->numSentMessages == 0); |
||||
|
||||
messageSender->canSend = true; |
||||
f->setStatus(Status::Status::Offline); |
||||
f->setStatus(Status::Status::Online); |
||||
|
||||
QVERIFY(messageSender->numSentMessages == 1); |
||||
} |
||||
|
||||
QTEST_GUILESS_MAIN(TestFriendMessageDispatcher) |
||||
#include "friendmessagedispatcher_test.moc" |
||||
Loading…
Reference in new issue