aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/room.cpp141
1 files changed, 81 insertions, 60 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index e99f9235..32cef571 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -231,7 +231,7 @@ class Room::Private
* Tries to find an event in the timeline and redact it; deletes the
* redaction event whether the redacted event was found or not.
*/
- void processRedaction(const RedactionEvent* redaction);
+ bool processRedaction(const RedactionEvent& redaction);
void broadcastTagUpdates()
{
@@ -979,6 +979,7 @@ inline auto makeErrorStr(const Event& e, QByteArray msg)
Room::Timeline::difference_type Room::Private::moveEventsToTimeline(
RoomEventsRange events, EventsPlacement placement)
{
+ Q_ASSERT(!events.empty());
// Historical messages arrive in newest-to-oldest order, so the process for
// them is symmetric to the one for new messages.
auto index = timeline.empty() ? -int(placement) :
@@ -996,31 +997,14 @@ Room::Timeline::difference_type Room::Private::moveEventsToTimeline(
makeErrorStr(*e, "Event is already in the timeline; "
"incoming events were not properly deduplicated"));
if (placement == Older)
- {
- // No need to process redaction events here: historical redacted
- // events already come redacted.
-#ifndef KEEP_REDACTIONS_IN_TIMELINE
- if (is<RedactionEvent>(*e))
- continue;
-#endif
timeline.emplace_front(move(e), --index);
- }
else
- {
- if (auto* redEvt = eventCast<const RedactionEvent>(e))
- {
- processRedaction(redEvt);
-#ifndef KEEP_REDACTIONS_IN_TIMELINE
- continue;
-#endif
- }
timeline.emplace_back(move(e), ++index);
- }
eventsIndex.insert(eId, index);
Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId);
}
const auto insertedSize = (index - baseIndex) * int(placement);
- Q_ASSERT(insertedSize >= 0);
+ Q_ASSERT(insertedSize == int(events.size()));
return insertedSize;
}
@@ -1463,35 +1447,16 @@ void Room::Private::dropDuplicateEvents(RoomEvents& events) const
events.erase(dupsBegin, events.end());
}
-inline bool isRedaction(const RoomEventPtr& e)
-{
- Q_ASSERT(e);
- return is<RedactionEvent>(*e);
-}
-
-void Room::Private::processRedaction(const RedactionEvent* redaction)
+/** Make a redacted event
+ *
+ * This applies the redaction procedure as defined by the CS API specification
+ * to the event's JSON and returns the resulting new event. It is
+ * the responsibility of the caller to dispose of the original event after that.
+ */
+RoomEventPtr makeRedacted(const RoomEvent& target,
+ const RedactionEvent& redaction)
{
- const auto pIdx = eventsIndex.find(redaction->redactedEvent());
- if (pIdx == eventsIndex.end())
- {
- qCDebug(MAIN) << "Redaction" << redaction->id()
- << "ignored: target event not found";
- return; // If the target events comes later, it comes already redacted.
- }
- Q_ASSERT(q->isValidIndex(*pIdx));
-
- auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
-
- // Apply the redaction procedure from chapter 6.5 of The Spec
- auto originalJson = ti->originalJsonObject();
- if (originalJson.value(UnsignedKeyL).toObject()
- .value(RedactedCauseKeyL).toObject()
- .value(EventIdKeyL) == redaction->id())
- {
- qCDebug(MAIN) << "Redaction" << redaction->id()
- << "of event" << ti.event()->id() << "already done, skipping";
- return;
- }
+ auto originalJson = target.originalJsonObject();
static const QStringList keepKeys =
{ EventIdKey, TypeKey, QStringLiteral("room_id"),
QStringLiteral("sender"), QStringLiteral("state_key"),
@@ -1518,7 +1483,7 @@ void Room::Private::processRedaction(const RedactionEvent* redaction)
}
auto keepContentKeys =
find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(),
- [&ti](const auto& t) { return ti->type() == t.first; } );
+ [&target](const auto& t) { return target.type() == t.first; } );
if (keepContentKeys == keepContentKeysMap.end())
{
originalJson.remove(ContentKeyL);
@@ -1535,15 +1500,37 @@ void Room::Private::processRedaction(const RedactionEvent* redaction)
originalJson.insert(ContentKey, content);
}
auto unsignedData = originalJson.take(UnsignedKeyL).toObject();
- unsignedData[RedactedCauseKeyL] = redaction->originalJsonObject();
+ unsignedData[RedactedCauseKeyL] = redaction.originalJsonObject();
originalJson.insert(QStringLiteral("unsigned"), unsignedData);
+ return loadEvent<RoomEvent>(originalJson);
+}
+
+bool Room::Private::processRedaction(const RedactionEvent& redaction)
+{
+ // 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;
+
+ Q_ASSERT(q->isValidIndex(*pIdx));
+
+ auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())];
+ if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id())
+ {
+ qCDebug(MAIN) << "Redaction" << redaction.id()
+ << "of event" << ti->id() << "already done, skipping";
+ return true;
+ }
+
// Make a new event from the redacted JSON, exchange events,
// notify everyone and delete the old event
- auto oldEvent = ti.replaceEvent(loadEvent<RoomEvent>(originalJson));
+ auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
q->onRedaction(*oldEvent, *ti.event());
- qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction->id();
+ qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction.id();
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
+ return true;
}
Connection* Room::connection() const
@@ -1557,18 +1544,49 @@ User* Room::localUser() const
return connection()->user();
}
-void Room::Private::addNewMessageEvents(RoomEvents&& events)
+inline bool isRedaction(const RoomEventPtr& ep)
{
- auto timelineSize = timeline.size();
+ Q_ASSERT(ep);
+ return is<RedactionEvent>(*ep);
+}
+void Room::Private::addNewMessageEvents(RoomEvents&& events)
+{
dropDuplicateEvents(events);
- if (events.empty())
+
+ // Pre-process redactions so that events that get redacted in the same
+ // batch landed in the timeline already redacted.
+ auto newEnd = std::find_if(events.begin(), events.end(), isRedaction);
+ // Either process the redaction, or shift the non-redaction event
+ // overwriting redactions in a remove_if fashion.
+ for(auto&& eptr: RoomEventsRange(newEnd, 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(), newEnd,
+ [id=r->redactedEvent()] (const RoomEventPtr& ep) {
+ return ep->id() == id;
+ });
+ if (targetIt != newEnd)
+ *targetIt = makeRedacted(**targetIt, *r);
+ else
+ qCDebug(MAIN) << "Redaction" << r->id()
+ << "ignored: target event not found";
+ // If the target events comes later, it comes already redacted.
+ }
+ else
+ *newEnd++ = std::move(eptr);
+
+ if (events.begin() == newEnd)
return;
+ auto timelineSize = timeline.size();
auto totalInserted = 0;
- for (auto it = events.begin(); it != events.end();)
+ for (auto it = events.begin(); it != newEnd;)
{
- auto nextPendingPair = findFirstOf(it, events.end(),
+ auto nextPendingPair = findFirstOf(it, newEnd,
unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent);
auto nextPending = nextPendingPair.first;
@@ -1583,7 +1601,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events)
}
emit q->addedMessages();
}
- if (nextPending == events.end())
+ if (nextPending == newEnd)
break;
it = nextPending + 1;
@@ -1633,11 +1651,14 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
const auto timelineSize = timeline.size();
dropDuplicateEvents(events);
- if (events.empty())
+ RoomEventsRange normalEvents {
+ events.begin(), remove_if(events.begin(), events.end(), isRedaction)
+ };
+ if (normalEvents.empty())
return;
- emit q->aboutToAddHistoricalMessages(events);
- const auto insertedSize = moveEventsToTimeline(events, Older);
+ emit q->aboutToAddHistoricalMessages(normalEvents);
+ const auto insertedSize = moveEventsToTimeline(normalEvents, Older);
const auto from = timeline.crend() - insertedSize;
qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize