Skip to content

Commit 4939b62

Browse files
KitsuneRalKitsune Ral
authored andcommitted
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 8d1edd5 commit 4939b62

File tree

2 files changed

+83
-62
lines changed

2 files changed

+83
-62
lines changed

Quotient/events/redactionevent.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,21 @@ class QUOTIENT_API RedactionEvent : public RoomEvent {
1212

1313
using RoomEvent::RoomEvent;
1414

15+
[[deprecated("Use redactedEvents() instead")]]
1516
QString redactedEvent() const
1617
{
1718
return fullJson()["redacts"_ls].toString();
1819
}
20+
QStringList redactedEvents() const
21+
{
22+
const auto evtIdJson = contentJson()["redacts"_ls];
23+
if (evtIdJson.isArray())
24+
return fromJson<QStringList>(evtIdJson); // MSC2244: a list of ids
25+
if (evtIdJson.isString())
26+
return { fromJson<QString>(evtIdJson) }; // MSC2174: id in content
27+
return { fullJson()["redacts"_ls].toString() }; // legacy fallback
28+
}
29+
1930
QUO_CONTENT_GETTER(QString, reason)
2031
};
2132
} // namespace Quotient

Quotient/room.cpp

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,10 @@ class Q_DECL_HIDDEN Room::Private {
343343

344344
/*! Apply redaction to the timeline
345345
*
346-
* Tries to find an event in the timeline and redact it; deletes the
347-
* redaction event whether the redacted event was found or not.
348-
* \return true if the event has been found and redacted; false otherwise
346+
* Tries to find events in the timeline and redact them.
347+
* \return the list of event ids that were NOT found and redacted
349348
*/
350-
bool processRedaction(const RedactionEvent& redaction);
349+
QStringList processRedaction(const RedactionEvent& redaction);
351350

352351
/*! Apply a new revision of the event to the timeline
353352
*
@@ -2836,59 +2835,67 @@ RoomEventPtr makeRedacted(const RoomEvent& target,
28362835
return loadEvent<RoomEvent>(originalJson);
28372836
}
28382837

2839-
bool Room::Private::processRedaction(const RedactionEvent& redaction)
2838+
QStringList Room::Private::processRedaction(const RedactionEvent& redaction)
28402839
{
2840+
QStringList unredactedIds;
28412841
// Can't use findInTimeline because it returns a const iterator, and
28422842
// we need to change the underlying TimelineItem.
2843-
const auto pIdx = eventsIndex.constFind(redaction.redactedEvent());
2844-
if (pIdx == eventsIndex.cend())
2845-
return false;
2846-
2847-
Q_ASSERT(q->isValidIndex(*pIdx));
2843+
const auto& eventIds = redaction.redactedEvents();
2844+
for (const auto& evtId: eventIds) {
2845+
const auto pIdx = eventsIndex.constFind(evtId);
2846+
if (pIdx == eventsIndex.cend()) {
2847+
unredactedIds.push_back(evtId);
2848+
continue;
2849+
}
28482850

2849-
auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
2850-
if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) {
2851-
qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event"
2852-
<< ti->id() << "already done, skipping";
2853-
return true;
2854-
}
2855-
if (ti->is<RoomMessageEvent>())
2856-
FileMetadataMap::remove(id, ti->id());
2851+
Q_ASSERT(q->isValidIndex(*pIdx));
28572852

2858-
// Make a new event from the redacted JSON and put it in the timeline
2859-
// instead of the redacted one. oldEvent will be deleted on return.
2860-
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
2861-
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with" << redaction.id();
2862-
if (oldEvent->isStateEvent()) {
2863-
// Check whether the old event was a part of current state; if it was,
2864-
// update the current state to the redacted event object.
2865-
const auto currentStateEvt =
2866-
currentState.get(oldEvent->matrixType(), oldEvent->stateKey());
2867-
Q_ASSERT(currentStateEvt);
2868-
if (currentStateEvt == oldEvent.get()) {
2869-
// Historical states can't be in currentState
2870-
Q_ASSERT(ti.index() >= 0);
2871-
qCDebug(STATE).nospace()
2872-
<< "Redacting state " << oldEvent->matrixType() << "/"
2873-
<< oldEvent->stateKey();
2874-
// Retarget the current state to the newly made event.
2875-
if (q->processStateEvent(*ti))
2876-
emit q->namesChanged(q);
2877-
updateDisplayname();
2853+
auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
2854+
if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) {
2855+
qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event"
2856+
<< ti->id() << "already done, skipping";
2857+
continue;
28782858
}
2879-
}
2880-
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
2881-
const auto& content = reaction->content().value;
2882-
const std::pair lookupKey { content.eventId, content.type };
2883-
if (relations.contains(lookupKey)) {
2884-
relations[lookupKey].removeOne(reaction);
2885-
emit q->updatedEvent(content.eventId);
2859+
if (ti->is<RoomMessageEvent>())
2860+
FileMetadataMap::remove(id, ti->id());
2861+
2862+
2863+
// Make a new event from the redacted JSON and put it in the timeline
2864+
// instead of the redacted one. oldEvent will be deleted on return.
2865+
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
2866+
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with"
2867+
<< redaction.id();
2868+
if (oldEvent->isStateEvent()) {
2869+
// Check whether the old event was a part of current state; if it was,
2870+
// update the current state to the redacted event object.
2871+
const auto currentStateEvt =
2872+
currentState.get(oldEvent->matrixType(), oldEvent->stateKey());
2873+
Q_ASSERT(currentStateEvt);
2874+
if (currentStateEvt == oldEvent.get()) {
2875+
// Historical states can't be in currentState
2876+
Q_ASSERT(ti.index() >= 0);
2877+
qCDebug(STATE).nospace()
2878+
<< "Redacting state " << oldEvent->matrixType() << "/"
2879+
<< oldEvent->stateKey();
2880+
// Retarget the current state to the newly made event.
2881+
if (q->processStateEvent(*ti))
2882+
emit q->namesChanged(q);
2883+
updateDisplayname();
2884+
}
2885+
}
2886+
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
2887+
const auto& content = reaction->content().value;
2888+
const std::pair lookupKey { content.eventId, content.type };
2889+
if (relations.contains(lookupKey)) {
2890+
relations[lookupKey].removeOne(reaction);
2891+
emit q->updatedEvent(conten.eventId);
2892+
}
28862893
}
2894+
q->onRedaction(*oldEvent, *ti);
2895+
emit q->replacedEvent(ti.event(), std::to_address(oldEvent));
2896+
// By now, all references to oldEvent must have been updated to ti.event()
28872897
}
2888-
q->onRedaction(*oldEvent, *ti);
2889-
emit q->replacedEvent(ti.event(), std::to_address(oldEvent));
2890-
// By now, all references to oldEvent must have been updated to ti.event()
2891-
return true;
2898+
return unredactedIds;
28922899
}
28932900

28942901
/** Make a replaced event
@@ -3011,19 +3018,22 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
30113018
auto it = std::find_if(events.begin(), events.end(), isEditing);
30123019
for (const auto& eptr : RoomEventsRange(it, events.end())) {
30133020
if (auto* r = eventCast<RedactionEvent>(eptr)) {
3014-
// Try to find the target in the timeline, then in the batch.
3015-
if (processRedaction(*r))
3016-
continue;
3017-
if (auto targetIt = std::find_if(events.begin(), events.end(),
3018-
[id = r->redactedEvent()](const RoomEventPtr& ep) {
3019-
return ep->id() == id;
3020-
}); targetIt != events.end())
3021-
*targetIt = makeRedacted(**targetIt, *r);
3022-
else
3023-
qCDebug(STATE)
3024-
<< "Redaction" << r->id() << "ignored: target event"
3025-
<< r->redactedEvent() << "is not found";
3026-
// If the target event comes later, it comes already redacted.
3021+
// Try to find the targets in the timeline, then in the batch.
3022+
const auto unredactedIds = processRedaction(*r);
3023+
for (const auto& idToRedact: unredactedIds) {
3024+
if (auto targetIt =
3025+
std::find_if(events.begin(), it,
3026+
[&idToRedact](const RoomEventPtr& ep) {
3027+
return ep->id() == idToRedact;
3028+
});
3029+
targetIt != it)
3030+
*targetIt = makeRedacted(**targetIt, *r);
3031+
else
3032+
qCDebug(EVENTS)
3033+
<< "Target event" << idToRedact << "in redaction"
3034+
<< r->id() << "is not found";
3035+
// If the target event comes later, it comes already redacted.
3036+
}
30273037
}
30283038
if (auto* msg = eventCast<RoomMessageEvent>(eptr);
30293039
msg && !msg->replacedEvent().isEmpty()) {

0 commit comments

Comments
 (0)