mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 21:48:50 +03:00
Member events: cache all over the session
This commit is contained in:
parent
8156b754c1
commit
787908287c
7 changed files with 55 additions and 118 deletions
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue