mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
feat(messages): Multipacket message support
* Introduced ToxExt and CoreExt abstraction * Along with interfaces for mocking and unit testing * Add "supportedExtensions" concept to Friend * Dispatch messages to CoreExt instead of Core when friend supports extended messages * Only split messages for core when extended messages are unavailable * Offline message engine/History not altered. Currently only valid for an existing session after extension negotiation has completed
This commit is contained in:
parent
abad3fc095
commit
7474c6d8ac
|
@ -218,6 +218,8 @@ set(${PROJECT_NAME}_SOURCES
|
||||||
src/chatlog/textformatter.h
|
src/chatlog/textformatter.h
|
||||||
src/core/coreav.cpp
|
src/core/coreav.cpp
|
||||||
src/core/coreav.h
|
src/core/coreav.h
|
||||||
|
src/core/coreext.cpp
|
||||||
|
src/core/coreext.h
|
||||||
src/core/core.cpp
|
src/core/core.cpp
|
||||||
src/core/corefile.cpp
|
src/core/corefile.cpp
|
||||||
src/core/corefile.h
|
src/core/corefile.h
|
||||||
|
|
|
@ -33,6 +33,9 @@ find_package(Qt5Test REQUIRED)
|
||||||
find_package(Qt5Widgets REQUIRED)
|
find_package(Qt5Widgets REQUIRED)
|
||||||
find_package(Qt5Xml REQUIRED)
|
find_package(Qt5Xml REQUIRED)
|
||||||
|
|
||||||
|
find_package(ToxExt REQUIRED)
|
||||||
|
find_package(ToxExtensionMessages REQUIRED)
|
||||||
|
|
||||||
function(add_dependency)
|
function(add_dependency)
|
||||||
set(ALL_LIBRARIES ${ALL_LIBRARIES} ${ARGN} PARENT_SCOPE)
|
set(ALL_LIBRARIES ${ALL_LIBRARIES} ${ARGN} PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
@ -47,6 +50,10 @@ add_dependency(
|
||||||
Qt5::Widgets
|
Qt5::Widgets
|
||||||
Qt5::Xml)
|
Qt5::Xml)
|
||||||
|
|
||||||
|
add_dependency(
|
||||||
|
ToxExt::ToxExt
|
||||||
|
ToxExtensionMessages::ToxExtensionMessages)
|
||||||
|
|
||||||
include(CMakeParseArguments)
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
function(search_dependency pkg)
|
function(search_dependency pkg)
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "coreav.h"
|
#include "coreav.h"
|
||||||
#include "corefile.h"
|
#include "corefile.h"
|
||||||
|
|
||||||
|
#include "src/core/coreext.h"
|
||||||
#include "src/core/dhtserver.h"
|
#include "src/core/dhtserver.h"
|
||||||
#include "src/core/icoresettings.h"
|
#include "src/core/icoresettings.h"
|
||||||
#include "src/core/toxlogger.h"
|
#include "src/core/toxlogger.h"
|
||||||
|
@ -515,6 +517,7 @@ void Core::registerCallbacks(Tox* tox)
|
||||||
tox_callback_conference_peer_list_changed(tox, onGroupPeerListChange);
|
tox_callback_conference_peer_list_changed(tox, onGroupPeerListChange);
|
||||||
tox_callback_conference_peer_name(tox, onGroupPeerNameChange);
|
tox_callback_conference_peer_name(tox, onGroupPeerNameChange);
|
||||||
tox_callback_conference_title(tox, onGroupTitleChange);
|
tox_callback_conference_title(tox, onGroupTitleChange);
|
||||||
|
tox_callback_friend_lossless_packet(tox, onLosslessPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -639,6 +642,9 @@ ToxCorePtr Core::makeToxCore(const QByteArray& savedata, const ICoreSettings* co
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core->ext = CoreExt::makeCoreExt(core->tox.get());
|
||||||
|
connect(core.get(), &Core::friendStatusChanged, core->ext.get(), &CoreExt::onFriendStatusChanged);
|
||||||
|
|
||||||
registerCallbacks(core->tox.get());
|
registerCallbacks(core->tox.get());
|
||||||
|
|
||||||
// connect the thread with the Core
|
// connect the thread with the Core
|
||||||
|
@ -714,6 +720,16 @@ QMutex &Core::getCoreLoopLock() const
|
||||||
return coreLoopLock;
|
return coreLoopLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CoreExt* Core::getExt() const
|
||||||
|
{
|
||||||
|
return ext.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreExt* Core::getExt()
|
||||||
|
{
|
||||||
|
return ext.get();
|
||||||
|
}
|
||||||
|
|
||||||
/* Using the now commented out statements in checkConnection(), I watched how
|
/* Using the now commented out statements in checkConnection(), I watched how
|
||||||
* many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials,
|
* many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials,
|
||||||
* 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks.
|
* 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks.
|
||||||
|
@ -734,6 +750,7 @@ void Core::process()
|
||||||
|
|
||||||
static int tolerance = CORE_DISCONNECT_TOLERANCE;
|
static int tolerance = CORE_DISCONNECT_TOLERANCE;
|
||||||
tox_iterate(tox.get(), this);
|
tox_iterate(tox.get(), this);
|
||||||
|
ext->process();
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
// we want to see the debug messages immediately
|
// we want to see the debug messages immediately
|
||||||
|
@ -988,6 +1005,16 @@ void Core::onGroupTitleChange(Tox*, uint32_t groupId, uint32_t peerId, const uin
|
||||||
emit core->groupTitleChanged(groupId, author, ToxString(cTitle, length).getQString());
|
emit core->groupTitleChanged(groupId, author, ToxString(cTitle, length).getQString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handling of custom lossless packets received by toxcore. Currently only used to forward toxext packets to CoreExt
|
||||||
|
*/
|
||||||
|
void Core::onLosslessPacket(Tox*, uint32_t friendId,
|
||||||
|
const uint8_t* data, size_t length, void* vCore)
|
||||||
|
{
|
||||||
|
Core* core = static_cast<Core*>(vCore);
|
||||||
|
core->ext->onLosslessPacket(friendId, data, length);
|
||||||
|
}
|
||||||
|
|
||||||
void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void* core)
|
void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void* core)
|
||||||
{
|
{
|
||||||
emit static_cast<Core*>(core)->receiptRecieved(friendId, ReceiptNum{receipt});
|
emit static_cast<Core*>(core)->receiptRecieved(friendId, ReceiptNum{receipt});
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
|
|
||||||
class CoreAV;
|
class CoreAV;
|
||||||
class CoreFile;
|
class CoreFile;
|
||||||
|
class CoreExt;
|
||||||
class IAudioControl;
|
class IAudioControl;
|
||||||
class ICoreSettings;
|
class ICoreSettings;
|
||||||
class GroupInvite;
|
class GroupInvite;
|
||||||
|
@ -79,6 +80,8 @@ public:
|
||||||
Tox* getTox() const;
|
Tox* getTox() const;
|
||||||
QMutex& getCoreLoopLock() const;
|
QMutex& getCoreLoopLock() const;
|
||||||
|
|
||||||
|
const CoreExt* getExt() const;
|
||||||
|
CoreExt* getExt();
|
||||||
~Core();
|
~Core();
|
||||||
|
|
||||||
static const QString TOX_EXT;
|
static const QString TOX_EXT;
|
||||||
|
@ -215,6 +218,9 @@ private:
|
||||||
size_t length, void* core);
|
size_t length, void* core);
|
||||||
static void onGroupTitleChange(Tox* tox, uint32_t groupId, uint32_t peerId,
|
static void onGroupTitleChange(Tox* tox, uint32_t groupId, uint32_t peerId,
|
||||||
const uint8_t* cTitle, size_t length, void* vCore);
|
const uint8_t* cTitle, size_t length, void* vCore);
|
||||||
|
|
||||||
|
static void onLosslessPacket(Tox* tox, uint32_t friendId,
|
||||||
|
const uint8_t* data, size_t length, void* core);
|
||||||
static void onReadReceiptCallback(Tox* tox, uint32_t friendId, uint32_t receipt, void* core);
|
static void onReadReceiptCallback(Tox* tox, uint32_t friendId, uint32_t receipt, void* core);
|
||||||
|
|
||||||
void sendGroupMessageWithType(int groupId, const QString& message, Tox_Message_Type type);
|
void sendGroupMessageWithType(int groupId, const QString& message, Tox_Message_Type type);
|
||||||
|
@ -249,6 +255,7 @@ private:
|
||||||
|
|
||||||
std::unique_ptr<CoreFile> file;
|
std::unique_ptr<CoreFile> file;
|
||||||
CoreAV* av = nullptr;
|
CoreAV* av = nullptr;
|
||||||
|
std::unique_ptr<CoreExt> ext;
|
||||||
QTimer* toxTimer = nullptr;
|
QTimer* toxTimer = nullptr;
|
||||||
// recursive, since we might call our own functions
|
// recursive, since we might call our own functions
|
||||||
mutable QMutex coreLoopLock{QMutex::Recursive};
|
mutable QMutex coreLoopLock{QMutex::Recursive};
|
||||||
|
|
159
src/core/coreext.cpp
Normal file
159
src/core/coreext.cpp
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2019-2020 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 "coreext.h"
|
||||||
|
#include "toxstring.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QTimeZone>
|
||||||
|
#include <QtCore>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <toxext/toxext.h>
|
||||||
|
#include <tox_extension_messages.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<CoreExt> CoreExt::makeCoreExt(Tox* core) {
|
||||||
|
auto toxExtPtr = toxext_init(core);
|
||||||
|
if (!toxExtPtr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto toxExt = ExtensionPtr<ToxExt>(toxExtPtr, toxext_free);
|
||||||
|
return std::unique_ptr<CoreExt>(new CoreExt(std::move(toxExt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreExt::CoreExt(ExtensionPtr<ToxExt> toxExt_)
|
||||||
|
: toxExt(std::move(toxExt_))
|
||||||
|
, toxExtMessages(nullptr, nullptr)
|
||||||
|
{
|
||||||
|
toxExtMessages = ExtensionPtr<ToxExtensionMessages>(
|
||||||
|
tox_extension_messages_register(
|
||||||
|
toxExt.get(),
|
||||||
|
CoreExt::onExtendedMessageReceived,
|
||||||
|
CoreExt::onExtendedMessageReceipt,
|
||||||
|
CoreExt::onExtendedMessageNegotiation,
|
||||||
|
this,
|
||||||
|
TOX_EXTENSION_MESSAGES_DEFAULT_MAX_RECEIVING_MESSAGE_SIZE),
|
||||||
|
tox_extension_messages_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreExt::process()
|
||||||
|
{
|
||||||
|
toxext_iterate(toxExt.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreExt::onLosslessPacket(uint32_t friendId, const uint8_t* data, size_t length)
|
||||||
|
{
|
||||||
|
if (is_toxext_packet(data, length)) {
|
||||||
|
toxext_handle_lossless_custom_packet(toxExt.get(), friendId, data, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreExt::Packet::Packet(
|
||||||
|
ToxExtPacketList* packetList,
|
||||||
|
ToxExtensionMessages* toxExtMessages,
|
||||||
|
uint32_t friendId,
|
||||||
|
PacketPassKey)
|
||||||
|
: toxExtMessages(toxExtMessages)
|
||||||
|
, packetList(packetList)
|
||||||
|
, friendId(friendId)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::unique_ptr<ICoreExtPacket> CoreExt::getPacket(uint32_t friendId)
|
||||||
|
{
|
||||||
|
return std::unique_ptr<Packet>(new Packet(
|
||||||
|
toxext_packet_list_create(toxExt.get(), friendId),
|
||||||
|
toxExtMessages.get(),
|
||||||
|
friendId,
|
||||||
|
PacketPassKey{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t CoreExt::Packet::addExtendedMessage(QString message)
|
||||||
|
{
|
||||||
|
if (hasBeenSent) {
|
||||||
|
assert(false);
|
||||||
|
qWarning() << "Invalid use of CoreExt::Packet";
|
||||||
|
// Hope that UINT64_MAX will never collide with an actual receipt num
|
||||||
|
// that we care about
|
||||||
|
return UINT64_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToxString toxString(message);
|
||||||
|
Tox_Extension_Messages_Error err;
|
||||||
|
|
||||||
|
return tox_extension_messages_append(
|
||||||
|
toxExtMessages,
|
||||||
|
packetList,
|
||||||
|
toxString.data(),
|
||||||
|
toxString.size(),
|
||||||
|
friendId,
|
||||||
|
&err);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CoreExt::Packet::send()
|
||||||
|
{
|
||||||
|
auto ret = toxext_send(packetList);
|
||||||
|
if (ret != TOXEXT_SUCCESS) {
|
||||||
|
qWarning() << "Failed to send packet";
|
||||||
|
}
|
||||||
|
// Indicate we've sent the packet even on failure since our packetlist will
|
||||||
|
// be invalid no matter what
|
||||||
|
hasBeenSent = true;
|
||||||
|
return ret == TOXEXT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreExt::onFriendStatusChanged(uint32_t friendId, Status::Status status)
|
||||||
|
{
|
||||||
|
const auto prevStatusIt = currentStatuses.find(friendId);
|
||||||
|
const auto prevStatus = prevStatusIt == currentStatuses.end()
|
||||||
|
? Status::Status::Offline : prevStatusIt->second;
|
||||||
|
|
||||||
|
currentStatuses[friendId] = status;
|
||||||
|
|
||||||
|
// Does not depend on prevStatus since prevStatus could be newly
|
||||||
|
// constructed. In this case we should still ensure the rest of the system
|
||||||
|
// knows there is no extension support
|
||||||
|
if (status == Status::Status::Offline) {
|
||||||
|
emit extendedMessageSupport(friendId, false);
|
||||||
|
} else if (prevStatus == Status::Status::Offline) {
|
||||||
|
tox_extension_messages_negotiate(toxExtMessages.get(), friendId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreExt::onExtendedMessageReceived(uint32_t friendId, const uint8_t* data, size_t size, void* userData)
|
||||||
|
{
|
||||||
|
QString msg = ToxString(data, size).getQString();
|
||||||
|
emit static_cast<CoreExt*>(userData)->extendedMessageReceived(friendId, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreExt::onExtendedMessageReceipt(uint32_t friendId, uint64_t receiptId, void* userData)
|
||||||
|
{
|
||||||
|
emit static_cast<CoreExt*>(userData)->extendedReceiptReceived(friendId, receiptId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreExt::onExtendedMessageNegotiation(uint32_t friendId, bool compatible, uint64_t maxMessageSize, void* userData)
|
||||||
|
{
|
||||||
|
auto coreExt = static_cast<CoreExt*>(userData);
|
||||||
|
emit coreExt->extendedMessageSupport(friendId, compatible);
|
||||||
|
}
|
||||||
|
|
145
src/core/coreext.h
Normal file
145
src/core/coreext.h
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2019-2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "src/model/status.h"
|
||||||
|
#include "icoreextpacket.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
struct Tox;
|
||||||
|
struct ToxExt;
|
||||||
|
struct ToxExtensionMessages;
|
||||||
|
struct ToxExtPacketList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge between the toxext library and the rest of qTox.
|
||||||
|
*/
|
||||||
|
class CoreExt : public QObject, public ICoreExtPacketAllocator
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private:
|
||||||
|
// PassKey idiom to prevent others from making PacketBuilders
|
||||||
|
struct PacketPassKey {};
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a CoreExt instance. Using a pointer here makes our
|
||||||
|
* registrations with extensions significantly easier to manage
|
||||||
|
*
|
||||||
|
* @param[in] pointer to core tox instance
|
||||||
|
* @return CoreExt on success, nullptr on failure
|
||||||
|
*/
|
||||||
|
static std::unique_ptr<CoreExt> makeCoreExt(Tox* core);
|
||||||
|
|
||||||
|
// We do registration with our own pointer, need to ensure we're in a stable location
|
||||||
|
CoreExt(CoreExt const& other) = delete;
|
||||||
|
CoreExt(CoreExt&& other) = delete;
|
||||||
|
CoreExt& operator=(CoreExt const& other) = delete;
|
||||||
|
CoreExt& operator=(CoreExt&& other) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Periodic service function
|
||||||
|
*/
|
||||||
|
void process();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles extension related lossless packets
|
||||||
|
* @param[in] friendId Core id of friend
|
||||||
|
* @param[in] data Packet data
|
||||||
|
* @param[in] length Length of packet data
|
||||||
|
*/
|
||||||
|
void onLosslessPacket(uint32_t friendId, const uint8_t* data, size_t length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation of ICoreExtPacket
|
||||||
|
*/
|
||||||
|
class Packet : public ICoreExtPacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Internal constructor for a packet.
|
||||||
|
*/
|
||||||
|
Packet(
|
||||||
|
ToxExtPacketList* packetList,
|
||||||
|
ToxExtensionMessages* toxExtMessages,
|
||||||
|
uint32_t friendId,
|
||||||
|
PacketPassKey);
|
||||||
|
|
||||||
|
// Delete copy constructor, we shouldn't be able to copy
|
||||||
|
Packet(Packet const& other) = delete;
|
||||||
|
|
||||||
|
Packet(Packet&& other)
|
||||||
|
{
|
||||||
|
toxExtMessages = other.toxExtMessages;
|
||||||
|
packetList = other.packetList;
|
||||||
|
friendId = other.friendId;
|
||||||
|
hasBeenSent = other.hasBeenSent;
|
||||||
|
other.toxExtMessages = nullptr;
|
||||||
|
other.packetList = nullptr;
|
||||||
|
other.friendId = 0;
|
||||||
|
other.hasBeenSent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t addExtendedMessage(QString message) override;
|
||||||
|
|
||||||
|
bool send() override;
|
||||||
|
private:
|
||||||
|
bool hasBeenSent = false;
|
||||||
|
// Note: non-owning pointer
|
||||||
|
ToxExtensionMessages* toxExtMessages;
|
||||||
|
// Note: packetList is freed on send() call
|
||||||
|
ToxExtPacketList* packetList;
|
||||||
|
uint32_t friendId;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void extendedMessageReceived(uint32_t friendId, const QString& message);
|
||||||
|
void extendedReceiptReceived(uint32_t friendId, uint64_t receiptId);
|
||||||
|
void extendedMessageSupport(uint32_t friendId, bool supported);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onFriendStatusChanged(uint32_t friendId, Status::Status status);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static void onExtendedMessageReceived(uint32_t friendId, const uint8_t* data, size_t size, void* userData);
|
||||||
|
static void onExtendedMessageReceipt(uint32_t friendId, uint64_t receiptId, void* userData);
|
||||||
|
static void onExtendedMessageNegotiation(uint32_t friendId, bool compatible, uint64_t maxMessageSize, void* userData);
|
||||||
|
|
||||||
|
// A little extra cost to hide the deleters, but this lets us fwd declare
|
||||||
|
// and prevent any extension headers from leaking out to the rest of the
|
||||||
|
// system
|
||||||
|
template <class T>
|
||||||
|
using ExtensionPtr = std::unique_ptr<T, void(*)(T*)>;
|
||||||
|
|
||||||
|
CoreExt(ExtensionPtr<ToxExt> toxExt);
|
||||||
|
|
||||||
|
std::unordered_map<uint32_t, Status::Status> currentStatuses;
|
||||||
|
ExtensionPtr<ToxExt> toxExt;
|
||||||
|
ExtensionPtr<ToxExtensionMessages> toxExtMessages;
|
||||||
|
};
|
32
src/core/extension.h
Normal file
32
src/core/extension.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
// Do not use enum class because we use these as indexes frequently (see ExtensionSet)
|
||||||
|
struct ExtensionType
|
||||||
|
{
|
||||||
|
enum {
|
||||||
|
messages,
|
||||||
|
max
|
||||||
|
};
|
||||||
|
};
|
||||||
|
using ExtensionSet = std::bitset<ExtensionType::max>;
|
68
src/core/icoreextpacket.h
Normal file
68
src/core/icoreextpacket.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2019 by The qTox Project Contributors
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
qTox is libre software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
qTox is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction around the toxext packet. The toxext flow is to allow several extensions
|
||||||
|
* to tack onto the same packet before sending it to avoid needing the toxext overhead
|
||||||
|
* for every single extension. This abstraction models a toxext packet list.
|
||||||
|
*
|
||||||
|
* Intent is to retrieve a ICoreExtPacket from an ICoreExtPacketAllocator, append all
|
||||||
|
* relevant extension data, and then finally send the packet. After sending the packet
|
||||||
|
* is no longer guaranteed to be valid.
|
||||||
|
*/
|
||||||
|
class ICoreExtPacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ICoreExtPacket() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds message to packet
|
||||||
|
* @return Extended message receipt, UINT64_MAX on failure
|
||||||
|
* @note Any other extensions related to this message have to be added
|
||||||
|
* _before_ the message itself
|
||||||
|
*/
|
||||||
|
virtual uint64_t addExtendedMessage(QString message) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Consumes the packet constructed with PacketBuilder packet and
|
||||||
|
* sends it to toxext
|
||||||
|
*/
|
||||||
|
virtual bool send() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider of toxext packets
|
||||||
|
*/
|
||||||
|
class ICoreExtPacketAllocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ICoreExtPacketAllocator() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets a new packet builder for friend with core friend id friendId
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) = 0;
|
||||||
|
};
|
|
@ -173,3 +173,14 @@ bool Friend::useHistory() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Friend::setExtendedMessageSupport(bool supported)
|
||||||
|
{
|
||||||
|
supportedExtensions[ExtensionType::messages] = supported;
|
||||||
|
emit extensionSupportChanged(supportedExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionSet Friend::getSupportedExtensions() const
|
||||||
|
{
|
||||||
|
return supportedExtensions;
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include "contact.h"
|
#include "contact.h"
|
||||||
#include "src/core/core.h"
|
#include "src/core/core.h"
|
||||||
|
#include "src/core/extension.h"
|
||||||
#include "src/core/toxid.h"
|
#include "src/core/toxid.h"
|
||||||
#include "src/core/contactid.h"
|
#include "src/core/contactid.h"
|
||||||
#include "src/model/status.h"
|
#include "src/model/status.h"
|
||||||
|
@ -50,20 +51,24 @@ public:
|
||||||
uint32_t getId() const override;
|
uint32_t getId() const override;
|
||||||
const ContactId& getPersistentId() const override;
|
const ContactId& getPersistentId() const override;
|
||||||
|
|
||||||
|
void finishNegotiation();
|
||||||
void setStatus(Status::Status s);
|
void setStatus(Status::Status s);
|
||||||
Status::Status getStatus() const;
|
Status::Status getStatus() const;
|
||||||
bool useHistory() const final;
|
bool useHistory() const final;
|
||||||
|
|
||||||
|
void setExtendedMessageSupport(bool supported);
|
||||||
|
ExtensionSet getSupportedExtensions() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged(const ToxPk& friendId, const QString& name);
|
void nameChanged(const ToxPk& friendId, const QString& name);
|
||||||
void aliasChanged(const ToxPk& friendId, QString alias);
|
void aliasChanged(const ToxPk& friendId, QString alias);
|
||||||
void statusChanged(const ToxPk& friendId, Status::Status status);
|
void statusChanged(const ToxPk& friendId, Status::Status status);
|
||||||
void onlineOfflineChanged(const ToxPk& friendId, bool isOnline);
|
void onlineOfflineChanged(const ToxPk& friendId, bool isOnline);
|
||||||
void statusMessageChanged(const ToxPk& friendId, const QString& message);
|
void statusMessageChanged(const ToxPk& friendId, const QString& message);
|
||||||
|
void extensionSupportChanged(ExtensionSet extensions);
|
||||||
void loadChatHistory();
|
void loadChatHistory();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString userName;
|
QString userName;
|
||||||
QString userAlias;
|
QString userAlias;
|
||||||
|
@ -72,4 +77,5 @@ private:
|
||||||
uint32_t friendId;
|
uint32_t friendId;
|
||||||
bool hasNewEvents;
|
bool hasNewEvents;
|
||||||
Status::Status friendStatus;
|
Status::Status friendStatus;
|
||||||
|
ExtensionSet supportedExtensions;
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
#include "src/persistence/settings.h"
|
#include "src/persistence/settings.h"
|
||||||
#include "src/model/status.h"
|
#include "src/model/status.h"
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,12 +42,15 @@ bool sendMessageToCore(ICoreFriendMessageSender& messageSender, const Friend& f,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, MessageProcessor processor_,
|
FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, MessageProcessor processor_,
|
||||||
ICoreFriendMessageSender& messageSender_)
|
ICoreFriendMessageSender& messageSender_,
|
||||||
|
ICoreExtPacketAllocator& coreExtPacketAllocator_)
|
||||||
: f(f_)
|
: f(f_)
|
||||||
, messageSender(messageSender_)
|
, messageSender(messageSender_)
|
||||||
, offlineMsgEngine(&f_, &messageSender_)
|
, offlineMsgEngine(&f_, &messageSender_)
|
||||||
, processor(std::move(processor_))
|
, processor(std::move(processor_))
|
||||||
|
, coreExtPacketAllocator(coreExtPacketAllocator_)
|
||||||
{
|
{
|
||||||
connect(&f, &Friend::onlineOfflineChanged, this, &FriendMessageDispatcher::onFriendOnlineOfflineChanged);
|
connect(&f, &Friend::onlineOfflineChanged, this, &FriendMessageDispatcher::onFriendOnlineOfflineChanged);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +63,9 @@ 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 : processor.processOutgoingMessage(isAction, content)) {
|
auto supportedExtensions = f.getSupportedExtensions();
|
||||||
|
const bool needsSplit = !supportedExtensions[ExtensionType::messages];
|
||||||
|
for (const auto& message : processor.processOutgoingMessage(isAction, content, needsSplit)) {
|
||||||
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); };
|
||||||
|
@ -70,8 +74,22 @@ FriendMessageDispatcher::sendMessage(bool isAction, const QString& content)
|
||||||
|
|
||||||
bool messageSent = false;
|
bool messageSent = false;
|
||||||
|
|
||||||
|
// NOTE: This branch is getting a little hairy but will be cleaned up in the following commit
|
||||||
if (Status::isOnline(f.getStatus())) {
|
if (Status::isOnline(f.getStatus())) {
|
||||||
messageSent = sendMessageToCore(messageSender, f, message, receipt);
|
|
||||||
|
// Action messages go over the regular mesage channel so we cannot use extensions with them
|
||||||
|
if (supportedExtensions[ExtensionType::messages] && !isAction) {
|
||||||
|
auto packet = coreExtPacketAllocator.getPacket(f.getId());
|
||||||
|
|
||||||
|
if (supportedExtensions[ExtensionType::messages]) {
|
||||||
|
// NOTE: Dirty hack to get extensions working that will be fixed in the following commit
|
||||||
|
receipt.get() = packet->addExtendedMessage(message.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageSent = packet->send();
|
||||||
|
} else {
|
||||||
|
messageSent = sendMessageToCore(messageSender, f, message, receipt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!messageSent) {
|
if (!messageSent) {
|
||||||
|
@ -92,7 +110,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(), processor.processIncomingMessage(isAction, content));
|
emit this->messageReceived(f.getPublicKey(), processor.processIncomingCoreMessage(isAction, content));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,6 +122,18 @@ void FriendMessageDispatcher::onReceiptReceived(ReceiptNum receipt)
|
||||||
offlineMsgEngine.onReceiptReceived(receipt);
|
offlineMsgEngine.onReceiptReceived(receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FriendMessageDispatcher::onExtMessageReceived(const QString& content)
|
||||||
|
{
|
||||||
|
auto message = processor.processIncomingExtMessage(content);
|
||||||
|
emit this->messageReceived(f.getPublicKey(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FriendMessageDispatcher::onExtReceiptReceived(uint64_t receiptId)
|
||||||
|
{
|
||||||
|
// NOTE: Reusing ReceiptNum is a dirty hack that will be cleaned up in the following commit
|
||||||
|
offlineMsgEngine.onReceiptReceived(ReceiptNum(receiptId));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handles status change for friend
|
* @brief Handles status change for friend
|
||||||
* @note Parameters just to fit slot api
|
* @note Parameters just to fit slot api
|
||||||
|
|
|
@ -35,18 +35,22 @@ class FriendMessageDispatcher : public IMessageDispatcher
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
FriendMessageDispatcher(Friend& f, MessageProcessor processor,
|
FriendMessageDispatcher(Friend& f, MessageProcessor processor,
|
||||||
ICoreFriendMessageSender& messageSender);
|
ICoreFriendMessageSender& messageSender,
|
||||||
|
ICoreExtPacketAllocator& coreExt);
|
||||||
|
|
||||||
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction,
|
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction,
|
||||||
const QString& content) override;
|
const QString& content) override;
|
||||||
void onMessageReceived(bool isAction, const QString& content);
|
void onMessageReceived(bool isAction, const QString& content);
|
||||||
void onReceiptReceived(ReceiptNum receipt);
|
void onReceiptReceived(ReceiptNum receipt);
|
||||||
|
void onExtMessageReceived(const QString& message);
|
||||||
|
void onExtReceiptReceived(uint64_t receiptId);
|
||||||
void clearOutgoingMessages();
|
void clearOutgoingMessages();
|
||||||
private slots:
|
private slots:
|
||||||
void onFriendOnlineOfflineChanged(const ToxPk& key, bool isOnline);
|
void onFriendOnlineOfflineChanged(const ToxPk& key, bool isOnline);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Friend& f;
|
Friend& f;
|
||||||
|
ICoreExtPacketAllocator& coreExtPacketAllocator;
|
||||||
DispatchedMessageId nextMessageId = DispatchedMessageId(0);
|
DispatchedMessageId nextMessageId = DispatchedMessageId(0);
|
||||||
|
|
||||||
ICoreFriendMessageSender& messageSender;
|
ICoreFriendMessageSender& messageSender;
|
||||||
|
|
|
@ -41,7 +41,7 @@ GroupMessageDispatcher::sendMessage(bool isAction, QString const& content)
|
||||||
const auto firstMessageId = nextMessageId;
|
const auto firstMessageId = nextMessageId;
|
||||||
auto lastMessageId = firstMessageId;
|
auto lastMessageId = firstMessageId;
|
||||||
|
|
||||||
for (auto const& message : processor.processOutgoingMessage(isAction, content)) {
|
for (auto const& message : processor.processOutgoingMessage(isAction, content, true /*needsSplit*/)) {
|
||||||
auto messageId = nextMessageId++;
|
auto messageId = nextMessageId++;
|
||||||
lastMessageId = messageId;
|
lastMessageId = messageId;
|
||||||
if (group.getPeersCount() != 1) {
|
if (group.getPeersCount() != 1) {
|
||||||
|
@ -84,5 +84,5 @@ void GroupMessageDispatcher::onMessageReceived(const ToxPk& sender, bool isActio
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit messageReceived(sender, processor.processIncomingMessage(isAction, content));
|
emit messageReceived(sender, processor.processIncomingCoreMessage(isAction, content));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "friend.h"
|
#include "friend.h"
|
||||||
#include "src/core/core.h"
|
#include "src/core/core.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
void MessageProcessor::SharedParams::onUserNameSet(const QString& username)
|
void MessageProcessor::SharedParams::onUserNameSet(const QString& username)
|
||||||
{
|
{
|
||||||
QString sanename = username;
|
QString sanename = username;
|
||||||
|
@ -49,11 +51,14 @@ MessageProcessor::MessageProcessor(const MessageProcessor::SharedParams& sharedP
|
||||||
/**
|
/**
|
||||||
* @brief Converts an outgoing message into one (or many) sanitized Message(s)
|
* @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> MessageProcessor::processOutgoingMessage(bool isAction, QString const& content, bool needsSplit)
|
||||||
{
|
{
|
||||||
std::vector<Message> ret;
|
std::vector<Message> ret;
|
||||||
|
|
||||||
QStringList splitMsgs = Core::splitMessage(content);
|
const auto splitMsgs = needsSplit
|
||||||
|
? Core::splitMessage(content)
|
||||||
|
: QStringList({content});
|
||||||
|
|
||||||
ret.reserve(splitMsgs.size());
|
ret.reserve(splitMsgs.size());
|
||||||
|
|
||||||
QDateTime timestamp = QDateTime::currentDateTime();
|
QDateTime timestamp = QDateTime::currentDateTime();
|
||||||
|
@ -69,11 +74,10 @@ std::vector<Message> MessageProcessor::processOutgoingMessage(bool isAction, QSt
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Converts an incoming message into a sanitized Message
|
* @brief Converts an incoming message into a sanitized Message
|
||||||
*/
|
*/
|
||||||
Message MessageProcessor::processIncomingMessage(bool isAction, QString const& message)
|
Message MessageProcessor::processIncomingCoreMessage(bool isAction, QString const& message)
|
||||||
{
|
{
|
||||||
QDateTime timestamp = QDateTime::currentDateTime();
|
QDateTime timestamp = QDateTime::currentDateTime();
|
||||||
auto ret = Message{};
|
auto ret = Message{};
|
||||||
|
@ -109,3 +113,17 @@ Message MessageProcessor::processIncomingMessage(bool isAction, QString const& m
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message MessageProcessor::processIncomingExtMessage(const QString& content)
|
||||||
|
{
|
||||||
|
// Note: detectingMentions not implemented here since mentions are only
|
||||||
|
// currently useful in group messages which do not support extensions. If we
|
||||||
|
// were to support mentions we would probably want to do something more
|
||||||
|
// intelligent anyways
|
||||||
|
assert(detectingMentions == false);
|
||||||
|
auto message = Message();
|
||||||
|
message.timestamp = QDateTime::currentDateTime();
|
||||||
|
message.content = content;
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "src/core/coreext.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -26,6 +28,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class Friend;
|
class Friend;
|
||||||
|
class CoreExt;
|
||||||
|
|
||||||
// NOTE: This could be extended in the future to handle all text processing (see
|
// NOTE: This could be extended in the future to handle all text processing (see
|
||||||
// ChatMessage::createChatMessage)
|
// ChatMessage::createChatMessage)
|
||||||
|
@ -89,9 +92,9 @@ public:
|
||||||
|
|
||||||
MessageProcessor(const SharedParams& sharedParams);
|
MessageProcessor(const SharedParams& sharedParams);
|
||||||
|
|
||||||
std::vector<Message> processOutgoingMessage(bool isAction, QString const& content);
|
std::vector<Message> processOutgoingMessage(bool isAction, const QString& content, bool needsSplit);
|
||||||
|
Message processIncomingCoreMessage(bool isAction, const QString& content);
|
||||||
Message processIncomingMessage(bool isAction, QString const& message);
|
Message processIncomingExtMessage(const QString& content);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enables mention detection in the processor
|
* @brief Enables mention detection in the processor
|
||||||
|
|
|
@ -108,6 +108,8 @@ void Nexus::start()
|
||||||
qRegisterMetaType<GroupInvite>("GroupInvite");
|
qRegisterMetaType<GroupInvite>("GroupInvite");
|
||||||
qRegisterMetaType<ReceiptNum>("ReceiptNum");
|
qRegisterMetaType<ReceiptNum>("ReceiptNum");
|
||||||
qRegisterMetaType<RowId>("RowId");
|
qRegisterMetaType<RowId>("RowId");
|
||||||
|
qRegisterMetaType<uint64_t>("uint64_t");
|
||||||
|
qRegisterMetaType<ExtensionSet>("ExtensionSet");
|
||||||
|
|
||||||
qApp->setQuitOnLastWindowClosed(false);
|
qApp->setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
|
|
@ -695,6 +695,13 @@ void Widget::onCoreChanged(Core& core)
|
||||||
connect(&core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged);
|
connect(&core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged);
|
||||||
connect(&core, &Core::groupSentFailed, this, &Widget::onGroupSendFailed);
|
connect(&core, &Core::groupSentFailed, this, &Widget::onGroupSendFailed);
|
||||||
connect(&core, &Core::usernameSet, this, &Widget::refreshPeerListsLocal);
|
connect(&core, &Core::usernameSet, this, &Widget::refreshPeerListsLocal);
|
||||||
|
|
||||||
|
auto coreExt = core.getExt();
|
||||||
|
|
||||||
|
connect(coreExt, &CoreExt::extendedMessageReceived, this, &Widget::onFriendExtMessageReceived);
|
||||||
|
connect(coreExt, &CoreExt::extendedReceiptReceived, this, &Widget::onExtReceiptReceived);
|
||||||
|
connect(coreExt, &CoreExt::extendedMessageSupport, this, &Widget::onExtendedMessageSupport);
|
||||||
|
|
||||||
connect(this, &Widget::statusSet, &core, &Core::setStatus);
|
connect(this, &Widget::statusSet, &core, &Core::setStatus);
|
||||||
connect(this, &Widget::friendRequested, &core, &Core::requestFriendship);
|
connect(this, &Widget::friendRequested, &core, &Core::requestFriendship);
|
||||||
connect(this, &Widget::friendRequestAccepted, &core, &Core::acceptFriendRequest);
|
connect(this, &Widget::friendRequestAccepted, &core, &Core::acceptFriendRequest);
|
||||||
|
@ -1146,7 +1153,7 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk)
|
||||||
|
|
||||||
auto messageProcessor = MessageProcessor(sharedMessageProcessorParams);
|
auto messageProcessor = MessageProcessor(sharedMessageProcessorParams);
|
||||||
auto friendMessageDispatcher =
|
auto friendMessageDispatcher =
|
||||||
std::make_shared<FriendMessageDispatcher>(*newfriend, std::move(messageProcessor), *core);
|
std::make_shared<FriendMessageDispatcher>(*newfriend, std::move(messageProcessor), *core, *core->getExt());
|
||||||
|
|
||||||
// Note: We do not have to connect the message dispatcher signals since
|
// Note: We do not have to connect the message dispatcher signals since
|
||||||
// ChatHistory hooks them up in a very specific order
|
// ChatHistory hooks them up in a very specific order
|
||||||
|
@ -1401,6 +1408,39 @@ void Widget::onReceiptReceived(int friendId, ReceiptNum receipt)
|
||||||
friendMessageDispatchers[f->getPublicKey()]->onReceiptReceived(receipt);
|
friendMessageDispatchers[f->getPublicKey()]->onReceiptReceived(receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Widget::onExtendedMessageSupport(uint32_t friendNumber, bool compatible)
|
||||||
|
{
|
||||||
|
const auto& friendKey = FriendList::id2Key(friendNumber);
|
||||||
|
Friend* f = FriendList::findFriend(friendKey);
|
||||||
|
if (!f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f->setExtendedMessageSupport(compatible);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::onFriendExtMessageReceived(uint32_t friendNumber, const QString& message)
|
||||||
|
{
|
||||||
|
const auto& friendKey = FriendList::id2Key(friendNumber);
|
||||||
|
Friend* f = FriendList::findFriend(friendKey);
|
||||||
|
if (!f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
friendMessageDispatchers[f->getPublicKey()]->onExtMessageReceived(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::onExtReceiptReceived(uint32_t friendNumber, uint64_t receiptId)
|
||||||
|
{
|
||||||
|
const auto& friendKey = FriendList::id2Key(friendNumber);
|
||||||
|
Friend* f = FriendList::findFriend(friendKey);
|
||||||
|
if (!f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
friendMessageDispatchers[f->getPublicKey()]->onExtReceiptReceived(receiptId);
|
||||||
|
}
|
||||||
|
|
||||||
void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog)
|
void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog)
|
||||||
{
|
{
|
||||||
const ToxPk& friendPk = frnd->getPublicKey();
|
const ToxPk& friendPk = frnd->getPublicKey();
|
||||||
|
|
|
@ -172,6 +172,9 @@ public slots:
|
||||||
void onFriendAliasChanged(const ToxPk& friendId, const QString& alias);
|
void onFriendAliasChanged(const ToxPk& friendId, const QString& alias);
|
||||||
void onFriendMessageReceived(uint32_t friendnumber, const QString& message, bool isAction);
|
void onFriendMessageReceived(uint32_t friendnumber, const QString& message, bool isAction);
|
||||||
void onReceiptReceived(int friendId, ReceiptNum receipt);
|
void onReceiptReceived(int friendId, ReceiptNum receipt);
|
||||||
|
void onExtendedMessageSupport(uint32_t friendNumber, bool supported);
|
||||||
|
void onFriendExtMessageReceived(uint32_t friendNumber, const QString& message);
|
||||||
|
void onExtReceiptReceived(uint32_t friendNumber, uint64_t receiptId);
|
||||||
void onFriendRequestReceived(const ToxPk& friendPk, const QString& message);
|
void onFriendRequestReceived(const ToxPk& friendPk, const QString& message);
|
||||||
void onFileReceiveRequested(const ToxFile& file);
|
void onFileReceiveRequested(const ToxFile& file);
|
||||||
void onEmptyGroupCreated(uint32_t groupnumber, const GroupId& groupId, const QString& title);
|
void onEmptyGroupCreated(uint32_t groupnumber, const GroupId& groupId, const QString& title);
|
||||||
|
@ -344,6 +347,7 @@ private:
|
||||||
QMap<ToxPk, std::shared_ptr<ChatHistory>> friendChatLogs;
|
QMap<ToxPk, std::shared_ptr<ChatHistory>> friendChatLogs;
|
||||||
QMap<ToxPk, std::shared_ptr<FriendChatroom>> friendChatrooms;
|
QMap<ToxPk, std::shared_ptr<FriendChatroom>> friendChatrooms;
|
||||||
QMap<ToxPk, ChatForm*> chatForms;
|
QMap<ToxPk, ChatForm*> chatForms;
|
||||||
|
std::map<ToxPk, std::unique_ptr<QTimer>> negotiateTimers;
|
||||||
|
|
||||||
QMap<GroupId, GroupWidget*> groupWidgets;
|
QMap<GroupId, GroupWidget*> groupWidgets;
|
||||||
QMap<GroupId, std::shared_ptr<GroupMessageDispatcher>> groupMessageDispatchers;
|
QMap<GroupId, std::shared_ptr<GroupMessageDispatcher>> groupMessageDispatchers;
|
||||||
|
|
|
@ -28,6 +28,45 @@
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
|
|
||||||
|
class MockCoreExtPacket : public ICoreExtPacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
MockCoreExtPacket(uint64_t& numSentMessages, uint64_t& currentReceiptId)
|
||||||
|
: numSentMessages(numSentMessages)
|
||||||
|
, currentReceiptId(currentReceiptId)
|
||||||
|
{}
|
||||||
|
|
||||||
|
uint64_t addExtendedMessage(QString message) override
|
||||||
|
{
|
||||||
|
this->message = message;
|
||||||
|
return currentReceiptId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send() override
|
||||||
|
{
|
||||||
|
numSentMessages++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t& numSentMessages;
|
||||||
|
uint64_t& currentReceiptId;
|
||||||
|
QDateTime senderTimestamp;
|
||||||
|
QString message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockCoreExtPacketAllocator : public ICoreExtPacketAllocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) override
|
||||||
|
{
|
||||||
|
return std::unique_ptr<MockCoreExtPacket>(new MockCoreExtPacket(numSentMessages, currentReceiptId));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t numSentMessages;
|
||||||
|
uint64_t currentReceiptId;
|
||||||
|
};
|
||||||
|
|
||||||
class MockFriendMessageSender : public ICoreFriendMessageSender
|
class MockFriendMessageSender : public ICoreFriendMessageSender
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -69,6 +108,8 @@ private slots:
|
||||||
void testMessageSending();
|
void testMessageSending();
|
||||||
void testOfflineMessages();
|
void testOfflineMessages();
|
||||||
void testFailedMessage();
|
void testFailedMessage();
|
||||||
|
void testNegotiationFailure();
|
||||||
|
void testNegotiationSuccess();
|
||||||
|
|
||||||
void onMessageSent(DispatchedMessageId id, Message message)
|
void onMessageSent(DispatchedMessageId id, Message message)
|
||||||
{
|
{
|
||||||
|
@ -93,6 +134,7 @@ 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<MockCoreExtPacketAllocator> coreExtPacketAllocator;
|
||||||
std::unique_ptr<MessageProcessor::SharedParams> sharedProcessorParams;
|
std::unique_ptr<MessageProcessor::SharedParams> sharedProcessorParams;
|
||||||
std::unique_ptr<MessageProcessor> messageProcessor;
|
std::unique_ptr<MessageProcessor> messageProcessor;
|
||||||
std::unique_ptr<FriendMessageDispatcher> friendMessageDispatcher;
|
std::unique_ptr<FriendMessageDispatcher> friendMessageDispatcher;
|
||||||
|
@ -110,11 +152,12 @@ 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());
|
||||||
|
coreExtPacketAllocator = std::unique_ptr<MockCoreExtPacketAllocator>(new MockCoreExtPacketAllocator());
|
||||||
sharedProcessorParams =
|
sharedProcessorParams =
|
||||||
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams());
|
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams());
|
||||||
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
|
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
|
||||||
friendMessageDispatcher = std::unique_ptr<FriendMessageDispatcher>(
|
friendMessageDispatcher = std::unique_ptr<FriendMessageDispatcher>(
|
||||||
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender));
|
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender, *coreExtPacketAllocator));
|
||||||
|
|
||||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this,
|
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this,
|
||||||
&TestFriendMessageDispatcher::onMessageSent);
|
&TestFriendMessageDispatcher::onMessageSent);
|
||||||
|
@ -227,5 +270,26 @@ void TestFriendMessageDispatcher::testFailedMessage()
|
||||||
QVERIFY(messageSender->numSentMessages == 1);
|
QVERIFY(messageSender->numSentMessages == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestFriendMessageDispatcher::testNegotiationFailure()
|
||||||
|
{
|
||||||
|
friendMessageDispatcher->sendMessage(false, "test");
|
||||||
|
|
||||||
|
QVERIFY(messageSender->numSentMessages == 1);
|
||||||
|
QVERIFY(coreExtPacketAllocator->numSentMessages == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestFriendMessageDispatcher::testNegotiationSuccess()
|
||||||
|
{
|
||||||
|
f->setExtendedMessageSupport(true);
|
||||||
|
|
||||||
|
friendMessageDispatcher->sendMessage(false, "test");
|
||||||
|
|
||||||
|
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
|
||||||
|
|
||||||
|
friendMessageDispatcher->sendMessage(false, "test");
|
||||||
|
QVERIFY(coreExtPacketAllocator->numSentMessages == 2);
|
||||||
|
QVERIFY(messageSender->numSentMessages == 0);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestFriendMessageDispatcher)
|
QTEST_GUILESS_MAIN(TestFriendMessageDispatcher)
|
||||||
#include "friendmessagedispatcher_test.moc"
|
#include "friendmessagedispatcher_test.moc"
|
||||||
|
|
|
@ -66,55 +66,55 @@ void TestMessageProcessor::testSelfMention()
|
||||||
for (const auto& str : {testUserName, testToxPk}) {
|
for (const auto& str : {testUserName, testToxPk}) {
|
||||||
|
|
||||||
// Using my name or public key should match
|
// Using my name or public key should match
|
||||||
auto processedMessage = messageProcessor.processIncomingMessage(false, str % " hi");
|
auto processedMessage = messageProcessor.processIncomingCoreMessage(false, str % " hi");
|
||||||
QVERIFY(messageHasSelfMention(processedMessage));
|
QVERIFY(messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// Action messages should match too
|
// Action messages should match too
|
||||||
processedMessage = messageProcessor.processIncomingMessage(true, str % " hi");
|
processedMessage = messageProcessor.processIncomingCoreMessage(true, str % " hi");
|
||||||
QVERIFY(messageHasSelfMention(processedMessage));
|
QVERIFY(messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// Too much text shouldn't match
|
// Too much text shouldn't match
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, str % "2");
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, str % "2");
|
||||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// Unless it's a colon
|
// Unless it's a colon
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, str % ": test");
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, str % ": test");
|
||||||
QVERIFY(messageHasSelfMention(processedMessage));
|
QVERIFY(messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// remove last character
|
// remove last character
|
||||||
QString chopped = str;
|
QString chopped = str;
|
||||||
chopped.chop(1);
|
chopped.chop(1);
|
||||||
// Too little text shouldn't match
|
// Too little text shouldn't match
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, chopped);
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, chopped);
|
||||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// make lower case
|
// make lower case
|
||||||
QString lower = QString(str).toLower();
|
QString lower = QString(str).toLower();
|
||||||
|
|
||||||
// The regex should be case insensitive
|
// The regex should be case insensitive
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, lower % " hi");
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, lower % " hi");
|
||||||
QVERIFY(messageHasSelfMention(processedMessage));
|
QVERIFY(messageHasSelfMention(processedMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
// New user name changes should be detected
|
// New user name changes should be detected
|
||||||
sharedParams.onUserNameSet("NewUserName");
|
sharedParams.onUserNameSet("NewUserName");
|
||||||
auto processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi");
|
auto processedMessage = messageProcessor.processIncomingCoreMessage(false, "NewUserName: hi");
|
||||||
QVERIFY(messageHasSelfMention(processedMessage));
|
QVERIFY(messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// Special characters should be removed
|
// Special characters should be removed
|
||||||
sharedParams.onUserNameSet("New\nUserName");
|
sharedParams.onUserNameSet("New\nUserName");
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi");
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, "NewUserName: hi");
|
||||||
QVERIFY(messageHasSelfMention(processedMessage));
|
QVERIFY(messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// Regression tests for: https://github.com/qTox/qTox/issues/2119
|
// Regression tests for: https://github.com/qTox/qTox/issues/2119
|
||||||
{
|
{
|
||||||
// Empty usernames should not match
|
// Empty usernames should not match
|
||||||
sharedParams.onUserNameSet("");
|
sharedParams.onUserNameSet("");
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, "");
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, "");
|
||||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||||
|
|
||||||
// Empty usernames matched on everything, ensure this is not the case
|
// Empty usernames matched on everything, ensure this is not the case
|
||||||
processedMessage = messageProcessor.processIncomingMessage(false, "a");
|
processedMessage = messageProcessor.processIncomingCoreMessage(false, "a");
|
||||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ void TestMessageProcessor::testOutgoingMessage()
|
||||||
testStr += "a";
|
testStr += "a";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto messages = messageProcessor.processOutgoingMessage(false, testStr);
|
auto messages = messageProcessor.processOutgoingMessage(false, testStr, true /*needsSplit*/);
|
||||||
|
|
||||||
// The message processor should split our messages
|
// The message processor should split our messages
|
||||||
QVERIFY(messages.size() == 2);
|
QVERIFY(messages.size() == 2);
|
||||||
|
@ -147,7 +147,7 @@ void TestMessageProcessor::testIncomingMessage()
|
||||||
// Nothing too special happening on the incoming side if we aren't looking for self mentions
|
// Nothing too special happening on the incoming side if we aren't looking for self mentions
|
||||||
auto sharedParams = MessageProcessor::SharedParams();
|
auto sharedParams = MessageProcessor::SharedParams();
|
||||||
auto messageProcessor = MessageProcessor(sharedParams);
|
auto messageProcessor = MessageProcessor(sharedParams);
|
||||||
auto message = messageProcessor.processIncomingMessage(false, "test");
|
auto message = messageProcessor.processIncomingCoreMessage(false, "test");
|
||||||
|
|
||||||
QVERIFY(message.isAction == false);
|
QVERIFY(message.isAction == false);
|
||||||
QVERIFY(message.content == "test");
|
QVERIFY(message.content == "test");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user