Member events: cache all over the session

This commit is contained in:
ganfra 2019-12-31 08:07:32 +01:00
parent 8156b754c1
commit 787908287c
7 changed files with 55 additions and 118 deletions

View file

@ -47,7 +47,7 @@ internal fun ChunkEntity.deleteOnCascade() {
internal fun ChunkEntity.merge(roomId: String,
chunkToMerge: ChunkEntity,
direction: PaginationDirection) {
direction: PaginationDirection): List<TimelineEventEntity> {
assertIsManaged()
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
val isCurrentChunkUnlinked = this.isUnlinked()
@ -66,23 +66,11 @@ internal fun ChunkEntity.merge(roomId: String,
this.isLastBackward = chunkToMerge.isLastBackward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
}
val timelineEvents = eventsToMerge
return eventsToMerge
.mapNotNull {
val event = it.root?.asDomain() ?: return@mapNotNull null
add(roomId, event, direction, isUnlinked = isUnlinked)
}
updateSenderDataFor(timelineEvents)
}
internal fun ChunkEntity.updateSenderDataFor(events: List<TimelineEventEntity>) {
val cache = RoomMembersCache()
events.forEach {
val result = cache.get(it)
it.isUniqueDisplayName = result.isUniqueDisplayName
it.senderAvatar = result.senderAvatar
it.senderName = result.senderName
it.senderMembershipEventId = result.senderMembershipEventId
}
}
internal fun ChunkEntity.add(roomId: String,

View file

@ -16,72 +16,9 @@
package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.query.next
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
internal fun TimelineEventEntity.updateSenderData() {
assertIsManaged()
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
val stateIndex = root?.stateIndex ?: return
val senderId = root?.sender ?: return
val chunkEntity = chunk?.firstOrNull() ?: return
val isUnlinked = chunkEntity.isUnlinked()
var senderMembershipEvent: EventEntity?
var senderRoomMemberContent: String?
var senderRoomMemberPrevContent: String?
if (stateIndex <= 0) {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.prevContent
senderRoomMemberPrevContent = senderMembershipEvent?.content
} else {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.content
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
if (senderMembershipEvent == null) {
senderMembershipEvent = roomEntity.untimelinedStateEvents
.where()
.equalTo(EventEntityFields.STATE_KEY, senderId)
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.prev(since = stateIndex)
senderRoomMemberContent = senderMembershipEvent?.content
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
this.senderAvatar = it.avatarUrl
this.senderName = it.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
}
// We try to fallback on prev content if we got a room member state events with null fields
if (root?.type == EventType.STATE_ROOM_MEMBER) {
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMemberContent>()?.also {
if (this.senderAvatar == null && it.avatarUrl != null) {
this.senderAvatar = it.avatarUrl
}
if (this.senderName == null && it.displayName != null) {
this.senderName = it.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
}
}
}
this.senderMembershipEventId = senderMembershipEvent?.eventId
}
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
@ -91,10 +28,3 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
currentIdNum.toLong() + 1
}
}
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
return where()
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
}

View file

@ -25,15 +25,17 @@ import im.vector.matrix.android.internal.database.query.next
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import io.realm.RealmList
import io.realm.RealmQuery
import timber.log.Timber
import javax.inject.Inject
/**
* This is an internal cache to avoid querying all the time the room member events
*/
internal class RoomMembersCache {
@SessionScope
internal class TimelineEventSenderVisitor @Inject constructor() {
internal data class Key(
val roomId: String,
@ -50,24 +52,34 @@ internal class RoomMembersCache {
private val values = HashMap<Key, Value>()
fun get(timelineEventEntity: TimelineEventEntity): Value {
fun clear() {
values.clear()
}
fun clear(roomId: String, senderId: String) {
val keysToRemove = values.keys.filter { it.senderId == senderId && it.roomId == roomId }
keysToRemove.forEach {
values.remove(it)
}
}
fun visit(timelineEventEntities: List<TimelineEventEntity>) = timelineEventEntities.forEach { visit(it) }
fun visit(timelineEventEntity: TimelineEventEntity) {
val key = Key(
roomId = timelineEventEntity.roomId,
stateIndex = timelineEventEntity.root?.stateIndex ?: 0,
senderId = timelineEventEntity.root?.sender ?: ""
)
val result: Value
val start = System.currentTimeMillis()
result = values.getOrPut(key) {
doQueryAndBuildValue(timelineEventEntity)
val result = values.getOrPut(key) {
timelineEventEntity.computeValue()
}
timelineEventEntity.apply {
this.isUniqueDisplayName = result.isUniqueDisplayName
this.senderAvatar = result.senderAvatar
this.senderName = result.senderName
this.senderMembershipEventId = result.senderMembershipEventId
}
val end = System.currentTimeMillis()
Timber.v("Get value took: ${end - start} millis")
return result
}
private fun doQueryAndBuildValue(timelineEventEntity: TimelineEventEntity): Value {
return timelineEventEntity.computeValue()
}
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
@ -80,7 +92,6 @@ internal class RoomMembersCache {
private fun TimelineEventEntity.computeValue(): Value {
assertIsManaged()
val result = Value()
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result
val stateIndex = root?.stateIndex ?: return result
val senderId = root?.sender ?: return result

View file

@ -18,8 +18,8 @@ package im.vector.matrix.android.internal.session.room.membership
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor
import im.vector.matrix.android.internal.database.helper.addStateEvent
import im.vector.matrix.android.internal.database.helper.updateSenderData
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest
@ -44,7 +44,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
private val monarchy: Monarchy,
private val syncTokenStore: SyncTokenStore,
private val roomSummaryUpdater: RoomSummaryUpdater,
private val roomMemberEventHandler: RoomMemberEventHandler
private val roomMemberEventHandler: RoomMemberEventHandler,
private val timelineEventSenderVisitor: TimelineEventSenderVisitor
) : LoadRoomMembersTask {
override suspend fun execute(params: LoadRoomMembersTask.Params) {
@ -68,8 +69,9 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
roomEntity.addStateEvent(roomMemberEvent)
roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
}
timelineEventSenderVisitor.clear()
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
it.updateSenderData()
timelineEventSenderVisitor.visit(it)
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId, updateMembers = true)

View file

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.internal.database.helper.updateSenderData
import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.mapper.EventMapper
import im.vector.matrix.android.internal.database.model.EventEntity
@ -41,7 +41,8 @@ internal interface PruneEventTask : Task<PruneEventTask.Params, Unit> {
)
}
internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask {
internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy,
private val timelineEventSenderVisitor: TimelineEventSenderVisitor) : PruneEventTask {
override suspend fun execute(params: PruneEventTask.Params) {
monarchy.awaitTransaction { realm ->
@ -65,12 +66,14 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
?: return
val allowedKeys = computeAllowedKeys(eventToPrune.type)
val typeToPrune = eventToPrune.type
val stateKey = eventToPrune.stateKey
val allowedKeys = computeAllowedKeys(typeToPrune)
if (allowedKeys.isNotEmpty()) {
val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) }
eventToPrune.content = ContentMapper.map(prunedContent)
} else {
when (eventToPrune.type) {
when (typeToPrune) {
EventType.ENCRYPTED,
EventType.MESSAGE -> {
Timber.d("REDACTION for message ${eventToPrune.eventId}")
@ -94,11 +97,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
// }
}
}
if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) {
if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) {
timelineEventSenderVisitor.clear(roomId = eventToPrune.roomId, senderId = stateKey)
val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId)
for (timelineEvent in timelineEventsToUpdate) {
timelineEvent.updateSenderData()
}
timelineEventSenderVisitor.visit(timelineEventsToUpdate)
}
}

View file

@ -36,7 +36,8 @@ import javax.inject.Inject
/**
* Insert Chunk in DB, and eventually merge with existing chunk event
*/
internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy) {
internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy,
private val timelineEventSenderVisitor: TimelineEventSenderVisitor) {
/**
* <pre>
@ -170,7 +171,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
for (stateEvent in receivedChunk.stateEvents) {
roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
}
currentChunk.updateSenderDataFor(timelineEvents)
timelineEventSenderVisitor.visit(timelineEvents)
}
}
return if (receivedChunk.events.isEmpty()) {
@ -191,11 +192,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
// We always merge the bottom chunk into top chunk, so we are always merging backwards
Timber.v("Merge ${currentChunk.prevToken} | ${currentChunk.nextToken} with ${otherChunk.prevToken} | ${otherChunk.nextToken}")
return if (direction == PaginationDirection.BACKWARDS && !otherChunk.isLastForward) {
currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
val events = currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
timelineEventSenderVisitor.visit(events)
roomEntity.deleteOnCascade(otherChunk)
currentChunk
} else {
otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
val events = otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
timelineEventSenderVisitor.visit(events)
roomEntity.deleteOnCascade(currentChunk)
otherChunk
}

View file

@ -48,7 +48,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private val roomTagHandler: RoomTagHandler,
private val roomFullyReadHandler: RoomFullyReadHandler,
private val cryptoService: DefaultCryptoService,
private val roomMemberEventHandler: RoomMemberEventHandler) {
private val roomMemberEventHandler: RoomMemberEventHandler,
private val timelineEventSenderVisitor: TimelineEventSenderVisitor) {
sealed class HandlingStrategy {
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
@ -209,7 +210,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
}
chunkEntity.updateSenderDataFor(timelineEvents)
timelineEventSenderVisitor.visit(timelineEvents)
return chunkEntity
}