Skip to content

Commit

Permalink
Room::ensureEvent()
Browse files Browse the repository at this point in the history
Pulls out the logic recently introduced in Quotest, to the library so
that everybody could benefit from it.
  • Loading branch information
KitsuneRal committed Dec 3, 2024
1 parent 75d559d commit d5ccb08
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 16 deletions.
45 changes: 45 additions & 0 deletions Quotient/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2330,6 +2330,51 @@ JobHandle<GetRoomEventsJob> Room::getPreviousContent(int limit, const QString& f
return d->getPreviousContent(limit, filter);
}

QFuture<const RoomEvent*> Room::ensureEvent(const QString& eventId, std::chrono::seconds maxWaitTime)
{
if (auto eventIt = findInTimeline(eventId); eventIt != historyEdge())
return makeReadyValueFuture(eventIt->event());

auto&& p = QPromise<const RoomEvent*>{};
auto future = p.future();
connectUntil(this, &Room::addedMessages, this,
[this, promise = std::move(p), eventId,
deadline = QDeadlineTimer(maxWaitTime, Qt::VeryCoarseTimer)](int fromIndex,
int toIndex) mutable {
using namespace std::ranges;
if (toIndex < 0) { // If it's >=0 then it's from sync, not interesting
const auto rangeToCheck =
subrange(findInTimeline(fromIndex), findInTimeline(toIndex) + 1);
if (auto it = find(rangeToCheck, eventId, &RoomEvent::id);
it != rangeToCheck.cend()) {
promise.addResult(it->event());
promise.finish();
return true;
}
}
if (deadline.hasExpired()) {
qCWarning(MESSAGES)
<< "Timeout - giving up on obtaining event" << eventId;
promise.future().cancel();
return true;
}
if (toIndex < 0) {
static constexpr auto EventsProgression =
std::array{ 50, 100, 200, 500, 1000 };
static_assert(is_sorted(EventsProgression));
const auto thisMany =
d->lastRequestedHistorySize >= EventsProgression.back()
? EventsProgression.back()
: *upper_bound(EventsProgression, d->lastRequestedHistorySize);
qCDebug(MESSAGES)
<< "Requesting" << thisMany << "events to ensure event" << eventId;
getPreviousContent(thisMany);
}
return false;
});
return future;
}

JobHandle<GetRoomEventsJob> Room::Private::getPreviousContent(int limit, const QString& filter)
{
if (!prevBatch)
Expand Down
14 changes: 14 additions & 0 deletions Quotient/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,20 @@ class QUOTIENT_API Room : public QObject {

QJsonArray exportMegolmSessions();

//! \brief Loads the message history until the specified event id is found
//!
//! This is much heavier than getEventFromServer(); clients should use this sparingly.
//! One intended use case for that is loading the timeline until the last read event, assuming
//! that the last read event is not too far back and that the user will read or at least scroll
//! through the just loaded events anyway. This will not be necessary once we move to sliding
//! sync but sliding sync support is still a bit away in the future.
//! Because the process is heavy (particularly on the homeserver), ensureEvent() will cancel
//! after \p maxWaitTime. It is not recommended to raise this too much.
//! \return the future that resolves to the event with \p eventId, or self-cancels if the event
//! is not found
Q_INVOKABLE QFuture<const RoomEvent*> ensureEvent(
const QString& eventId, std::chrono::seconds maxWaitTime = std::chrono::seconds(60));

public Q_SLOTS:
/** Check whether the room should be upgraded */
void checkVersion();
Expand Down
21 changes: 5 additions & 16 deletions quotest/quotest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -573,18 +573,6 @@ TEST_IMPL(setTopic)
return false;
}

// TODO: maybe move it to Room?..
QFuture<void> ensureEvent(Room* room, const QString& evtId, QPromise<void>&& p = QPromise<void>{})
{
auto future = p.future();
if (room->findInTimeline(evtId) == room->historyEdge()) {
clog << "Loading a page of history, " << room->timelineSize() << " events so far\n";
room->getPreviousContent().then(std::bind_front(ensureEvent, room, evtId, std::move(p)));
} else
p.finish();
return future;
}

TEST_IMPL(redactEvent)
{
using TargetEventType = RoomMemberEvent;
Expand All @@ -599,9 +587,9 @@ TEST_IMPL(redactEvent)
Q_ASSERT(memberEventToRedact); // ...or the room state is totally screwed
const auto& evtId = memberEventToRedact->id();

// Make sure the event is loaded in the timeline before proceeding with the test, to make sure
// the replacement tracked below actually occurs
ensureEvent(targetRoom, evtId).then([this, thisTest, evtId] {
// Make sure the event is loaded in the timeline before proceeding with the test, so that
// Room::replacedEvent is actually emitted
targetRoom->ensureEvent(evtId).then([this, thisTest, evtId](auto) {
clog << "Redacting the latest member event" << endl;
targetRoom->redactEvent(evtId, origin);
connectUntil(targetRoom, &Room::replacedEvent, this,
Expand All @@ -611,7 +599,8 @@ TEST_IMPL(redactEvent)
if (evt->id() != evtId)
return false;
FINISH_TEST(evt->switchOnType([this](const TargetEventType& e) {
return e.redactionReason() == origin && e.membership() == Membership::Join;
return e.redactionReason() == origin
&& e.membership() == Membership::Join;
// The second condition above tests MSC2176 - if it's violated (pre 0.8
// beta), membership() ends up being Membership::Undefined
}));
Expand Down

0 comments on commit d5ccb08

Please sign in to comment.