mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
refactor(offlinemsg): Decouple OfflineMsgEngine from other components
This commit is contained in:
parent
d934cf372b
commit
e5016337bb
|
@ -27,6 +27,7 @@ auto_test(net toxmedata)
|
||||||
auto_test(net bsu)
|
auto_test(net bsu)
|
||||||
auto_test(persistence paths)
|
auto_test(persistence paths)
|
||||||
auto_test(persistence dbschema)
|
auto_test(persistence dbschema)
|
||||||
|
auto_test(persistence offlinemsgengine)
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
auto_test(platform posixsignalnotifier)
|
auto_test(platform posixsignalnotifier)
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
#ifndef CORE_HPP
|
#ifndef CORE_HPP
|
||||||
#define CORE_HPP
|
#define CORE_HPP
|
||||||
|
|
||||||
|
#include "groupid.h"
|
||||||
|
#include "icorefriendmessagesender.h"
|
||||||
|
#include "receiptnum.h"
|
||||||
#include "toxfile.h"
|
#include "toxfile.h"
|
||||||
#include "toxid.h"
|
#include "toxid.h"
|
||||||
#include "toxpk.h"
|
#include "toxpk.h"
|
||||||
#include "groupid.h"
|
|
||||||
|
|
||||||
#include "src/util/strongtype.h"
|
#include "src/util/strongtype.h"
|
||||||
#include "src/model/status.h"
|
#include "src/model/status.h"
|
||||||
|
@ -47,10 +49,8 @@ class Profile;
|
||||||
class Core;
|
class Core;
|
||||||
|
|
||||||
using ToxCorePtr = std::unique_ptr<Core>;
|
using ToxCorePtr = std::unique_ptr<Core>;
|
||||||
using ReceiptNum = NamedType<uint32_t, struct ReceiptNumTag>;
|
|
||||||
Q_DECLARE_METATYPE(ReceiptNum);
|
|
||||||
|
|
||||||
class Core : public QObject
|
class Core : public QObject, public ICoreFriendMessageSender
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -114,11 +114,11 @@ public slots:
|
||||||
void setUsername(const QString& username);
|
void setUsername(const QString& username);
|
||||||
void setStatusMessage(const QString& message);
|
void setStatusMessage(const QString& message);
|
||||||
|
|
||||||
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt);
|
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override;
|
||||||
void sendGroupMessage(int groupId, const QString& message);
|
void sendGroupMessage(int groupId, const QString& message);
|
||||||
void sendGroupAction(int groupId, const QString& message);
|
void sendGroupAction(int groupId, const QString& message);
|
||||||
void changeGroupTitle(int groupId, const QString& title);
|
void changeGroupTitle(int groupId, const QString& title);
|
||||||
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt);
|
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override;
|
||||||
void sendTyping(uint32_t friendId, bool typing);
|
void sendTyping(uint32_t friendId, bool typing);
|
||||||
|
|
||||||
void setNospam(uint32_t nospam);
|
void setNospam(uint32_t nospam);
|
||||||
|
|
36
src/core/icorefriendmessagesender.h
Normal file
36
src/core/icorefriendmessagesender.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
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 ICORE_FRIEND_MESSAGE_SENDER_H
|
||||||
|
#define ICORE_FRIEND_MESSAGE_SENDER_H
|
||||||
|
|
||||||
|
#include "receiptnum.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class ICoreFriendMessageSender
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) = 0;
|
||||||
|
virtual bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* ICORE_FRIEND_MESSAGE_SENDER_H */
|
31
src/core/receiptnum.h
Normal file
31
src/core/receiptnum.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
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 RECEIPT_NUM_H
|
||||||
|
#define RECEIPT_NUM_H
|
||||||
|
|
||||||
|
#include "src/util/strongtype.h"
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
using ReceiptNum = NamedType<uint32_t, struct ReceiptNumTag>;
|
||||||
|
Q_DECLARE_METATYPE(ReceiptNum);
|
||||||
|
|
||||||
|
#endif /* RECEIPT_NUM_H */
|
33
src/model/message.h
Normal file
33
src/model/message.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
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 MESSAGE_H
|
||||||
|
#define MESSAGE_H
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
bool isAction;
|
||||||
|
QString content;
|
||||||
|
QDateTime timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /*MESSAGE_H*/
|
|
@ -28,11 +28,11 @@
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
OfflineMsgEngine::OfflineMsgEngine(Friend* frnd)
|
OfflineMsgEngine::OfflineMsgEngine(Friend* frnd, ICoreFriendMessageSender* messageSender)
|
||||||
: mutex(QMutex::Recursive)
|
: mutex(QMutex::Recursive)
|
||||||
, f(frnd)
|
, f(frnd)
|
||||||
{
|
, messageSender(messageSender)
|
||||||
}
|
{}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Notification that the message is now delivered.
|
* @brief Notification that the message is now delivered.
|
||||||
|
@ -59,18 +59,10 @@ void OfflineMsgEngine::onReceiptReceived(ReceiptNum receipt)
|
||||||
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history
|
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history
|
||||||
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp
|
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp
|
||||||
*/
|
*/
|
||||||
void OfflineMsgEngine::addSavedMessage(RowId messageID, ChatMessage::Ptr chatMessage)
|
void OfflineMsgEngine::addUnsentMessage(Message const& message, CompletionFn completionCallback)
|
||||||
{
|
{
|
||||||
QMutexLocker ml(&mutex);
|
QMutexLocker ml(&mutex);
|
||||||
assert([&](){
|
unsentMessages.append(OfflineMessage{message, std::chrono::steady_clock::now(), completionCallback});
|
||||||
for (const auto& message : unsentSavedMessages) {
|
|
||||||
if (message.rowId == messageID) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
unsentSavedMessages.append(Message{chatMessage, messageID, std::chrono::steady_clock::now()});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,11 +75,12 @@ void OfflineMsgEngine::addSavedMessage(RowId messageID, ChatMessage::Ptr chatMes
|
||||||
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history
|
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history
|
||||||
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp
|
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp
|
||||||
*/
|
*/
|
||||||
void OfflineMsgEngine::addSentSavedMessage(ReceiptNum receipt, RowId messageID, ChatMessage::Ptr chatMessage)
|
void OfflineMsgEngine::addSentMessage(ReceiptNum receipt, Message const& message,
|
||||||
|
CompletionFn completionCallback)
|
||||||
{
|
{
|
||||||
QMutexLocker ml(&mutex);
|
QMutexLocker ml(&mutex);
|
||||||
assert(!sentSavedMessages.contains(receipt));
|
assert(!sentMessages.contains(receipt));
|
||||||
sentSavedMessages.insert(receipt, {chatMessage, messageID, std::chrono::steady_clock::now()});
|
sentMessages.insert(receipt, {message, std::chrono::steady_clock::now(), completionCallback});
|
||||||
checkForCompleteMessages(receipt);
|
checkForCompleteMessages(receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,29 +95,31 @@ void OfflineMsgEngine::deliverOfflineMsgs()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sentSavedMessages.empty() && unsentSavedMessages.empty()) {
|
if (sentMessages.empty() && unsentMessages.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<Message> messages = sentSavedMessages.values().toVector() + unsentSavedMessages;
|
QVector<OfflineMessage> messages = sentMessages.values().toVector() + unsentMessages;
|
||||||
// order messages by authorship time to resend in same order as they were written
|
// order messages by authorship time to resend in same order as they were written
|
||||||
qSort(messages.begin(), messages.end(), [](const Message& lhs, const Message& rhs){ return lhs.authorshipTime < rhs.authorshipTime; });
|
qSort(messages.begin(), messages.end(), [](const OfflineMessage& lhs, const OfflineMessage& rhs) {
|
||||||
|
return lhs.authorshipTime < rhs.authorshipTime;
|
||||||
|
});
|
||||||
removeAllMessages();
|
removeAllMessages();
|
||||||
|
|
||||||
for (const auto& message : messages) {
|
for (const auto& message : messages) {
|
||||||
QString messageText = message.chatMessage->toString();
|
QString messageText = message.message.content;
|
||||||
ReceiptNum receipt;
|
ReceiptNum receipt;
|
||||||
bool messageSent{false};
|
bool messageSent{false};
|
||||||
if (message.chatMessage->isAction()) {
|
if (message.message.isAction) {
|
||||||
messageSent = Core::getInstance()->sendAction(f->getId(), messageText, receipt);
|
messageSent = messageSender->sendAction(f->getId(), messageText, receipt);
|
||||||
} else {
|
} else {
|
||||||
messageSent = Core::getInstance()->sendMessage(f->getId(), messageText, receipt);
|
messageSent = messageSender->sendMessage(f->getId(), messageText, receipt);
|
||||||
}
|
}
|
||||||
if (messageSent) {
|
if (messageSent) {
|
||||||
addSentSavedMessage(receipt, message.rowId, message.chatMessage);
|
addSentMessage(receipt, message.message, message.completionFn);
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "deliverOfflineMsgs failed to send message";
|
qCritical() << "deliverOfflineMsgs failed to send message";
|
||||||
addSavedMessage(message.rowId, message.chatMessage);
|
addUnsentMessage(message.message, message.completionFn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,38 +131,23 @@ void OfflineMsgEngine::removeAllMessages()
|
||||||
{
|
{
|
||||||
QMutexLocker ml(&mutex);
|
QMutexLocker ml(&mutex);
|
||||||
receivedReceipts.clear();
|
receivedReceipts.clear();
|
||||||
sentSavedMessages.clear();
|
sentMessages.clear();
|
||||||
unsentSavedMessages.clear();
|
unsentMessages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OfflineMsgEngine::completeMessage(QMap<ReceiptNum, Message>::iterator msgIt)
|
void OfflineMsgEngine::completeMessage(QMap<ReceiptNum, OfflineMessage>::iterator msgIt)
|
||||||
{
|
{
|
||||||
Profile* const profile = Nexus::getProfile();
|
msgIt->completionFn();
|
||||||
if (profile->isHistoryEnabled()) {
|
sentMessages.erase(msgIt);
|
||||||
profile->getHistory()->markAsSent(msgIt->rowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
|
|
||||||
updateTimestamp(msgIt->chatMessage);
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(this, "updateTimestamp", Qt::BlockingQueuedConnection, Q_ARG(ChatMessage::Ptr, msgIt->chatMessage));
|
|
||||||
}
|
|
||||||
sentSavedMessages.erase(msgIt);
|
|
||||||
receivedReceipts.removeOne(msgIt.key());
|
receivedReceipts.removeOne(msgIt.key());
|
||||||
}
|
}
|
||||||
|
|
||||||
void OfflineMsgEngine::checkForCompleteMessages(ReceiptNum receipt)
|
void OfflineMsgEngine::checkForCompleteMessages(ReceiptNum receipt)
|
||||||
{
|
{
|
||||||
auto msgIt = sentSavedMessages.find(receipt);
|
auto msgIt = sentMessages.find(receipt);
|
||||||
const bool receiptReceived = receivedReceipts.contains(receipt);
|
const bool receiptReceived = receivedReceipts.contains(receipt);
|
||||||
if (!receiptReceived || msgIt == sentSavedMessages.end()) {
|
if (!receiptReceived || msgIt == sentMessages.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(!unsentSavedMessages.contains(*msgIt));
|
|
||||||
completeMessage(msgIt);
|
completeMessage(msgIt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OfflineMsgEngine::updateTimestamp(ChatMessage::Ptr msg)
|
|
||||||
{
|
|
||||||
msg->markAsSent(QDateTime::currentDateTime());
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include "src/chatlog/chatmessage.h"
|
#include "src/chatlog/chatmessage.h"
|
||||||
#include "src/core/core.h"
|
#include "src/core/core.h"
|
||||||
|
#include "src/model/message.h"
|
||||||
#include "src/persistence/db/rawdatabase.h"
|
#include "src/persistence/db/rawdatabase.h"
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
@ -31,14 +32,17 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
class Friend;
|
class Friend;
|
||||||
|
class ICoreFriendMessageSender;
|
||||||
|
|
||||||
class OfflineMsgEngine : public QObject
|
class OfflineMsgEngine : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit OfflineMsgEngine(Friend*);
|
explicit OfflineMsgEngine(Friend* f, ICoreFriendMessageSender* messageSender);
|
||||||
void addSavedMessage(RowId messageID, ChatMessage::Ptr msg);
|
|
||||||
void addSentSavedMessage(ReceiptNum receipt, RowId messageID, ChatMessage::Ptr msg);
|
using CompletionFn = std::function<void()>;
|
||||||
|
void addUnsentMessage(Message const& message, CompletionFn completionCallback);
|
||||||
|
void addSentMessage(ReceiptNum receipt, Message const& message, CompletionFn completionCallback);
|
||||||
void deliverOfflineMsgs();
|
void deliverOfflineMsgs();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -46,28 +50,25 @@ public slots:
|
||||||
void onReceiptReceived(ReceiptNum receipt);
|
void onReceiptReceived(ReceiptNum receipt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Message
|
struct OfflineMessage
|
||||||
{
|
{
|
||||||
bool operator==(const Message& rhs) const { return rhs.rowId == rowId; }
|
Message message;
|
||||||
ChatMessage::Ptr chatMessage;
|
|
||||||
RowId rowId;
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> authorshipTime;
|
std::chrono::time_point<std::chrono::steady_clock> authorshipTime;
|
||||||
|
CompletionFn completionFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void completeMessage(QMap<ReceiptNum, Message>::iterator msgIt);
|
void completeMessage(QMap<ReceiptNum, OfflineMessage>::iterator msgIt);
|
||||||
|
|
||||||
private slots:
|
|
||||||
void updateTimestamp(ChatMessage::Ptr msg);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkForCompleteMessages(ReceiptNum receipt);
|
void checkForCompleteMessages(ReceiptNum receipt);
|
||||||
|
|
||||||
QMutex mutex;
|
QMutex mutex;
|
||||||
const Friend* f;
|
const Friend* f;
|
||||||
|
ICoreFriendMessageSender* messageSender;
|
||||||
QVector<ReceiptNum> receivedReceipts;
|
QVector<ReceiptNum> receivedReceipts;
|
||||||
QMap<ReceiptNum, Message> sentSavedMessages;
|
QMap<ReceiptNum, OfflineMessage> sentMessages;
|
||||||
QVector<Message> unsentSavedMessages;
|
QVector<OfflineMessage> unsentMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OFFLINEMSGENGINE_H
|
#endif // OFFLINEMSGENGINE_H
|
||||||
|
|
|
@ -102,6 +102,30 @@ namespace
|
||||||
|
|
||||||
return cD + res.sprintf("%02ds", seconds);
|
return cD + res.sprintf("%02ds", seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void completeMessage(ChatMessage::Ptr ma, RowId rowId)
|
||||||
|
{
|
||||||
|
auto profile = Nexus::getProfile();
|
||||||
|
if (profile->isHistoryEnabled()) {
|
||||||
|
profile->getHistory()->markAsSent(rowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// force execution on the gui thread
|
||||||
|
QTimer::singleShot(0, QCoreApplication::instance(), [ma] {
|
||||||
|
ma->markAsSent(QDateTime::currentDateTime());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CompleteMessageFunctor
|
||||||
|
{
|
||||||
|
void operator()() const
|
||||||
|
{
|
||||||
|
completeMessage(ma, rowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatMessage::Ptr ma;
|
||||||
|
RowId rowId;
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ChatForm::ChatForm(Friend* chatFriend, History* history)
|
ChatForm::ChatForm(Friend* chatFriend, History* history)
|
||||||
|
@ -122,7 +146,7 @@ ChatForm::ChatForm(Friend* chatFriend, History* history)
|
||||||
statusMessageLabel->setTextFormat(Qt::PlainText);
|
statusMessageLabel->setTextFormat(Qt::PlainText);
|
||||||
statusMessageLabel->setContextMenuPolicy(Qt::CustomContextMenu);
|
statusMessageLabel->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
offlineEngine = new OfflineMsgEngine(f);
|
offlineEngine = new OfflineMsgEngine(f, Core::getInstance());
|
||||||
|
|
||||||
typingTimer.setSingleShot(true);
|
typingTimer.setSingleShot(true);
|
||||||
|
|
||||||
|
@ -919,20 +943,26 @@ void ChatForm::sendLoadedMessage(ChatMessage::Ptr chatMsg, MessageMetadata const
|
||||||
|
|
||||||
ReceiptNum receipt;
|
ReceiptNum receipt;
|
||||||
bool messageSent{false};
|
bool messageSent{false};
|
||||||
|
QString stringMsg = chatMsg->toString();
|
||||||
if (f->isOnline()) {
|
if (f->isOnline()) {
|
||||||
Core* core = Core::getInstance();
|
Core* core = Core::getInstance();
|
||||||
uint32_t friendId = f->getId();
|
uint32_t friendId = f->getId();
|
||||||
QString stringMsg = chatMsg->toString();
|
|
||||||
messageSent = metadata.isAction ? core->sendAction(friendId, stringMsg, receipt)
|
messageSent = metadata.isAction ? core->sendAction(friendId, stringMsg, receipt)
|
||||||
: core->sendMessage(friendId, stringMsg, receipt);
|
: core->sendMessage(friendId, stringMsg, receipt);
|
||||||
if (!messageSent) {
|
if (!messageSent) {
|
||||||
qWarning() << "Failed to send loaded message, adding to offline messaging";
|
qWarning() << "Failed to send loaded message, adding to offline messaging";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto onCompletion = CompleteMessageFunctor{};
|
||||||
|
onCompletion.ma = chatMsg;
|
||||||
|
onCompletion.rowId = metadata.id;
|
||||||
|
|
||||||
|
auto modelMsg = Message{metadata.isAction, stringMsg, QDateTime::currentDateTime()};
|
||||||
if (messageSent) {
|
if (messageSent) {
|
||||||
getOfflineMsgEngine()->addSentSavedMessage(receipt, metadata.id, chatMsg);
|
getOfflineMsgEngine()->addSentMessage(receipt, modelMsg, onCompletion);
|
||||||
} else {
|
} else {
|
||||||
getOfflineMsgEngine()->addSavedMessage(metadata.id, chatMsg);
|
getOfflineMsgEngine()->addUnsentMessage(modelMsg, onCompletion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1136,22 +1166,32 @@ void ChatForm::SendMessageStr(QString msg)
|
||||||
|
|
||||||
ChatMessage::Ptr ma = createSelfMessage(part, timestamp, isAction, false);
|
ChatMessage::Ptr ma = createSelfMessage(part, timestamp, isAction, false);
|
||||||
|
|
||||||
|
Message modelMsg{isAction, part, timestamp};
|
||||||
|
|
||||||
|
|
||||||
if (history && Settings::getInstance().getEnableLogging()) {
|
if (history && Settings::getInstance().getEnableLogging()) {
|
||||||
auto* offMsgEngine = getOfflineMsgEngine();
|
auto* offMsgEngine = getOfflineMsgEngine();
|
||||||
QString selfPk = Core::getInstance()->getSelfId().toString();
|
QString selfPk = Core::getInstance()->getSelfId().toString();
|
||||||
QString pk = f->getPublicKey().toString();
|
QString pk = f->getPublicKey().toString();
|
||||||
QString name = Core::getInstance()->getUsername();
|
QString name = Core::getInstance()->getUsername();
|
||||||
bool const isSent = false; // This forces history to add it to the offline messages table
|
bool const isSent = false; // This forces history to add it to the offline messages table
|
||||||
|
|
||||||
|
// Use functor to avoid having to declare a lambda in a lambda
|
||||||
|
CompleteMessageFunctor onCompletion;
|
||||||
|
onCompletion.ma = ma;
|
||||||
|
|
||||||
history->addNewMessage(pk, historyPart, selfPk, timestamp, isSent, name,
|
history->addNewMessage(pk, historyPart, selfPk, timestamp, isSent, name,
|
||||||
[messageSent, offMsgEngine, receipt, ma](RowId id) {
|
[messageSent, offMsgEngine, receipt, modelMsg,
|
||||||
if (messageSent) {
|
onCompletion](RowId id) mutable {
|
||||||
offMsgEngine->addSentSavedMessage(receipt, id, ma);
|
onCompletion.rowId = id;
|
||||||
} else {
|
if (messageSent) {
|
||||||
offMsgEngine->addSavedMessage(id, ma);
|
offMsgEngine->addSentMessage(receipt, modelMsg,
|
||||||
}
|
onCompletion);
|
||||||
|
} else {
|
||||||
|
offMsgEngine->addUnsentMessage(modelMsg, onCompletion);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// TODO: Make faux-offline messaging work partially with the history disabled
|
|
||||||
ma->markAsSent(QDateTime::currentDateTime());
|
ma->markAsSent(QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
193
test/persistence/offlinemsgengine_test.cpp
Normal file
193
test/persistence/offlinemsgengine_test.cpp
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
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 "src/core/core.h"
|
||||||
|
#include "src/model/friend.h"
|
||||||
|
#include "src/persistence/offlinemsgengine.h"
|
||||||
|
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
|
struct MockFriendMessageSender : public QObject, public ICoreFriendMessageSender
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
MockFriendMessageSender(Friend* f)
|
||||||
|
: f(f){};
|
||||||
|
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override
|
||||||
|
{
|
||||||
|
if (f->isOnline()) {
|
||||||
|
receipt.get() = receiptNum++;
|
||||||
|
if (!dropReceipts) {
|
||||||
|
msgs.push_back(message);
|
||||||
|
emit receiptReceived(receipt);
|
||||||
|
}
|
||||||
|
numMessagesSent++;
|
||||||
|
} else {
|
||||||
|
numMessagesFailed++;
|
||||||
|
}
|
||||||
|
return f->isOnline();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void receiptReceived(ReceiptNum receipt);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Friend* f;
|
||||||
|
bool dropReceipts = false;
|
||||||
|
size_t numMessagesSent = 0;
|
||||||
|
size_t numMessagesFailed = 0;
|
||||||
|
int receiptNum = 0;
|
||||||
|
std::vector<QString> msgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestOfflineMsgEngine : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testReceiptResolution();
|
||||||
|
void testOfflineFriend();
|
||||||
|
void testSentUnsentCoordination();
|
||||||
|
void testCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
class OfflineMsgEngineFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OfflineMsgEngineFixture()
|
||||||
|
: f(0, ToxPk(QByteArray(32, 0)))
|
||||||
|
, friendMessageSender(&f)
|
||||||
|
, offlineMsgEngine(&f, &friendMessageSender)
|
||||||
|
{
|
||||||
|
f.setStatus(Status::Status::Online);
|
||||||
|
QObject::connect(&friendMessageSender, &MockFriendMessageSender::receiptReceived,
|
||||||
|
&offlineMsgEngine, &OfflineMsgEngine::onReceiptReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
Friend f;
|
||||||
|
MockFriendMessageSender friendMessageSender;
|
||||||
|
OfflineMsgEngine offlineMsgEngine;
|
||||||
|
};
|
||||||
|
|
||||||
|
void completionFn() {}
|
||||||
|
|
||||||
|
void TestOfflineMsgEngine::testReceiptResolution()
|
||||||
|
{
|
||||||
|
OfflineMsgEngineFixture fixture;
|
||||||
|
|
||||||
|
Message msg{false, QString(), QDateTime()};
|
||||||
|
|
||||||
|
ReceiptNum receipt;
|
||||||
|
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||||
|
fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn);
|
||||||
|
|
||||||
|
// We should have no offline messages to deliver if we resolved our receipt
|
||||||
|
// correctly
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
|
||||||
|
QVERIFY(fixture.friendMessageSender.numMessagesSent == 1);
|
||||||
|
|
||||||
|
// If we drop receipts we should keep trying to send messages every time we
|
||||||
|
// "deliverOfflineMsgs"
|
||||||
|
fixture.friendMessageSender.dropReceipts = true;
|
||||||
|
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||||
|
fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn);
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
|
||||||
|
QVERIFY(fixture.friendMessageSender.numMessagesSent == 5);
|
||||||
|
|
||||||
|
// And once we stop dropping and try one more time we should run out of
|
||||||
|
// messages to send again
|
||||||
|
fixture.friendMessageSender.dropReceipts = false;
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
QVERIFY(fixture.friendMessageSender.numMessagesSent == 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestOfflineMsgEngine::testOfflineFriend()
|
||||||
|
{
|
||||||
|
OfflineMsgEngineFixture fixture;
|
||||||
|
|
||||||
|
Message msg{false, QString(), QDateTime()};
|
||||||
|
|
||||||
|
fixture.f.setStatus(Status::Status::Offline);
|
||||||
|
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
|
||||||
|
fixture.f.setStatus(Status::Status::Online);
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
|
||||||
|
|
||||||
|
QVERIFY(fixture.friendMessageSender.numMessagesFailed == 0);
|
||||||
|
QVERIFY(fixture.friendMessageSender.numMessagesSent == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestOfflineMsgEngine::testSentUnsentCoordination()
|
||||||
|
{
|
||||||
|
OfflineMsgEngineFixture fixture;
|
||||||
|
Message msg{false, QString("a"), QDateTime()};
|
||||||
|
ReceiptNum receipt;
|
||||||
|
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
msg.content = "b";
|
||||||
|
fixture.friendMessageSender.dropReceipts = true;
|
||||||
|
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||||
|
fixture.friendMessageSender.dropReceipts = false;
|
||||||
|
fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn);
|
||||||
|
msg.content = "c";
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||||
|
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
|
||||||
|
auto expectedResponseOrder = std::vector<QString>{"a", "b", "c"};
|
||||||
|
QVERIFY(fixture.friendMessageSender.msgs == expectedResponseOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestOfflineMsgEngine::testCallback()
|
||||||
|
{
|
||||||
|
OfflineMsgEngineFixture fixture;
|
||||||
|
|
||||||
|
size_t numCallbacks = 0;
|
||||||
|
auto callback = [&numCallbacks] { numCallbacks++; };
|
||||||
|
Message msg{false, QString(), QDateTime()};
|
||||||
|
ReceiptNum receipt;
|
||||||
|
|
||||||
|
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||||
|
fixture.offlineMsgEngine.addSentMessage(receipt, msg, callback);
|
||||||
|
fixture.offlineMsgEngine.addUnsentMessage(msg, callback);
|
||||||
|
|
||||||
|
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||||
|
QVERIFY(numCallbacks == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(TestOfflineMsgEngine)
|
||||||
|
#include "offlinemsgengine_test.moc"
|
Loading…
Reference in New Issue
Block a user