1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

refactor(messages): Create class to manage sending/receiving group messages from core

This commit is contained in:
Mick Sayson 2019-06-06 08:41:21 -07:00
parent 3fd4ce5952
commit f0d840002a
20 changed files with 886 additions and 34 deletions

View File

@ -331,6 +331,10 @@ set(${PROJECT_NAME}_SOURCES
src/model/imessagedispatcher.h
src/model/friendmessagedispatcher.h
src/model/friendmessagedispatcher.cpp
src/model/groupmessagedispatcher.h
src/model/groupmessagedispatcher.cpp
src/model/message.h
src/model/message.cpp
src/model/groupinvite.cpp
src/model/groupinvite.h
src/model/group.cpp

View File

@ -29,6 +29,8 @@ auto_test(persistence paths)
auto_test(persistence dbschema)
auto_test(persistence offlinemsgengine)
auto_test(model friendmessagedispatcher)
auto_test(model groupmessagedispatcher)
auto_test(model messageprocessor)
if (UNIX)
auto_test(platform posixsignalnotifier)

View File

@ -23,6 +23,9 @@
#include "groupid.h"
#include "icorefriendmessagesender.h"
#include "icoregroupmessagesender.h"
#include "icoregroupquery.h"
#include "icoreidhandler.h"
#include "receiptnum.h"
#include "toxfile.h"
#include "toxid.h"
@ -50,7 +53,11 @@ class Core;
using ToxCorePtr = std::unique_ptr<Core>;
class Core : public QObject, public ICoreFriendMessageSender
class Core : public QObject,
public ICoreFriendMessageSender,
public ICoreIdHandler,
public ICoreGroupMessageSender,
public ICoreGroupQuery
{
Q_OBJECT
public:
@ -74,12 +81,12 @@ public:
static QStringList splitMessage(const QString& message);
QString getPeerName(const ToxPk& id) const;
QVector<uint32_t> getFriendList() const;
GroupId getGroupPersistentId(uint32_t groupNumber) const;
uint32_t getGroupNumberPeers(int groupId) const;
QString getGroupPeerName(int groupId, int peerId) const;
ToxPk getGroupPeerPk(int groupId, int peerId) const;
QStringList getGroupPeerNames(int groupId) const;
bool getGroupAvEnabled(int groupId) const;
GroupId getGroupPersistentId(uint32_t groupNumber) const override;
uint32_t getGroupNumberPeers(int groupId) const override;
QString getGroupPeerName(int groupId, int peerId) const override;
ToxPk getGroupPeerPk(int groupId, int peerId) const override;
QStringList getGroupPeerNames(int groupId) const override;
bool getGroupAvEnabled(int groupId) const override;
ToxPk getFriendPublicKey(uint32_t friendNumber) const;
QString getFriendUsername(uint32_t friendNumber) const;
@ -88,11 +95,11 @@ public:
uint32_t joinGroupchat(const GroupInvite& inviteInfo);
void quitGroupChat(int groupId) const;
QString getUsername() const;
QString getUsername() const override;
Status::Status getStatus() const;
QString getStatusMessage() const;
ToxId getSelfId() const;
ToxPk getSelfPublicKey() const;
ToxId getSelfId() const override;
ToxPk getSelfPublicKey() const override;
QPair<QByteArray, QByteArray> getKeypair() const;
void sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize);
@ -115,8 +122,8 @@ public slots:
void setStatusMessage(const QString& message);
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override;
void sendGroupMessage(int groupId, const QString& message);
void sendGroupAction(int groupId, const QString& message);
void sendGroupMessage(int groupId, const QString& message) override;
void sendGroupAction(int groupId, const QString& message) override;
void changeGroupTitle(int groupId, const QString& title);
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override;
void sendTyping(uint32_t friendId, bool typing);

View 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 ICORE_GROUP_MESSAGE_SENDER_H
#define ICORE_GROUP_MESSAGE_SENDER_H
#include <QString>
class ICoreGroupMessageSender
{
public:
virtual ~ICoreGroupMessageSender() = default;
virtual void sendGroupAction(int groupId, const QString& message) = 0;
virtual void sendGroupMessage(int groupId, const QString& message) = 0;
};
#endif /*ICORE_GROUP_MESSAGE_SENDER_H*/

View File

@ -0,0 +1,44 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
Copyright © 2014-2018 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is free 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_GROUP_QUERY_H
#define ICORE_GROUP_QUERY_H
#include "groupid.h"
#include "toxpk.h"
#include <QString>
#include <QStringList>
#include <cstdint>
class ICoreGroupQuery
{
public:
virtual ~ICoreGroupQuery() = default;
virtual GroupId getGroupPersistentId(uint32_t groupNumber) const = 0;
virtual uint32_t getGroupNumberPeers(int groupId) const = 0;
virtual QString getGroupPeerName(int groupId, int peerId) const = 0;
virtual ToxPk getGroupPeerPk(int groupId, int peerId) const = 0;
virtual QStringList getGroupPeerNames(int groupId) const = 0;
virtual bool getGroupAvEnabled(int groupId) const = 0;
};
#endif /*ICORE_GROUP_QUERY_H*/

37
src/core/icoreidhandler.h Normal file
View File

@ -0,0 +1,37 @@
/*
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_ID_HANDLER_H
#define ICORE_ID_HANDLER_H
#include "toxid.h"
#include "toxpk.h"
class ICoreIdHandler
{
public:
virtual ~ICoreIdHandler() = default;
virtual ToxId getSelfId() const = 0;
virtual ToxPk getSelfPublicKey() const = 0;
virtual QString getUsername() const = 0;
};
#endif /*ICORE_ID_HANDLER_H*/

View File

@ -18,6 +18,7 @@
*/
#include "grouplist.h"
#include "src/core/core.h"
#include "src/model/group.h"
#include <QDebug>
#include <QHash>
@ -31,7 +32,10 @@ Group* GroupList::addGroup(int groupNum, const GroupId& groupId, const QString&
if (checker != groupList.end())
qWarning() << "addGroup: groupId already taken";
Group* newGroup = new Group(groupNum, groupId, name, isAvGroupchat, selfName);
// TODO: Core instance is bad but grouplist is also an instance so we can
// deal with this later
auto core = Core::getInstance();
Group* newGroup = new Group(groupNum, groupId, name, isAvGroupchat, selfName, *core, *core);
groupList[groupId] = newGroup;
id2key[groupNum] = groupId;
return newGroup;

View File

@ -42,10 +42,12 @@ bool sendMessageToCore(ICoreFriendMessageSender& messageSender, const Friend& f,
}
} // namespace
FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, ICoreFriendMessageSender& messageSender_)
FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, MessageProcessor processor_,
ICoreFriendMessageSender& messageSender_)
: f(f_)
, messageSender(messageSender_)
, offlineMsgEngine(&f_, &messageSender_)
, processor(std::move(processor_))
{
connect(&f, &Friend::statusChanged, this, &FriendMessageDispatcher::onFriendStatusChange);
}
@ -58,7 +60,7 @@ FriendMessageDispatcher::sendMessage(bool isAction, const QString& content)
{
const auto firstId = nextMessageId;
auto lastId = nextMessageId;
for (const auto& message : processOutgoingMessage(isAction, content)) {
for (const auto& message : processor.processOutgoingMessage(isAction, content)) {
auto messageId = nextMessageId++;
lastId = messageId;
auto onOfflineMsgComplete = [this, messageId] { emit this->messageComplete(messageId); };
@ -89,7 +91,7 @@ FriendMessageDispatcher::sendMessage(bool isAction, const QString& content)
*/
void FriendMessageDispatcher::onMessageReceived(bool isAction, const QString& content)
{
emit this->messageReceived(f.getPublicKey(), processIncomingMessage(isAction, content));
emit this->messageReceived(f.getPublicKey(), processor.processIncomingMessage(isAction, content));
}
/**

View File

@ -35,7 +35,8 @@ class FriendMessageDispatcher : public IMessageDispatcher
{
Q_OBJECT
public:
FriendMessageDispatcher(Friend& f, ICoreFriendMessageSender& messageSender);
FriendMessageDispatcher(Friend& f, MessageProcessor processor,
ICoreFriendMessageSender& messageSender);
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction,
const QString& content) override;
@ -51,6 +52,7 @@ private:
ICoreFriendMessageSender& messageSender;
OfflineMsgEngine offlineMsgEngine;
MessageProcessor processor;
};

View File

@ -33,12 +33,14 @@
static const int MAX_GROUP_TITLE_LENGTH = 128;
Group::Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat,
const QString& selfName)
const QString& selfName, ICoreGroupQuery& groupQuery, ICoreIdHandler& idHandler)
: selfName{selfName}
, title{name}
, toxGroupNum(groupId)
, groupId{persistentGroupId}
, avGroupchat{isAvGroupchat}
, groupQuery(groupQuery)
, idHandler(idHandler)
{
// in groupchats, we only notify on messages containing your name <-- dumb
// sound notifications should be on all messages, but system popup notification
@ -88,15 +90,14 @@ void Group::regeneratePeerList()
// receive the name changed signal a little later, we will emit userJoined before we have their
// username, using just their ToxPk, then shortly after emit another peerNameChanged signal.
// This can cause double-updated to UI and chatlog, but is unavoidable given the API of toxcore.
const Core* core = Core::getInstance();
QStringList peers = core->getGroupPeerNames(toxGroupNum);
QStringList peers = groupQuery.getGroupPeerNames(toxGroupNum);
const auto oldPeerNames = peerDisplayNames;
peerDisplayNames.clear();
const int nPeers = peers.size();
for (int i = 0; i < nPeers; ++i) {
const auto pk = core->getGroupPeerPk(toxGroupNum, i);
if (pk == core->getSelfPublicKey()) {
peerDisplayNames[pk] = core->getUsername();
const auto pk = groupQuery.getGroupPeerPk(toxGroupNum, i);
if (pk == idHandler.getSelfPublicKey()) {
peerDisplayNames[pk] = idHandler.getUsername();
} else {
peerDisplayNames[pk] = FriendList::decideNickname(pk, peers[i]);
}

View File

@ -24,6 +24,8 @@
#include "src/core/contactid.h"
#include "src/core/groupid.h"
#include "src/core/icoregroupquery.h"
#include "src/core/icoreidhandler.h"
#include "src/core/toxpk.h"
#include <QMap>
@ -34,7 +36,8 @@ class Group : public Contact
{
Q_OBJECT
public:
Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat, const QString& selfName);
Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat,
const QString& selfName, ICoreGroupQuery& groupQuery, ICoreIdHandler& idHandler);
bool isAvGroupchat() const;
uint32_t getId() const override;
const GroupId& getPersistentId() const override;
@ -70,6 +73,8 @@ private:
void stopAudioOfDepartedPeers(const ToxPk& peerPk);
private:
ICoreGroupQuery& groupQuery;
ICoreIdHandler& idHandler;
QString selfName;
QString title;
QMap<ToxPk, QString> peerDisplayNames;

View File

@ -0,0 +1,88 @@
/*
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 "groupmessagedispatcher.h"
#include "src/persistence/igroupsettings.h"
#include <QtCore>
GroupMessageDispatcher::GroupMessageDispatcher(Group& g_, MessageProcessor processor_,
ICoreIdHandler& idHandler_,
ICoreGroupMessageSender& messageSender_,
const IGroupSettings& groupSettings_)
: group(g_)
, processor(processor_)
, idHandler(idHandler_)
, messageSender(messageSender_)
, groupSettings(groupSettings_)
{
processor.enableMentions();
}
std::pair<DispatchedMessageId, DispatchedMessageId>
GroupMessageDispatcher::sendMessage(bool isAction, QString const& content)
{
const auto firstMessageId = nextMessageId;
auto lastMessageId = firstMessageId;
for (auto const& message : processor.processOutgoingMessage(isAction, content)) {
auto messageId = nextMessageId++;
lastMessageId = messageId;
if (group.getPeersCount() != 1) {
if (message.isAction) {
messageSender.sendGroupAction(group.getId(), message.content);
} else {
messageSender.sendGroupMessage(group.getId(), message.content);
}
}
// Emit both signals since we do not have receipts for groups
//
// NOTE: We could in theory keep track of our sent message and wait for
// toxcore to send it back to us to indicate a completed message, but
// this isn't necessarily the design of toxcore and associating the
// received message back would be difficult.
emit this->messageSent(messageId, message);
emit this->messageComplete(messageId);
}
return std::make_pair(firstMessageId, lastMessageId);
}
/**
* @brief Processes and dispatches received message from toxcore
* @param[in] sender
* @param[in] isAction True if is action
* @param[in] content Message content
*/
void GroupMessageDispatcher::onMessageReceived(const ToxPk& sender, bool isAction, QString const& content)
{
bool isSelf = sender == idHandler.getSelfPublicKey();
if (isSelf) {
return;
}
if (groupSettings.getBlackList().contains(sender.toString())) {
qDebug() << "onGroupMessageReceived: Filtered:" << sender.toString();
return;
}
emit messageReceived(sender, processor.processIncomingMessage(isAction, content));
}

View File

@ -0,0 +1,58 @@
/*
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 GROUP_MESSAGE_DISPATCHER_H
#define GROUP_MESSAGE_DISPATCHER_H
#include "src/core/icoregroupmessagesender.h"
#include "src/core/icoreidhandler.h"
#include "src/model/group.h"
#include "src/model/imessagedispatcher.h"
#include "src/model/message.h"
#include <QObject>
#include <QString>
#include <cstdint>
class IGroupSettings;
class GroupMessageDispatcher : public IMessageDispatcher
{
Q_OBJECT
public:
GroupMessageDispatcher(Group& group, MessageProcessor processor, ICoreIdHandler& idHandler,
ICoreGroupMessageSender& messageSender,
const IGroupSettings& groupSettings);
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction,
QString const& content) override;
void onMessageReceived(ToxPk const& sender, bool isAction, QString const& content);
private:
Group& group;
MessageProcessor processor;
ICoreIdHandler& idHandler;
ICoreGroupMessageSender& messageSender;
const IGroupSettings& groupSettings;
DispatchedMessageId nextMessageId{0};
};
#endif /* IMESSAGE_DISPATCHER_H */

View File

@ -18,9 +18,25 @@
*/
#include "message.h"
#include "friend.h"
#include "src/core/core.h"
std::vector<Message> processOutgoingMessage(bool isAction, const QString& content)
void MessageProcessor::SharedParams::onUserNameSet(const QString& username)
{
QString sanename = username;
sanename.remove(QRegExp("[\\t\\n\\v\\f\\r\\x0000]"));
nameMention = QRegExp("\\b" + QRegExp::escape(username) + "\\b", Qt::CaseInsensitive);
sanitizedNameMention = nameMention;
}
MessageProcessor::MessageProcessor(const MessageProcessor::SharedParams& sharedParams)
: sharedParams(sharedParams)
{}
/**
* @brief Converts an outgoing message into one (or many) sanitized Message(s)
*/
std::vector<Message> MessageProcessor::processOutgoingMessage(bool isAction, QString const& content)
{
std::vector<Message> ret;
@ -40,12 +56,34 @@ std::vector<Message> processOutgoingMessage(bool isAction, const QString& conten
return ret;
}
Message processIncomingMessage(bool isAction, const QString& message)
/**
* @brief Converts an incoming message into a sanitized Message
*/
Message MessageProcessor::processIncomingMessage(bool isAction, QString const& message)
{
QDateTime timestamp = QDateTime::currentDateTime();
auto ret = Message{};
ret.isAction = isAction;
ret.content = message;
ret.timestamp = timestamp;
if (detectingMentions) {
auto nameMention = sharedParams.GetNameMention();
auto sanitizedNameMention = sharedParams.GetSanitizedNameMention();
for (auto const& mention : {nameMention, sanitizedNameMention}) {
if (mention.indexIn(ret.content) == -1) {
continue;
}
auto pos = static_cast<size_t>(mention.pos(0));
auto length = static_cast<size_t>(mention.matchedLength());
ret.metadata.push_back({MessageMetadataType::selfMention, pos, pos + length});
break;
}
}
return ret;
}

View File

@ -25,15 +25,87 @@
#include <vector>
class Friend;
// NOTE: This could be extended in the future to handle all text processing (see
// ChatMessage::createChatMessage)
enum class MessageMetadataType
{
selfMention,
};
// May need to be extended in the future to have a more varianty type (imagine
// if we wanted to add message replies and shoved a reply id in here)
struct MessageMetadata
{
MessageMetadataType type;
// Indicates start position within a Message::content
size_t start;
// Indicates end position within a Message::content
size_t end;
};
struct Message
{
bool isAction;
QString content;
QDateTime timestamp;
std::vector<MessageMetadata> metadata;
};
std::vector<Message> processOutgoingMessage(bool isAction, const QString& content);
Message processIncomingMessage(bool isAction, const QString& message);
class MessageProcessor
{
public:
/**
* Parameters needed by all message processors. Used to reduce duplication
* of expensive data looked at by all message processors
*/
class SharedParams
{
public:
QRegExp GetNameMention() const
{
return nameMention;
}
QRegExp GetSanitizedNameMention() const
{
return sanitizedNameMention;
}
void onUserNameSet(const QString& username);
private:
QRegExp nameMention;
QRegExp sanitizedNameMention;
};
MessageProcessor(const SharedParams& sharedParams);
std::vector<Message> processOutgoingMessage(bool isAction, QString const& content);
Message processIncomingMessage(bool isAction, QString const& message);
/**
* @brief Enables mention detection in the processor
*/
inline void enableMentions()
{
detectingMentions = true;
}
/**
* @brief Disables mention detection in the processor
*/
inline void disableMentions()
{
detectingMentions = false;
};
private:
bool detectingMentions = false;
const SharedParams& sharedParams;
};
#endif /*MESSAGE_H*/

View File

@ -0,0 +1,35 @@
/*
Copyright © 2014-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 IGROUP_SETTINGS_H
#define IGROUP_SETTINGS_H
#include <QStringList>
class IGroupSettings
{
public:
virtual ~IGroupSettings() = default;
virtual QStringList getBlackList() const = 0;
virtual void setBlackList(const QStringList& blist) = 0;
virtual bool getGroupAlwaysNotify() const = 0;
virtual void setGroupAlwaysNotify(bool newValue) = 0;
};
#endif /*IGROUP_SETTINGS_H*/

View File

@ -26,6 +26,7 @@
#include "src/core/toxencrypt.h"
#include "src/core/toxfile.h"
#include "src/persistence/ifriendsettings.h"
#include "src/persistence/igroupsettings.h"
#include "src/video/ivideosettings.h"
#include <QDateTime>
@ -46,6 +47,7 @@ enum class syncType;
class Settings : public QObject,
public ICoreSettings,
public IFriendSettings,
public IGroupSettings,
public IAudioSettings,
public IVideoSettings
{
@ -343,8 +345,8 @@ public:
bool getBusySound() const;
void setBusySound(bool newValue);
bool getGroupAlwaysNotify() const;
void setGroupAlwaysNotify(bool newValue);
bool getGroupAlwaysNotify() const override;
void setGroupAlwaysNotify(bool newValue) override;
QString getInDev() const override;
void setInDev(const QString& deviceSpecifier) override;
@ -476,8 +478,8 @@ public:
// Privacy
bool getTypingNotification() const;
void setTypingNotification(bool enabled);
QStringList getBlackList() const;
void setBlackList(const QStringList& blist);
QStringList getBlackList() const override;
void setBlackList(const QStringList& blist) override;
// State
QByteArray getWindowGeometry() const;

View File

@ -74,6 +74,8 @@ private:
// All unique_ptrs to make construction/init() easier to manage
std::unique_ptr<Friend> f;
std::unique_ptr<MockFriendMessageSender> messageSender;
std::unique_ptr<MessageProcessor::SharedParams> sharedProcessorParams;
std::unique_ptr<MessageProcessor> messageProcessor;
std::unique_ptr<FriendMessageDispatcher> friendMessageDispatcher;
std::map<DispatchedMessageId, Message> outgoingMessages;
std::deque<Message> receivedMessages;
@ -89,8 +91,11 @@ 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));
sharedProcessorParams =
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams());
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
friendMessageDispatcher = std::unique_ptr<FriendMessageDispatcher>(
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender));
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this,
&TestFriendMessageDispatcher::onMessageSent);

View File

@ -0,0 +1,300 @@
#include "src/core/icoregroupmessagesender.h"
#include "src/model/group.h"
#include "src/model/groupmessagedispatcher.h"
#include "src/model/message.h"
#include "src/persistence/settings.h"
#include <QObject>
#include <QtTest/QtTest>
#include <deque>
class MockGroupMessageSender : public ICoreGroupMessageSender
{
public:
void sendGroupAction(int groupId, const QString& action) override
{
numSentActions++;
}
void sendGroupMessage(int groupId, const QString& message) override
{
numSentMessages++;
}
size_t numSentActions = 0;
size_t numSentMessages = 0;
};
/**
* Mock 1 peer at group number 0
*/
class MockGroupQuery : public ICoreGroupQuery
{
public:
GroupId getGroupPersistentId(uint32_t groupNumber) const override
{
return GroupId(0);
}
uint32_t getGroupNumberPeers(int groupId) const override
{
if (emptyGroup) {
return 1;
}
return 2;
}
QString getGroupPeerName(int groupId, int peerId) const override
{
return QString("peer") + peerId;
}
ToxPk getGroupPeerPk(int groupId, int peerId) const override
{
uint8_t id[TOX_PUBLIC_KEY_SIZE] = {static_cast<uint8_t>(peerId)};
return ToxPk(id);
}
QStringList getGroupPeerNames(int groupId) const override
{
if (emptyGroup) {
return QStringList({QString("me")});
}
return QStringList({QString("me"), QString("other")});
}
bool getGroupAvEnabled(int groupId) const override
{
return false;
}
void setAsEmptyGroup()
{
emptyGroup = true;
}
void setAsFunctionalGroup()
{
emptyGroup = false;
}
private:
bool emptyGroup = false;
};
class MockCoreIdHandler : public ICoreIdHandler
{
public:
ToxId getSelfId() const override
{
std::terminate();
return ToxId();
}
ToxPk getSelfPublicKey() const override
{
static uint8_t id[TOX_PUBLIC_KEY_SIZE] = {0};
return ToxPk(id);
}
QString getUsername() const override
{
return "me";
}
};
class MockGroupSettings : public IGroupSettings
{
public:
QStringList getBlackList() const override
{
return blacklist;
}
void setBlackList(const QStringList& blist) override
{
blacklist = blist;
}
bool getGroupAlwaysNotify() const override
{
return false;
}
void setGroupAlwaysNotify(bool newValue) override {}
private:
QStringList blacklist;
};
class TestGroupMessageDispatcher : public QObject
{
Q_OBJECT
public:
TestGroupMessageDispatcher();
private slots:
void init();
void testSignals();
void testMessageSending();
void testEmptyGroup();
void testSelfReceive();
void testBlacklist();
void onMessageSent(DispatchedMessageId id, Message message)
{
auto it = outgoingMessages.find(id);
QVERIFY(it == outgoingMessages.end());
outgoingMessages.emplace(id);
sentMessages.push_back(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<MockGroupSettings> groupSettings;
std::unique_ptr<MockGroupQuery> groupQuery;
std::unique_ptr<MockCoreIdHandler> coreIdHandler;
std::unique_ptr<Group> g;
std::unique_ptr<MockGroupMessageSender> messageSender;
std::unique_ptr<MessageProcessor::SharedParams> sharedProcessorParams;
std::unique_ptr<MessageProcessor> messageProcessor;
std::unique_ptr<GroupMessageDispatcher> groupMessageDispatcher;
std::set<DispatchedMessageId> outgoingMessages;
std::deque<Message> sentMessages;
std::deque<Message> receivedMessages;
};
TestGroupMessageDispatcher::TestGroupMessageDispatcher() {}
/**
* @brief Test initialization. Resets all members to initial state
*/
void TestGroupMessageDispatcher::init()
{
groupSettings = std::unique_ptr<MockGroupSettings>(new MockGroupSettings());
groupQuery = std::unique_ptr<MockGroupQuery>(new MockGroupQuery());
coreIdHandler = std::unique_ptr<MockCoreIdHandler>(new MockCoreIdHandler());
g = std::unique_ptr<Group>(
new Group(0, GroupId(0), "TestGroup", false, "me", *groupQuery, *coreIdHandler));
messageSender = std::unique_ptr<MockGroupMessageSender>(new MockGroupMessageSender());
sharedProcessorParams =
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams());
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
groupMessageDispatcher = std::unique_ptr<GroupMessageDispatcher>(
new GroupMessageDispatcher(*g, *messageProcessor, *coreIdHandler, *messageSender,
*groupSettings));
connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageSent, this,
&TestGroupMessageDispatcher::onMessageSent);
connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageComplete, this,
&TestGroupMessageDispatcher::onMessageComplete);
connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageReceived, this,
&TestGroupMessageDispatcher::onMessageReceived);
outgoingMessages = std::set<DispatchedMessageId>();
sentMessages = std::deque<Message>();
receivedMessages = std::deque<Message>();
}
/**
* @brief Tests that the signals emitted by the dispatcher are all emitted at the correct times
*/
void TestGroupMessageDispatcher::testSignals()
{
groupMessageDispatcher->sendMessage(false, "test");
// For groups we pair our sent and completed signals since we have no receiver reports
QVERIFY(outgoingMessages.size() == 0);
QVERIFY(!sentMessages.empty());
QVERIFY(sentMessages.front().isAction == false);
QVERIFY(sentMessages.front().content == "test");
// If signals are emitted correctly we should have one message in our received message buffer
QVERIFY(receivedMessages.empty());
groupMessageDispatcher->onMessageReceived(ToxPk(), 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 TestGroupMessageDispatcher::testMessageSending()
{
groupMessageDispatcher->sendMessage(false, "Test");
QVERIFY(messageSender->numSentMessages == 1);
QVERIFY(messageSender->numSentActions == 0);
groupMessageDispatcher->sendMessage(true, "Test");
QVERIFY(messageSender->numSentMessages == 1);
QVERIFY(messageSender->numSentActions == 1);
}
/**
* @brief Tests that if we are the only member in a group we do _not_ send messages to core. Toxcore
* isn't too happy if we send messages and we're the only one in the group
*/
void TestGroupMessageDispatcher::testEmptyGroup()
{
groupQuery->setAsEmptyGroup();
g->regeneratePeerList();
groupMessageDispatcher->sendMessage(false, "Test");
groupMessageDispatcher->sendMessage(true, "Test");
QVERIFY(messageSender->numSentMessages == 0);
QVERIFY(messageSender->numSentActions == 0);
}
/**
* @brief Tests that we do not emit any signals if we receive a message from ourself. Toxcore will send us back messages we sent
*/
void TestGroupMessageDispatcher::testSelfReceive()
{
uint8_t selfId[TOX_PUBLIC_KEY_SIZE] = {0};
groupMessageDispatcher->onMessageReceived(ToxPk(selfId), false, "Test");
QVERIFY(receivedMessages.size() == 0);
uint8_t id[TOX_PUBLIC_KEY_SIZE] = {1};
groupMessageDispatcher->onMessageReceived(ToxPk(id), false, "Test");
QVERIFY(receivedMessages.size() == 1);
}
/**
* @brief Tests that messages from blacklisted peers do not get propogated from the dispatcher
*/
void TestGroupMessageDispatcher::testBlacklist()
{
uint8_t id[TOX_PUBLIC_KEY_SIZE] = {1};
auto otherPk = ToxPk(id);
groupMessageDispatcher->onMessageReceived(otherPk, false, "Test");
QVERIFY(receivedMessages.size() == 1);
groupSettings->setBlackList({otherPk.toString()});
groupMessageDispatcher->onMessageReceived(otherPk, false, "Test");
QVERIFY(receivedMessages.size() == 1);
}
// Cannot be guiless due to a settings instance in GroupMessageDispatcher
QTEST_GUILESS_MAIN(TestGroupMessageDispatcher)
#include "groupmessagedispatcher_test.moc"

View File

@ -0,0 +1,113 @@
#include "src/model/message.h"
#include <tox/tox.h>
#include <QObject>
#include <QtTest/QtTest>
namespace {
bool messageHasSelfMention(const Message& message)
{
return std::any_of(message.metadata.begin(), message.metadata.end(), [](MessageMetadata meta) {
return meta.type == MessageMetadataType::selfMention;
});
}
} // namespace
class TestMessageProcessor : public QObject
{
Q_OBJECT
public:
TestMessageProcessor(){};
private slots:
void testSelfMention();
void testOutgoingMessage();
void testIncomingMessage();
};
/**
* @brief Tests detection of username
*/
void TestMessageProcessor::testSelfMention()
{
MessageProcessor::SharedParams sharedParams;
sharedParams.onUserNameSet("MyUserName");
auto messageProcessor = MessageProcessor(sharedParams);
messageProcessor.enableMentions();
// Using my name should match
auto processedMessage = messageProcessor.processIncomingMessage(false, "MyUserName hi");
QVERIFY(messageHasSelfMention(processedMessage));
// Action messages should match too
processedMessage = messageProcessor.processIncomingMessage(true, "MyUserName hi");
QVERIFY(messageHasSelfMention(processedMessage));
// Too much text shouldn't match
processedMessage = messageProcessor.processIncomingMessage(false, "MyUserName2");
QVERIFY(!messageHasSelfMention(processedMessage));
// Unless it's a colon
processedMessage = messageProcessor.processIncomingMessage(false, "MyUserName: test");
QVERIFY(messageHasSelfMention(processedMessage));
// Too little text shouldn't match
processedMessage = messageProcessor.processIncomingMessage(false, "MyUser");
QVERIFY(!messageHasSelfMention(processedMessage));
// The regex should be case insensitive
processedMessage = messageProcessor.processIncomingMessage(false, "myusername hi");
QVERIFY(messageHasSelfMention(processedMessage));
// New user name changes should be detected
sharedParams.onUserNameSet("NewUserName");
processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi");
QVERIFY(messageHasSelfMention(processedMessage));
// Special characters should be removed
sharedParams.onUserNameSet("New\nUserName");
processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi");
QVERIFY(messageHasSelfMention(processedMessage));
}
/**
* @brief Tests behavior of the processor for outgoing messages
*/
void TestMessageProcessor::testOutgoingMessage()
{
auto sharedParams = MessageProcessor::SharedParams();
auto messageProcessor = MessageProcessor(sharedParams);
QString testStr;
for (size_t i = 0; i < tox_max_message_length() + 50; ++i) {
testStr += "a";
}
auto messages = messageProcessor.processOutgoingMessage(false, testStr);
// The message processor should split our messages
QVERIFY(messages.size() == 2);
}
/**
* @brief Tests behavior of the processor for incoming messages
*/
void TestMessageProcessor::testIncomingMessage()
{
// Nothing too special happening on the incoming side if we aren't looking for self mentions
auto sharedParams = MessageProcessor::SharedParams();
auto messageProcessor = MessageProcessor(sharedParams);
auto message = messageProcessor.processIncomingMessage(false, "test");
QVERIFY(message.isAction == false);
QVERIFY(message.content == "test");
QVERIFY(message.timestamp.isValid());
}
QTEST_GUILESS_MAIN(TestMessageProcessor)
#include "messageprocessor_test.moc"