Skip to content

Commit 44d3daf

Browse files
committed
Support multiple event ids in RedactionEvent (MSC2244)
Cross-ref: matrix-org/matrix-spec-proposals#2244. The implementation does not validate the redaction event contents against a particular room version, accepting both singular event ids and lists of ids in any room. (In fact, Quotient cannot create objects of different classes for the same event type depending on the room version - see #362.) Also, this commit doesn't change Room::redactEvent - it still only gets a single event id.
1 parent fdd094a commit 44d3daf

2 files changed

Lines changed: 74 additions & 57 deletions

File tree

lib/events/redactionevent.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,20 @@ class RedactionEvent : public RoomEvent {
2828
explicit RedactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj)
2929
{}
3030

31+
[[deprecated("Use redactedEvents() instead")]]
3132
QString redactedEvent() const
3233
{
3334
return fullJson()["redacts"_ls].toString();
3435
}
36+
QStringList redactedEvents() const
37+
{
38+
const auto evtIdJson = contentJson()["redacts"_ls];
39+
if (evtIdJson.isArray())
40+
return fromJson<QStringList>(evtIdJson); // MSC2244: a list of ids
41+
if (evtIdJson.isString())
42+
return { fromJson<QString>(evtIdJson) }; // MSC2174: id in content
43+
return { fullJson()["redacts"_ls].toString() }; // legacy fallback
44+
}
3545
QString reason() const { return contentJson()["reason"_ls].toString(); }
3646
};
3747
REGISTER_EVENT_TYPE(RedactionEvent)

lib/room.cpp

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -321,11 +321,10 @@ class Room::Private {
321321

322322
/*! Apply redaction to the timeline
323323
*
324-
* Tries to find an event in the timeline and redact it; deletes the
325-
* redaction event whether the redacted event was found or not.
326-
* \return true if the event has been found and redacted; false otherwise
324+
* Tries to find events in the timeline and redact them.
325+
* \return the list of event ids that were NOT found and redacted
327326
*/
328-
bool processRedaction(const RedactionEvent& redaction);
327+
QStringList processRedaction(const RedactionEvent& redaction);
329328

330329
/*! Apply a new revision of the event to the timeline
331330
*
@@ -1989,54 +1988,61 @@ RoomEventPtr makeRedacted(const RoomEvent& target,
19891988
return loadEvent<RoomEvent>(originalJson);
19901989
}
19911990

1992-
bool Room::Private::processRedaction(const RedactionEvent& redaction)
1991+
QStringList Room::Private::processRedaction(const RedactionEvent& redaction)
19931992
{
1993+
QStringList unredactedIds;
19941994
// Can't use findInTimeline because it returns a const iterator, and
19951995
// we need to change the underlying TimelineItem.
1996-
const auto pIdx = eventsIndex.find(redaction.redactedEvent());
1997-
if (pIdx == eventsIndex.end())
1998-
return false;
1996+
const auto& eventIds = redaction.redactedEvents();
1997+
for (const auto& evtId: eventIds) {
1998+
const auto pIdx = eventsIndex.find(evtId);
1999+
if (pIdx == eventsIndex.end()) {
2000+
unredactedIds.push_back(evtId);
2001+
continue;
2002+
}
19992003

2000-
Q_ASSERT(q->isValidIndex(*pIdx));
2004+
Q_ASSERT(q->isValidIndex(*pIdx));
20012005

2002-
auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
2003-
if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) {
2004-
qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event"
2005-
<< ti->id() << "already done, skipping";
2006-
return true;
2007-
}
2006+
auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
2007+
if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) {
2008+
qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event"
2009+
<< ti->id() << "already done, skipping";
2010+
continue;
2011+
}
20082012

2009-
// Make a new event from the redacted JSON and put it in the timeline
2010-
// instead of the redacted one. oldEvent will be deleted on return.
2011-
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
2012-
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with" << redaction.id();
2013-
if (oldEvent->isStateEvent()) {
2014-
const StateEventKey evtKey { oldEvent->matrixType(),
2015-
oldEvent->stateKey() };
2016-
Q_ASSERT(currentState.contains(evtKey));
2017-
if (currentState.value(evtKey) == oldEvent.get()) {
2018-
Q_ASSERT(ti.index() >= 0); // Historical states can't be in
2019-
// currentState
2020-
qCDebug(EVENTS).nospace()
2021-
<< "Redacting state " << oldEvent->matrixType() << "/"
2022-
<< oldEvent->stateKey();
2023-
// Retarget the current state to the newly made event.
2024-
if (q->processStateEvent(*ti))
2025-
emit q->namesChanged(q);
2026-
updateDisplayname();
2013+
// Make a new event from the redacted JSON and put it in the timeline
2014+
// instead of the redacted one. oldEvent will be deleted on return.
2015+
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
2016+
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with"
2017+
<< redaction.id();
2018+
if (oldEvent->isStateEvent()) {
2019+
const StateEventKey evtKey { oldEvent->matrixType(),
2020+
oldEvent->stateKey() };
2021+
Q_ASSERT(currentState.contains(evtKey));
2022+
if (currentState.value(evtKey) == oldEvent.get()) {
2023+
Q_ASSERT(ti.index() >= 0); // Historical states can't be in
2024+
// currentState
2025+
qCDebug(EVENTS).nospace()
2026+
<< "Redacting state " << oldEvent->matrixType() << "/"
2027+
<< oldEvent->stateKey();
2028+
// Retarget the current state to the newly made event.
2029+
if (q->processStateEvent(*ti))
2030+
emit q->namesChanged(q);
2031+
updateDisplayname();
2032+
}
20272033
}
2028-
}
2029-
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
2030-
const auto& targetEvtId = reaction->relation().eventId;
2031-
const auto lookupKey =
2032-
qMakePair(targetEvtId, EventRelation::Annotation());
2033-
if (relations.contains(lookupKey)) {
2034-
relations[lookupKey].removeOne(reaction);
2034+
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
2035+
const auto& targetEvtId = reaction->relation().eventId;
2036+
const auto lookupKey =
2037+
qMakePair(targetEvtId, EventRelation::Annotation());
2038+
if (relations.contains(lookupKey)) {
2039+
relations[lookupKey].removeOne(reaction);
2040+
}
20352041
}
2042+
q->onRedaction(*oldEvent, *ti);
2043+
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
20362044
}
2037-
q->onRedaction(*oldEvent, *ti);
2038-
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
2039-
return true;
2045+
return unredactedIds;
20402046
}
20412047

20422048
/** Make a replaced event
@@ -2120,20 +2126,21 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
21202126
for (const auto& eptr : RoomEventsRange(it, events.end())) {
21212127
if (auto* r = eventCast<RedactionEvent>(eptr)) {
21222128
// Try to find the target in the timeline, then in the batch.
2123-
if (processRedaction(*r))
2124-
continue;
2125-
auto targetIt = std::find_if(events.begin(), it,
2126-
[id = r->redactedEvent()](
2127-
const RoomEventPtr& ep) {
2128-
return ep->id() == id;
2129-
});
2130-
if (targetIt != it)
2131-
*targetIt = makeRedacted(**targetIt, *r);
2132-
else
2133-
qCDebug(EVENTS)
2134-
<< "Redaction" << r->id() << "ignored: target event"
2135-
<< r->redactedEvent() << "is not found";
2136-
// If the target event comes later, it comes already redacted.
2129+
const auto unredactedIds = processRedaction(*r);
2130+
for (const auto& idToRedact: unredactedIds) {
2131+
if (auto targetIt =
2132+
std::find_if(events.begin(), it,
2133+
[idToRedact](const RoomEventPtr& ep) {
2134+
return ep->id() == idToRedact;
2135+
});
2136+
targetIt != it)
2137+
*targetIt = makeRedacted(**targetIt, *r);
2138+
else
2139+
qCDebug(EVENTS)
2140+
<< "Target event" << idToRedact << "in redaction"
2141+
<< r->id() << "is not found";
2142+
// If the target event comes later, it comes already redacted.
2143+
}
21372144
}
21382145
if (auto* msg = eventCast<RoomMessageEvent>(eptr)) {
21392146
if (!msg->replacedEvent().isEmpty()) {

0 commit comments

Comments
 (0)