Skip to content

Commit

Permalink
Room::getEvent()
Browse files Browse the repository at this point in the history
You can now request an arbitrary event in the room, and save it for
future use (within a single running session, same as timeline).
  • Loading branch information
KitsuneRal committed Dec 16, 2024
1 parent 0cb2171 commit ded6a35
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 7 deletions.
71 changes: 64 additions & 7 deletions Quotient/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ using std::llround;
namespace {
enum EventsPlacement : int { Older = -1, Newer = 1 };

struct SingleEventRequest {
EventId eventId;
JobHandle<GetOneRoomEventJob> requestHandle;
std::vector<EventPromise> eventPromises{};
};

struct HistoryRequest {
EventId upToEventId;
QDeadlineTimer deadline;
Expand Down Expand Up @@ -118,6 +124,8 @@ class Q_DECL_HIDDEN Room::Private {

Timeline timeline;
PendingEvents unsyncedEvents;
std::unordered_map<EventId, event_ptr_tt<const RoomEvent>> cachedEvents;
std::vector<SingleEventRequest> singleEventRequests;
QHash<QString, TimelineItem::index_t> eventsIndex;
// A map from event id/relation type pairs to a vector of event pointers. Not using QMultiHash,
// because we want to quickly return a number of relations for a given event without enumerating
Expand Down Expand Up @@ -256,12 +264,30 @@ class Q_DECL_HIDDEN Room::Private {
}
return changes;
}
SingleEventRequest makeSingleEventRequest(const QString& eventId);
void addRelation(const ReactionEvent& reactionEvt);
void addRelations(auto from, auto to)
void finishEventPromises(const RoomEvent& evt)
{
if (auto it = std::ranges::find(singleEventRequests, evt.id(), &SingleEventRequest::eventId);
it != cend(singleEventRequests)) {
for (auto& p : it->eventPromises)
if (!p.isCanceled()) {
p.addResult(std::cref(evt));
p.finish();
}
if (!it->requestHandle.isFinished())
it->requestHandle.abandon();
singleEventRequests.erase(it);
}
}
void afterAddedMessages(auto from, auto to)
{
for (auto it = from; it != to; ++it)
if (const auto* reaction = it->template viewAs<ReactionEvent>())
addRelation(*reaction);
for (const auto& ti : std::ranges::subrange(from, to)) {
// NB: switchOnType() and std::bind_front() don't go well together
ti->switchOnType([this](const ReactionEvent& e) { addRelation(e); });
finishEventPromises(*ti);
cachedEvents.erase(ti->id());
}
}

Changes addNewMessageEvents(RoomEvents&& events);
Expand Down Expand Up @@ -1765,7 +1791,8 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events,
},
[](QUrl&&) {} },
fileContent->commonInfo().source);

// TODO: check if it has relations to another event and call getEvent() for it, _if_
// we are in the displayed area
if (auto n = q->checkForNotifications(ti); n.type != Notification::None)
notifications.insert(eId, n);
Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId);
Expand Down Expand Up @@ -2382,6 +2409,36 @@ JobHandle<GetRoomEventsJob> Room::getPreviousContent(int limit, const QString& f
return d->getPreviousContent(limit, filter);
}

inline SingleEventRequest Room::Private::makeSingleEventRequest(const QString& eventId)
{
return { eventId,
connection->callApi<GetOneRoomEventJob>(id, eventId).then([this](RoomEventPtr&& pEvt) {
const auto [it, cachedEventInserted] =
cachedEvents.insert_or_assign(pEvt->id(), std::move(pEvt));
finishEventPromises(*(it->second));
if (QUO_ALARM(!cachedEventInserted))
emit q->updatedEvent(it->first); // At least notify clients...
}) };
}

EventFuture Room::getEvent(const QString& eventId)
{
using namespace std::ranges;
if (auto timelineIt = findInTimeline(eventId); timelineIt != historyEdge())
return makeReadyValueFuture(std::cref(**timelineIt));
auto baseStateObjects = d->baseState | views::values;
if (auto stateIt = find(baseStateObjects, eventId, &RoomEvent::id);
stateIt != end(baseStateObjects))
return makeReadyValueFuture(std::cref<RoomEvent>(**stateIt));
if (auto cachedIt = d->cachedEvents.find(eventId); cachedIt != d->cachedEvents.end())
return makeReadyValueFuture(std::cref(*cachedIt->second));
auto alreadyRequestedIt = find(d->singleEventRequests, eventId, &SingleEventRequest::eventId);
if (alreadyRequestedIt == end(d->singleEventRequests))
alreadyRequestedIt = d->singleEventRequests.insert(d->singleEventRequests.cend(),
d->makeSingleEventRequest(eventId));
return alreadyRequestedIt->eventPromises.emplace_back().future();
}

EventFuture Room::ensureEvent(const QString& eventId, quint16 maxWaitSeconds)
{
if (auto eventIt = findInTimeline(eventId); eventIt != historyEdge())
Expand Down Expand Up @@ -3061,7 +3118,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
}

if (totalInserted > 0) {
addRelations(from, syncEdge());
afterAddedMessages(from, syncEdge());

qCDebug(MESSAGES) << "Room" << q->objectName() << "received"
<< totalInserted << "new events; the last event is now"
Expand Down Expand Up @@ -3120,7 +3177,7 @@ std::pair<Room::Changes, Room::rev_iter_t> Room::Private::addHistoricalMessageEv
q->onAddHistoricalTimelineEvents(from);
emit q->addedMessages(timeline.front().index(), from->index());

addRelations(from, historyEdge());
afterAddedMessages(from, historyEdge());
Q_ASSERT(timeline.size() == timelineSize + insertedSize);
if (insertedSize > 9 || et.nsecsElapsed() >= ProfilerMinNsecs)
qCDebug(PROFILER) << "Added" << insertedSize << "historical event(s) to" << q->objectName()
Expand Down
9 changes: 9 additions & 0 deletions Quotient/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,15 @@ class QUOTIENT_API Room : public QObject {

QJsonArray exportMegolmSessions();

//! \brief Obtain an arbitrary room event by its id
//!
//! Looks through the timeline, state events that arrived out of the timeline, and finally
//! cached individual events; if the event is not found locally, requests this one event from
//! the homeserver.
//! \return a ready future with the event reference if an event with \p eventId is found locally;
//! otherwise, a running future connected to the homeserver request
EventFuture getEvent(const QString& eventId);

//! \brief Loads the message history until the specified event id is found
//!
//! This is potentially heavy; clients should use this sparingly. One intended use case is
Expand Down
15 changes: 15 additions & 0 deletions quotest/quotest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ private slots:
TEST_DECL(sendReaction)
TEST_DECL(sendFile)
TEST_DECL(sendCustomEvent)
TEST_DECL(getEvent)
TEST_DECL(setTopic)
TEST_DECL(redactEvent)
TEST_DECL(changeName)
Expand Down Expand Up @@ -547,6 +548,20 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest,
return true;
}

TEST_IMPL(getEvent)
{
QUO_CHECK(targetRoom->maxTimelineIndex() - targetRoom->minTimelineIndex() > 5);
const auto& timelineEventIt = targetRoom->findInTimeline(targetRoom->maxTimelineIndex() - 5);
const auto timelineEventFuture = targetRoom->getEvent((*timelineEventIt)->id());
FAIL_TEST_IF(!timelineEventFuture.isFinished());
FINISH_TEST_IF(&timelineEventFuture.result().get() == timelineEventIt->event());
const auto& stateEvent = targetRoom->creation();
const auto stateEventFuture = targetRoom->getEvent(stateEvent->id());
FAIL_TEST_IF(!stateEventFuture.isFinished());
FINISH_TEST_IF(&stateEventFuture.result().get() == stateEvent);
FINISH_TEST(true); // TODO: Test a request to an event that is not loaded yet
}

DEFINE_SIMPLE_EVENT(CustomEvent, RoomEvent, "quotest.custom", int, testValue,
"test_value")

Expand Down

0 comments on commit ded6a35

Please sign in to comment.