Browse Source

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

reviewable/pr5703/r40
Mick Sayson 6 years ago
parent
commit
f0d840002a
  1. 4
      CMakeLists.txt
  2. 2
      cmake/Testing.cmake
  3. 31
      src/core/core.h
  4. 33
      src/core/icoregroupmessagesender.h
  5. 44
      src/core/icoregroupquery.h
  6. 37
      src/core/icoreidhandler.h
  7. 6
      src/grouplist.cpp
  8. 8
      src/model/friendmessagedispatcher.cpp
  9. 4
      src/model/friendmessagedispatcher.h
  10. 13
      src/model/group.cpp
  11. 7
      src/model/group.h
  12. 88
      src/model/groupmessagedispatcher.cpp
  13. 58
      src/model/groupmessagedispatcher.h
  14. 42
      src/model/message.cpp
  15. 76
      src/model/message.h
  16. 35
      src/persistence/igroupsettings.h
  17. 10
      src/persistence/settings.h
  18. 9
      test/model/friendmessagedispatcher_test.cpp
  19. 300
      test/model/groupmessagedispatcher_test.cpp
  20. 113
      test/model/messageprocessor_test.cpp

4
CMakeLists.txt

@ -331,6 +331,10 @@ set(${PROJECT_NAME}_SOURCES
src/model/imessagedispatcher.h src/model/imessagedispatcher.h
src/model/friendmessagedispatcher.h src/model/friendmessagedispatcher.h
src/model/friendmessagedispatcher.cpp 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.cpp
src/model/groupinvite.h src/model/groupinvite.h
src/model/group.cpp src/model/group.cpp

2
cmake/Testing.cmake

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

31
src/core/core.h

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

33
src/core/icoregroupmessagesender.h

@ -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*/

44
src/core/icoregroupquery.h

@ -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

@ -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*/

6
src/grouplist.cpp

@ -18,6 +18,7 @@
*/ */
#include "grouplist.h" #include "grouplist.h"
#include "src/core/core.h"
#include "src/model/group.h" #include "src/model/group.h"
#include <QDebug> #include <QDebug>
#include <QHash> #include <QHash>
@ -31,7 +32,10 @@ Group* GroupList::addGroup(int groupNum, const GroupId& groupId, const QString&
if (checker != groupList.end()) if (checker != groupList.end())
qWarning() << "addGroup: groupId already taken"; 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; groupList[groupId] = newGroup;
id2key[groupNum] = groupId; id2key[groupNum] = groupId;
return newGroup; return newGroup;

8
src/model/friendmessagedispatcher.cpp

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

4
src/model/friendmessagedispatcher.h

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

13
src/model/group.cpp

@ -33,12 +33,14 @@
static const int MAX_GROUP_TITLE_LENGTH = 128; static const int MAX_GROUP_TITLE_LENGTH = 128;
Group::Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat, Group::Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat,
const QString& selfName) const QString& selfName, ICoreGroupQuery& groupQuery, ICoreIdHandler& idHandler)
: selfName{selfName} : selfName{selfName}
, title{name} , title{name}
, toxGroupNum(groupId) , toxGroupNum(groupId)
, groupId{persistentGroupId} , groupId{persistentGroupId}
, avGroupchat{isAvGroupchat} , avGroupchat{isAvGroupchat}
, groupQuery(groupQuery)
, idHandler(idHandler)
{ {
// in groupchats, we only notify on messages containing your name <-- dumb // in groupchats, we only notify on messages containing your name <-- dumb
// sound notifications should be on all messages, but system popup notification // 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 // 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. // 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. // This can cause double-updated to UI and chatlog, but is unavoidable given the API of toxcore.
const Core* core = Core::getInstance(); QStringList peers = groupQuery.getGroupPeerNames(toxGroupNum);
QStringList peers = core->getGroupPeerNames(toxGroupNum);
const auto oldPeerNames = peerDisplayNames; const auto oldPeerNames = peerDisplayNames;
peerDisplayNames.clear(); peerDisplayNames.clear();
const int nPeers = peers.size(); const int nPeers = peers.size();
for (int i = 0; i < nPeers; ++i) { for (int i = 0; i < nPeers; ++i) {
const auto pk = core->getGroupPeerPk(toxGroupNum, i); const auto pk = groupQuery.getGroupPeerPk(toxGroupNum, i);
if (pk == core->getSelfPublicKey()) { if (pk == idHandler.getSelfPublicKey()) {
peerDisplayNames[pk] = core->getUsername(); peerDisplayNames[pk] = idHandler.getUsername();
} else { } else {
peerDisplayNames[pk] = FriendList::decideNickname(pk, peers[i]); peerDisplayNames[pk] = FriendList::decideNickname(pk, peers[i]);
} }

7
src/model/group.h

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

88
src/model/groupmessagedispatcher.cpp

@ -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));
}

58
src/model/groupmessagedispatcher.h

@ -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 */

42
src/model/message.cpp

@ -18,9 +18,25 @@
*/ */
#include "message.h" #include "message.h"
#include "friend.h"
#include "src/core/core.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; std::vector<Message> ret;
@ -40,12 +56,34 @@ std::vector<Message> processOutgoingMessage(bool isAction, const QString& conten
return ret; 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(); QDateTime timestamp = QDateTime::currentDateTime();
auto ret = Message{}; auto ret = Message{};
ret.isAction = isAction; ret.isAction = isAction;
ret.content = message; ret.content = message;
ret.timestamp = timestamp; 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; return ret;
} }

76
src/model/message.h

@ -25,15 +25,87 @@
#include <vector> #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 struct Message
{ {
bool isAction; bool isAction;
QString content; QString content;
QDateTime timestamp; QDateTime timestamp;
std::vector<MessageMetadata> metadata;
}; };
std::vector<Message> processOutgoingMessage(bool isAction, const QString& content); class MessageProcessor
Message processIncomingMessage(bool isAction, const QString& message); {
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*/ #endif /*MESSAGE_H*/

35
src/persistence/igroupsettings.h

@ -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*/

10
src/persistence/settings.h

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

9
test/model/friendmessagedispatcher_test.cpp

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

300
test/model/groupmessagedispatcher_test.cpp

@ -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"

113
test/model/messageprocessor_test.cpp

@ -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"
Loading…
Cancel
Save