Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create ThreadView to track threads in a room #825

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ target_sources(${QUOTIENT_LIB_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS .
Quotient/events/keyverificationevent.h
Quotient/keyimport.h
Quotient/qt_connection_util.h
Quotient/threadview.h
PRIVATE
Quotient/function_traits.cpp
Quotient/networkaccessmanager.cpp
Expand Down Expand Up @@ -247,6 +248,7 @@ target_sources(${QUOTIENT_LIB_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS .
Quotient/e2ee/cryptoutils.cpp
Quotient/e2ee/sssshandler.cpp
Quotient/keyimport.cpp
Quotient/threadview.cpp
libquotientemojis.qrc
)

Expand Down
5 changes: 3 additions & 2 deletions Quotient/events/roommessageevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,10 @@ QString RoomMessageEvent::threadRootEventId() const
const auto relation = relatesTo();
if (relation && relation.value().type == EventRelation::ThreadType) {
return relation.value().eventId;
} else {
return unsignedPart<QJsonObject>("m.relations"_ls)[EventRelation::ThreadType].toString();
} else if (unsignedPart<QJsonObject>("m.relations"_L1).contains(EventRelation::ThreadType)) {
return id();
}
return {};
}

namespace {
Expand Down
4 changes: 4 additions & 0 deletions Quotient/events/roommessageevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent {
//!
//! \note This will return the ID of the event if it is the thread root.
//!
//! \note If the event is the thread root event and has not been updated with the server-side
//! the function will return an empty string as we can't tell if the message
//! is threaded.
//!
//! \return The event ID of the thread root if threaded, an empty string otherwise.
QString threadRootEventId()const;

Expand Down
46 changes: 46 additions & 0 deletions Quotient/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "roommember.h"
#include "roomstateview.h"
#include "syncdata.h"
#include "threadview.h"
#include "user.h"

#include "csapi/account-data.h"
Expand Down Expand Up @@ -123,6 +124,8 @@ class Q_DECL_HIDDEN Room::Private {
// about the timeline.
EventStats partiallyReadStats {}, unreadStats {};

ThreadView threads;

// For storing a list of current member names for the purpose of disambiguation.
QMultiHash<QString, QString> memberNameMap;
QStringList membersInvited;
Expand Down Expand Up @@ -270,6 +273,8 @@ class Q_DECL_HIDDEN Room::Private {
Timeline::size_type moveEventsToTimeline(RoomEventsRange events,
EventsPlacement placement);

void updateThread(const RoomEvent* event);

/**
* Remove events from the passed container that are already in the timeline
*/
Expand Down Expand Up @@ -555,6 +560,8 @@ const Room::PendingEvents& Room::pendingEvents() const
return d->unsyncedEvents;
}

const ThreadView& Room::threads() const { return d->threads; }

int Room::requestedHistorySize() const
{
return eventsHistoryJob() != nullptr ? d->lastRequestedHistorySize : 0;
Expand Down Expand Up @@ -1752,12 +1759,51 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events,
if (auto n = q->checkForNotifications(ti); n.type != Notification::None)
notifications.insert(eId, n);
Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId);
updateThread(ti.event());
}
const auto insertedSize = (index - baseIndex) * placement;
Q_ASSERT(insertedSize == int(events.size()));
return Timeline::size_type(insertedSize);
}

void Room::Private::updateThread(const RoomEvent* event)
{
const auto rme = eventCast<const RoomMessageEvent>(event);
if (rme == nullptr) {
return;
}
if (!rme->isThreaded()) {
return;
}

if (threads.exists(rme->threadRootEventId())) {
auto thread = threads.getThread(rme->threadRootEventId());
const auto threadLatestIndex = eventsIndex.constFind(thread->latestEventId());
if (threadLatestIndex == eventsIndex.cend()) {
// Assume this is the latest event. This shouldn't happen but we can work around it.
thread->addEvent(rme, true, rme->senderId() == connection->userId());
return;
}

const auto eventIndexIt = eventsIndex.constFind(rme->id());
if (eventIndexIt == eventsIndex.cend()) {
qCCritical(EVENTS) << rme->id() << "not in the timeline. Update a thread after moving the event to timeline.";
}

thread->addEvent(rme, *eventIndexIt > *threadLatestIndex, rme->senderId() == connection->userId());
} else {
if (!event->id().isEmpty()) {
threads.add(std::make_unique<Thread>(rme->threadRootEventId(),
// For pending events we can get the full correct details when the remote echo comes in.
rme->id().isEmpty() ? rme->threadRootEventId() : rme->id(),
// When we can't find the root we assume its a historical event that will load later if
// we can find it we assume a new thread was just created.
rme->id().isEmpty() || q->findInTimeline(rme->threadRootEventId()) == historyEdge() ? 1 : 2,
rme->senderId() == connection->userId()));
}
}
}

const Avatar& Room::memberAvatarObject(const QString& memberId) const
{
return connection()->userAvatar(member(memberId).avatarUrl());
Expand Down
4 changes: 4 additions & 0 deletions Quotient/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct MemberSorter;
class LeaveRoomJob;
class SetRoomStateWithKeyJob;
class RedactEventJob;
class ThreadView;

/** The data structure used to expose file transfer information to views
*
Expand Down Expand Up @@ -363,6 +364,9 @@ class QUOTIENT_API Room : public QObject {
//!
//! Same as messageEvents().crend()
rev_iter_t historyEdge() const;

const ThreadView& threads() const;

//! \brief Get an iterator for the position beyond the latest arrived event
//!
//! Same as messageEvents().cend()
Expand Down
78 changes: 78 additions & 0 deletions Quotient/threadview.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "threadview.h"

#include "events/roommessageevent.h"

using namespace Quotient;

namespace {
inline auto checkThreadRoot(const QString& threadRootId) {
return [threadRootId](const std::unique_ptr<Thread>& thread) { return threadRootId == thread->threadRootId(); };
}
}

void ThreadView::add(std::unique_ptr<Thread> thread)
{
_threads.push_back(std::move(thread));
}

bool ThreadView::erase(const QString& threadRootId)
{
return std::erase_if(_threads, checkThreadRoot(threadRootId)) > 0;
}

bool ThreadView::exists(const QString& threadRootId) const
{
return std::ranges::any_of(_threads, checkThreadRoot(threadRootId));
}

Thread* ThreadView::getThread(const QString& threadRootId) const
{
auto threadIt = std::ranges::find_if(_threads, checkThreadRoot(threadRootId));
if (threadIt == _threads.end()) {
return nullptr;
}
return threadIt->get();
}

Thread::Thread(QString threadRootId, QString latestEventId, int size, bool localUserParticipated)
nvrWhere marked this conversation as resolved.
Show resolved Hide resolved
: _threadRootId(threadRootId), _latestEventId(latestEventId), _size(size), _localUserParticipated(localUserParticipated)
{}

QString Thread::threadRootId() const
{
return _threadRootId;
}

QString Thread::latestEventId() const
{
return _latestEventId;
}

int Thread::size() const
{
return _size;
}

bool Thread::localUserParticipated() const
{
return _localUserParticipated;
}

bool Thread::addEvent(const RoomMessageEvent* event, bool isLatest, bool isLocalUser)
{
if (event->threadRootEventId() != _threadRootId) {
return false;
}
if (isLatest) {
_latestEventId = event->id();
}
++_size;
if (!_localUserParticipated) {
_localUserParticipated = isLocalUser;
}

return true;
}
49 changes: 49 additions & 0 deletions Quotient/threadview.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: LGPL-2.1-or-later

#pragma once

#include <QString>
#include <QJsonObject>

#include "quotient_export.h"

namespace Quotient {

class RoomMessageEvent;

class QUOTIENT_API Thread {
public:
explicit Thread(QString threadRootId, QString latestEventId, int size, bool localUserParticipated);

QString threadRootId() const;
QString latestEventId() const;
int size() const;
bool localUserParticipated() const;

bool addEvent(const RoomMessageEvent* event, bool isLatest, bool isLocalUser);

private:
const QString _threadRootId;
QString _latestEventId;
int _size;
bool _localUserParticipated;
};

class QUOTIENT_API ThreadView {
public:
ThreadView() = default;

void add(std::unique_ptr<Thread> thread);

bool erase(const QString& threadRootId);

bool exists(const QString& threadRootId) const;

Thread* getThread(const QString& threadRootId) const;

private:
std::vector<std::unique_ptr<Thread>> _threads;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why std::unique_ptr and not Thread itself? I don't see polymorphism being involved, and I'm not sure what other reasons might require dynamic allocation of a structure the size of 4 pointers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently the Thread object is not copy assignable by design so this makes erasing possible. Maybe however this is just a sign that std::vector is not the right container

};

} // namespace Quotient
2 changes: 1 addition & 1 deletion gtad/gtad
Submodule gtad updated 17 files
+44 −99 .clang-format
+0 −173 .clang-tidy
+20 −14 CMakeLists.txt
+195 −218 README.md
+344 −465 analyzer.cpp
+32 −33 analyzer.h
+6 −7 main.cpp
+39 −30 model.cpp
+51 −89 model.h
+136 −110 printer.cpp
+5 −6 printer.h
+150 −176 translator.cpp
+11 −14 translator.h
+0 −2 util.h
+1 −1 yaml-cpp
+55 −91 yaml.cpp
+211 −356 yaml.h
Loading