Skip to content

Commit

Permalink
Support multiple event ids in RedactionEvent (MSC2244)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
KitsuneRal committed Nov 22, 2019
1 parent fdd094a commit 44d3daf
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 57 deletions.
10 changes: 10 additions & 0 deletions lib/events/redactionevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,20 @@ class RedactionEvent : public RoomEvent {
explicit RedactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj)
{}

[[deprecated("Use redactedEvents() instead")]]
QString redactedEvent() const
{
return fullJson()["redacts"_ls].toString();
}
QStringList redactedEvents() const
{
const auto evtIdJson = contentJson()["redacts"_ls];
if (evtIdJson.isArray())
return fromJson<QStringList>(evtIdJson); // MSC2244: a list of ids
if (evtIdJson.isString())
return { fromJson<QString>(evtIdJson) }; // MSC2174: id in content
return { fullJson()["redacts"_ls].toString() }; // legacy fallback
}
QString reason() const { return contentJson()["reason"_ls].toString(); }
};
REGISTER_EVENT_TYPE(RedactionEvent)
Expand Down
121 changes: 64 additions & 57 deletions lib/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,10 @@ class Room::Private {

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

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

bool Room::Private::processRedaction(const RedactionEvent& redaction)
QStringList Room::Private::processRedaction(const RedactionEvent& redaction)
{
QStringList unredactedIds;
// Can't use findInTimeline because it returns a const iterator, and
// we need to change the underlying TimelineItem.
const auto pIdx = eventsIndex.find(redaction.redactedEvent());
if (pIdx == eventsIndex.end())
return false;
const auto& eventIds = redaction.redactedEvents();
for (const auto& evtId: eventIds) {
const auto pIdx = eventsIndex.find(evtId);
if (pIdx == eventsIndex.end()) {
unredactedIds.push_back(evtId);
continue;
}

Q_ASSERT(q->isValidIndex(*pIdx));
Q_ASSERT(q->isValidIndex(*pIdx));

auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) {
qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event"
<< ti->id() << "already done, skipping";
return true;
}
auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) {
qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event"
<< ti->id() << "already done, skipping";
continue;
}

// Make a new event from the redacted JSON and put it in the timeline
// instead of the redacted one. oldEvent will be deleted on return.
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with" << redaction.id();
if (oldEvent->isStateEvent()) {
const StateEventKey evtKey { oldEvent->matrixType(),
oldEvent->stateKey() };
Q_ASSERT(currentState.contains(evtKey));
if (currentState.value(evtKey) == oldEvent.get()) {
Q_ASSERT(ti.index() >= 0); // Historical states can't be in
// currentState
qCDebug(EVENTS).nospace()
<< "Redacting state " << oldEvent->matrixType() << "/"
<< oldEvent->stateKey();
// Retarget the current state to the newly made event.
if (q->processStateEvent(*ti))
emit q->namesChanged(q);
updateDisplayname();
// Make a new event from the redacted JSON and put it in the timeline
// instead of the redacted one. oldEvent will be deleted on return.
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with"
<< redaction.id();
if (oldEvent->isStateEvent()) {
const StateEventKey evtKey { oldEvent->matrixType(),
oldEvent->stateKey() };
Q_ASSERT(currentState.contains(evtKey));
if (currentState.value(evtKey) == oldEvent.get()) {
Q_ASSERT(ti.index() >= 0); // Historical states can't be in
// currentState
qCDebug(EVENTS).nospace()
<< "Redacting state " << oldEvent->matrixType() << "/"
<< oldEvent->stateKey();
// Retarget the current state to the newly made event.
if (q->processStateEvent(*ti))
emit q->namesChanged(q);
updateDisplayname();
}
}
}
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
const auto& targetEvtId = reaction->relation().eventId;
const auto lookupKey =
qMakePair(targetEvtId, EventRelation::Annotation());
if (relations.contains(lookupKey)) {
relations[lookupKey].removeOne(reaction);
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
const auto& targetEvtId = reaction->relation().eventId;
const auto lookupKey =
qMakePair(targetEvtId, EventRelation::Annotation());
if (relations.contains(lookupKey)) {
relations[lookupKey].removeOne(reaction);
}
}
q->onRedaction(*oldEvent, *ti);
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
}
q->onRedaction(*oldEvent, *ti);
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
return true;
return unredactedIds;
}

/** Make a replaced event
Expand Down Expand Up @@ -2120,20 +2126,21 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
for (const auto& eptr : RoomEventsRange(it, events.end())) {
if (auto* r = eventCast<RedactionEvent>(eptr)) {
// Try to find the target in the timeline, then in the batch.
if (processRedaction(*r))
continue;
auto targetIt = std::find_if(events.begin(), it,
[id = r->redactedEvent()](
const RoomEventPtr& ep) {
return ep->id() == id;
});
if (targetIt != it)
*targetIt = makeRedacted(**targetIt, *r);
else
qCDebug(EVENTS)
<< "Redaction" << r->id() << "ignored: target event"
<< r->redactedEvent() << "is not found";
// If the target event comes later, it comes already redacted.
const auto unredactedIds = processRedaction(*r);
for (const auto& idToRedact: unredactedIds) {
if (auto targetIt =
std::find_if(events.begin(), it,
[idToRedact](const RoomEventPtr& ep) {
return ep->id() == idToRedact;
});
targetIt != it)
*targetIt = makeRedacted(**targetIt, *r);
else
qCDebug(EVENTS)
<< "Target event" << idToRedact << "in redaction"
<< r->id() << "is not found";
// If the target event comes later, it comes already redacted.
}
}
if (auto* msg = eventCast<RoomMessageEvent>(eptr)) {
if (!msg->replacedEvent().isEmpty()) {
Expand Down

0 comments on commit 44d3daf

Please sign in to comment.