Rework edition of event management - step 2
This commit is contained in:
parent
1bfd78753a
commit
097668b762
@ -25,7 +25,7 @@ Test:
|
|||||||
-
|
-
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
- Rework edition of event management
|
||||||
|
|
||||||
Changes in Element 1.1.0 (2021-02-19)
|
Changes in Element 1.1.0 (2021-02-19)
|
||||||
===================================================
|
===================================================
|
||||||
|
@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.database
|
|||||||
|
|
||||||
import io.realm.DynamicRealm
|
import io.realm.DynamicRealm
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||||
@ -30,7 +32,7 @@ import javax.inject.Inject
|
|||||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SESSION_STORE_SCHEMA_VERSION = 7L
|
const val SESSION_STORE_SCHEMA_VERSION = 8L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
@ -43,6 +45,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||||||
if (oldVersion <= 4) migrateTo5(realm)
|
if (oldVersion <= 4) migrateTo5(realm)
|
||||||
if (oldVersion <= 5) migrateTo6(realm)
|
if (oldVersion <= 5) migrateTo6(realm)
|
||||||
if (oldVersion <= 6) migrateTo7(realm)
|
if (oldVersion <= 6) migrateTo7(realm)
|
||||||
|
if (oldVersion <= 7) migrateTo8(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
@ -122,4 +125,29 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||||||
}
|
}
|
||||||
?.removeField("areAllMembersLoaded")
|
?.removeField("areAllMembersLoaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun migrateTo8(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 7 -> 8")
|
||||||
|
|
||||||
|
val editionOfEventSchema = realm.schema.create("EditionOfEvent")
|
||||||
|
.apply {
|
||||||
|
// setEmbedded does not return `this`...
|
||||||
|
isEmbedded = true
|
||||||
|
}
|
||||||
|
.addField(EditionOfEventFields.CONTENT, String::class.java)
|
||||||
|
.addField(EditionOfEventFields.EVENT_ID, String::class.java)
|
||||||
|
.setRequired(EditionOfEventFields.EVENT_ID, true)
|
||||||
|
.addField(EditionOfEventFields.SENDER_ID, String::class.java)
|
||||||
|
.setRequired(EditionOfEventFields.SENDER_ID, true)
|
||||||
|
.addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
|
||||||
|
.addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
|
||||||
|
|
||||||
|
|
||||||
|
realm.schema.get("EditAggregatedSummaryEntity")
|
||||||
|
?.removeField("aggregatedContent")
|
||||||
|
?.removeField("sourceEvents")
|
||||||
|
?.removeField("lastEditTs")
|
||||||
|
?.removeField("sourceLocalEchoEvents")
|
||||||
|
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
|||||||
this.eventId = eventId
|
this.eventId = eventId
|
||||||
this.roomId = roomId
|
this.roomId = roomId
|
||||||
this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||||
|
?.also { it.cleanUp(eventEntity.sender) }
|
||||||
this.readReceipts = readReceiptsSummaryEntity
|
this.readReceipts = readReceiptsSummaryEntity
|
||||||
this.displayIndex = displayIndex
|
this.displayIndex = displayIndex
|
||||||
val roomMemberContent = roomMemberContentsByUser[senderId]
|
val roomMemberContent = roomMemberContentsByUser[senderId]
|
||||||
|
@ -20,11 +20,7 @@ import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary
|
|||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary
|
||||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
|
||||||
import io.realm.RealmList
|
|
||||||
|
|
||||||
internal object EventAnnotationsSummaryMapper {
|
internal object EventAnnotationsSummaryMapper {
|
||||||
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
|
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
|
||||||
@ -41,11 +37,14 @@ internal object EventAnnotationsSummaryMapper {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
editSummary = annotationsSummary.editSummary?.let {
|
editSummary = annotationsSummary.editSummary?.let {
|
||||||
|
val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp }
|
||||||
EditAggregatedSummary(
|
EditAggregatedSummary(
|
||||||
ContentMapper.map(it.aggregatedContent),
|
ContentMapper.map(latestEdition?.content),
|
||||||
it.sourceEvents.toList(),
|
it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
|
||||||
it.sourceLocalEchoEvents.toList(),
|
.map { editionOfEvent -> editionOfEvent.eventId },
|
||||||
it.lastEditTs
|
it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
|
||||||
|
.map { editionOfEvent -> editionOfEvent.eventId },
|
||||||
|
latestEdition?.timestamp ?: 0L
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
|
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
|
||||||
@ -62,46 +61,6 @@ internal object EventAnnotationsSummaryMapper {
|
|||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity {
|
|
||||||
val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity()
|
|
||||||
eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId
|
|
||||||
eventAnnotationsSummaryEntity.roomId = roomId
|
|
||||||
eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let {
|
|
||||||
EditAggregatedSummaryEntity(
|
|
||||||
ContentMapper.map(it.aggregatedContent),
|
|
||||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
|
||||||
RealmList<String>().apply { addAll(it.localEchos) },
|
|
||||||
it.lastEditTs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary.let {
|
|
||||||
RealmList<ReactionAggregatedSummaryEntity>().apply {
|
|
||||||
addAll(it.map {
|
|
||||||
ReactionAggregatedSummaryEntity(
|
|
||||||
it.key,
|
|
||||||
it.count,
|
|
||||||
it.addedByMe,
|
|
||||||
it.firstTimestamp,
|
|
||||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
|
||||||
RealmList<String>().apply { addAll(it.localEchoEvents) }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let {
|
|
||||||
ReferencesAggregatedSummaryEntity(
|
|
||||||
it.eventId,
|
|
||||||
ContentMapper.map(it.content),
|
|
||||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
|
||||||
RealmList<String>().apply { addAll(it.localEchos) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
|
||||||
PollResponseAggregatedSummaryEntityMapper.map(it)
|
|
||||||
}
|
|
||||||
return eventAnnotationsSummaryEntity
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
|
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
|
||||||
|
@ -17,17 +17,24 @@ package org.matrix.android.sdk.internal.database.model
|
|||||||
|
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.RealmClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep the latest state of edition of a message
|
* Keep all the editions of a message
|
||||||
*/
|
*/
|
||||||
internal open class EditAggregatedSummaryEntity(
|
internal open class EditAggregatedSummaryEntity(
|
||||||
var aggregatedContent: String? = null,
|
// The list of the editions used to build the summary (might be out of sync if chunked received from message chunk)
|
||||||
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
var editions: RealmList<EditionOfEvent> = RealmList()
|
||||||
var sourceEvents: RealmList<String> = RealmList(),
|
|
||||||
var sourceLocalEchoEvents: RealmList<String> = RealmList(),
|
|
||||||
var lastEditTs: Long = 0
|
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RealmClass(embedded = true)
|
||||||
|
internal open class EditionOfEvent(
|
||||||
|
var senderId: String = "",
|
||||||
|
var eventId: String = "",
|
||||||
|
var content: String? = null,
|
||||||
|
var timestamp: Long = 0,
|
||||||
|
var isLocalEcho: Boolean = false
|
||||||
|
) : RealmObject()
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
|
|||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
internal open class EventAnnotationsSummaryEntity(
|
internal open class EventAnnotationsSummaryEntity(
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@ -29,6 +30,21 @@ internal open class EventAnnotationsSummaryEntity(
|
|||||||
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
|
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup undesired editions, done by users different from the originalEventSender
|
||||||
|
*/
|
||||||
|
fun cleanUp(originalEventSenderId: String?) {
|
||||||
|
originalEventSenderId ?: return
|
||||||
|
|
||||||
|
editSummary?.editions?.filter {
|
||||||
|
it.senderId != originalEventSenderId
|
||||||
|
}
|
||||||
|
?.forEach {
|
||||||
|
Timber.w("Deleting an edition from ${it.senderId} of event sent by $originalEventSenderId")
|
||||||
|
it.deleteFromRealm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ import io.realm.annotations.RealmModule
|
|||||||
EventAnnotationsSummaryEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
EditAggregatedSummaryEntity::class,
|
EditAggregatedSummaryEntity::class,
|
||||||
|
EditionOfEvent::class,
|
||||||
PollResponseAggregatedSummaryEntity::class,
|
PollResponseAggregatedSummaryEntity::class,
|
||||||
ReferencesAggregatedSummaryEntity::class,
|
ReferencesAggregatedSummaryEntity::class,
|
||||||
PushRulesEntity::class,
|
PushRulesEntity::class,
|
||||||
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.verification.toState
|
|||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
import org.matrix.android.sdk.internal.database.mapper.EventMapper
|
import org.matrix.android.sdk.internal.database.mapper.EventMapper
|
||||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EditionOfEvent
|
||||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
@ -219,49 +220,48 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
|
|||||||
// create the edit summary
|
// create the edit summary
|
||||||
eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
|
eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
|
||||||
.also { editSummary ->
|
.also { editSummary ->
|
||||||
editSummary.aggregatedContent = ContentMapper.map(newContent)
|
editSummary.editions.add(
|
||||||
if (isLocalEcho) {
|
EditionOfEvent(
|
||||||
editSummary.lastEditTs = 0
|
senderId = event.senderId ?: "",
|
||||||
editSummary.sourceLocalEchoEvents.add(eventId)
|
eventId = event.eventId,
|
||||||
} else {
|
content = ContentMapper.map(newContent),
|
||||||
editSummary.lastEditTs = event.originServerTs ?: 0
|
timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0,
|
||||||
editSummary.sourceEvents.add(eventId)
|
isLocalEcho = isLocalEcho
|
||||||
}
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (existingSummary.sourceEvents.contains(eventId)) {
|
if (existingSummary.editions.any { it.eventId == eventId }) {
|
||||||
// ignore this event, we already know it (??)
|
// ignore this event, we already know it (??)
|
||||||
Timber.v("###REPLACE ignoring event for summary, it's known $eventId")
|
Timber.v("###REPLACE ignoring event for summary, it's known $eventId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
// is it a remote echo?
|
// is it a remote echo?
|
||||||
if (!isLocalEcho && existingSummary.sourceLocalEchoEvents.contains(txId)) {
|
if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) {
|
||||||
// ok it has already been managed
|
// ok it has already been managed
|
||||||
Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
|
Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
|
||||||
existingSummary.sourceLocalEchoEvents.remove(txId)
|
existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
|
||||||
existingSummary.sourceEvents.add(event.eventId)
|
it.eventId = event.eventId
|
||||||
} else if (
|
it.timestamp = event.originServerTs ?: System.currentTimeMillis()
|
||||||
isLocalEcho // do not rely on ts for local echo, take it
|
it.isLocalEcho = false
|
||||||
|| event.originServerTs ?: 0 >= existingSummary.lastEditTs
|
|
||||||
) {
|
|
||||||
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
|
|
||||||
if (!isLocalEcho) {
|
|
||||||
// Do not take local echo originServerTs here, could mess up ordering (keep old ts)
|
|
||||||
existingSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
existingSummary.aggregatedContent = ContentMapper.map(newContent)
|
|
||||||
if (isLocalEcho) {
|
|
||||||
existingSummary.sourceLocalEchoEvents.add(eventId)
|
|
||||||
} else {
|
|
||||||
existingSummary.sourceEvents.add(eventId)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ignore this event for the summary (back paginate)
|
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
|
||||||
if (!isLocalEcho) {
|
existingSummary.editions.add(
|
||||||
existingSummary.sourceEvents.add(eventId)
|
EditionOfEvent(
|
||||||
}
|
senderId = event.senderId ?: "",
|
||||||
Timber.v("###REPLACE ignoring event for summary, it's to old $eventId")
|
eventId = event.eventId,
|
||||||
|
content = ContentMapper.map(newContent),
|
||||||
|
timestamp = if (isLocalEcho) {
|
||||||
|
System.currentTimeMillis()
|
||||||
|
} else {
|
||||||
|
// Do not take local echo originServerTs here, could mess up ordering (keep old ts)
|
||||||
|
event.originServerTs ?: System.currentTimeMillis()
|
||||||
|
},
|
||||||
|
isLocalEcho = isLocalEcho
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -448,29 +448,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
|
|||||||
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
|
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val sourceEvents = eventSummary.editSummary?.sourceEvents
|
val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull {it.eventId == redacted.eventId }
|
||||||
val sourceToDiscard = sourceEvents?.indexOf(redacted.eventId)
|
|
||||||
if (sourceToDiscard == null) {
|
if (sourceToDiscard == null) {
|
||||||
Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard")
|
Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Need to remove this event from the redaction list and compute new aggregation state
|
// Need to remove this event from the edition list
|
||||||
sourceEvents.removeAt(sourceToDiscard)
|
sourceToDiscard.deleteFromRealm()
|
||||||
val previousEdit = sourceEvents.mapNotNull { EventEntity.where(realm, it).findFirst() }.sortedBy { it.originServerTs }.lastOrNull()
|
|
||||||
if (previousEdit == null) {
|
|
||||||
// revert to original
|
|
||||||
eventSummary.editSummary?.deleteFromRealm()
|
|
||||||
} else {
|
|
||||||
// I have the last event
|
|
||||||
ContentMapper.map(previousEdit.content)?.toModel<MessageContent>()?.newContent?.let { newContent ->
|
|
||||||
eventSummary.editSummary?.lastEditTs = previousEdit.originServerTs
|
|
||||||
?: System.currentTimeMillis()
|
|
||||||
eventSummary.editSummary?.aggregatedContent = ContentMapper.map(newContent)
|
|
||||||
} ?: run {
|
|
||||||
Timber.e("Failed to udate edited summary")
|
|
||||||
// TODO how to reccover that
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
|
private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user