Try to insert users directly to see if perfs are better [WIP]

This commit is contained in:
ganfra 2019-07-11 18:55:13 +02:00
parent b77310fe92
commit 10e4d0190f
17 changed files with 343 additions and 202 deletions

View File

@ -33,7 +33,7 @@ data class TimelineEvent(
val localId: Long,
val displayIndex: Int,
val senderName: String?,
val isUniqueDisplayName: Boolean,
val isUniqueDisplayName: Boolean = false,
val senderAvatar: String?,
val sendState: SendState,
val annotations: EventAnnotationsSummary? = null

View File

@ -103,7 +103,6 @@ internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
}
}

@VisibleForTesting
internal fun ChunkEntity.add(roomId: String,
event: Event,
direction: PaginationDirection,

View File

@ -16,6 +16,7 @@

package im.vector.matrix.android.internal.database.helper

import com.squareup.moshi.JsonReader
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.mapper.toEntity
@ -24,7 +25,10 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.network.parsing.GetRoomMembersResponseHandler
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import okhttp3.ResponseBody
import okio.Okio

internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
@ -37,25 +41,42 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
}
}

internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) {
internal fun RoomEntity.addStateEvent(stateEvent: Event,
stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) {
assertIsManaged()

stateEvents.forEach { event ->
if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) {
return@forEach
}
val eventEntity = event.toEntity(roomId).apply {
if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) {
return
} else {
val entity = stateEvent.toEntity(roomId).apply {
this.stateIndex = stateIndex
this.isUnlinked = isUnlinked
this.sendState = SendState.SYNCED
}
untimelinedStateEvents.add(0, eventEntity)
untimelinedStateEvents.add(entity)
}
}

internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) {
stateEvents.forEach { event ->
addStateEvent(event, stateIndex, filterDuplicates, isUnlinked)
}
}

internal fun RoomEntity.addStateEvents(response: ResponseBody,
stateIndex: Int = Int.MIN_VALUE,
isUnlinked: Boolean = false) {
val manualParser = GetRoomMembersResponseHandler()
val bufferedSource = Okio.buffer(Okio.source(response.byteStream()))
val inputReader = JsonReader.of(bufferedSource)
manualParser.handle(inputReader, this, stateIndex, isUnlinked)
}


internal fun RoomEntity.addSendingEvent(event: Event) {
assertIsManaged()
val senderId = event.senderId ?: return
@ -64,7 +85,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
}
val roomMembers = RoomMembers(realm, roomId)
val myUser = roomMembers.get(senderId)
val localId = TimelineEventEntity.nextId(realm)
val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also {
it.root = eventEntity
it.eventId = event.eventId ?: ""

View File

@ -0,0 +1,59 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.internal.network.parsing

import com.squareup.moshi.JsonReader
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity

internal class GetRoomMembersResponseHandler {

companion object {
private val NAMES = JsonReader.Options.of("event_id", "content", "prev_content", "origin_server_ts", "sender", "state_key")
}

internal fun handle(reader: JsonReader, roomEntity: RoomEntity, stateIndex: Int = Int.MIN_VALUE, isUnlinked: Boolean = false) {
reader.readObject {
reader.nextName()
val eventEntity = EventEntity().apply {
this.roomId = roomEntity.roomId
this.stateIndex = stateIndex
this.isUnlinked = isUnlinked
this.sendState = SendState.SYNCED
this.type = EventType.STATE_ROOM_MEMBER
}
reader.readArray {
reader.readObject {
when
(reader.selectName(NAMES)) {
0 -> eventEntity.eventId = reader.nextString()
1 -> eventEntity.content = reader.readJsonValue()?.toString()
2 -> eventEntity.prevContent = reader.readJsonValue()?.toString()
3 -> eventEntity.originServerTs = reader.nextLong()
4 -> eventEntity.sender = reader.nextString()
5 -> eventEntity.stateKey = reader.nextString()
else -> reader.skipNameAndValue()
}
}
roomEntity.untimelinedStateEvents.add(eventEntity)
}
}
}

}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.internal.network.parsing

import com.squareup.moshi.JsonReader

fun JsonReader.skipNameAndValue() {
skipName()
skipValue()
}

inline fun JsonReader.readObject(body: () -> Unit) {
beginObject()
while (hasNext()) {
body()
}
endObject()
}

inline fun JsonReader.readArray(body: () -> Unit) {
beginArray()
while (hasNext()) {
body()
}
endArray()
}

View File

@ -38,7 +38,6 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.user.UserEntityUpdater
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
@ -129,10 +128,6 @@ internal abstract class SessionModule {
@IntoSet
abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver

@Binds
@IntoSet
abstract fun bindUserEntityUpdater(groupSummaryUpdater: UserEntityUpdater): LiveEntityObserver

@Binds
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService


View File

@ -20,14 +20,13 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
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.Membership
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import javax.inject.Inject

@ -42,32 +41,25 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
fun resolve(roomId: String): String? {
var res: String? = null
monarchy.doWithRealm { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()?.asDomain()
res = roomName?.content.toModel<RoomAvatarContent>()?.avatarUrl
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
val roomMembers = RoomMembers(realm, roomId)
val members = roomMembers.getLoaded()
if (roomEntity?.membership == Membership.INVITE) {
if (members.size == 1) {
res = members.entries.first().value.avatarUrl
} else if (members.size > 1) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull()
res = firstOtherMember?.avatarUrl
}
} else {
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (members.size == 1) {
res = members.entries.first().value.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull()
res = firstOtherMember?.avatarUrl
}
val members = roomMembers.queryRoomMembersEvent().findAll()
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (members.size == 1) {
res = members.firstOrNull()?.toRoomMember()?.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, credentials.userId).findFirst()
res = firstOtherMember?.toRoomMember()?.avatarUrl
}

}
return res
}

private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
}
}

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.latestEvent
@ -86,12 +87,20 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C

val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }

val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
.findAll()
.asSequence()
.map { it.stateKey }

roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.latestEvent = latestEvent
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)

}
}

View File

@ -18,19 +18,25 @@ package im.vector.matrix.android.internal.session.room.membership

import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.helper.addStateEvent
import im.vector.matrix.android.internal.database.helper.addStateEvents
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.model.UserEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.Realm
import io.realm.kotlin.createObject
import okhttp3.ResponseBody
import javax.inject.Inject

internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolean> {
@ -60,23 +66,27 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
}
}

private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<RoomMembersResponse> {
private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<Unit> {
return monarchy
.tryTransactionSync { realm ->
// We ignore all the already known members
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)

val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
roomEntity.addStateEvents(eventsToInsert)
val userEntities = ArrayList<UserEntity>(response.roomMemberEvents.size)
for (roomMemberEvent in response.roomMemberEvents) {
roomEntity.addStateEvent(roomMemberEvent)
UserEntityFactory.create(roomMemberEvent)?.also {
userEntities.add(it)
}
}
realm.insertOrUpdate(userEntities)
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
it.updateSenderData()
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId)
}
.map { response }
}

private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
@ -85,4 +95,4 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
}
}

}
}

View File

@ -25,13 +25,16 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomNameContent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import io.realm.RealmResults
import javax.inject.Inject

/**
@ -39,7 +42,6 @@ import javax.inject.Inject
*/
internal class RoomDisplayNameResolver @Inject constructor(private val context: Context,
private val monarchy: Monarchy,
private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver,
private val credentials: Credentials
) {

@ -78,48 +80,59 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}

val roomMembers = RoomMembers(realm, roomId)
val loadedMembers = roomMembers.getLoaded()
val otherRoomMembers = loadedMembers.filterKeys { it != credentials.userId }
val loadedMembers = roomMembers.queryRoomMembersEvent().findAll()
val otherMembersSubset = loadedMembers.where()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
.limit(3)
.findAll()

if (roomEntity?.membership == Membership.INVITE) {
val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst()
val inviterId = inviteMeEvent?.sender
name = if (inviterId != null && otherRoomMembers.containsKey(inviterId)) {
roomMemberDisplayNameResolver.resolve(inviterId, otherRoomMembers)
name = if (inviterId != null) {
val inviterMemberEvent = loadedMembers.where().equalTo(EventEntityFields.STATE_KEY, inviterId).findFirst()
inviterMemberEvent?.toRoomMember()?.displayName
} else {
context.getString(R.string.room_displayname_room_invite)
}
} else {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) {
val memberIds: List<String> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes
} else {
otherRoomMembers.keys.toList()
otherMembersSubset.mapNotNull { it.stateKey }
}

val nbOfOtherMembers = memberIds.size

when (nbOfOtherMembers) {
0 -> name = context.getString(R.string.room_displayname_empty_room)
1 -> name = roomMemberDisplayNameResolver.resolve(memberIds[0], otherRoomMembers)
2 -> {
val member1 = memberIds[0]
val member2 = memberIds[1]
name = context.getString(R.string.room_displayname_two_members,
roomMemberDisplayNameResolver.resolve(member1, otherRoomMembers),
roomMemberDisplayNameResolver.resolve(member2, otherRoomMembers)
)
}
else -> {
val member = memberIds[0]
name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1,
roomMemberDisplayNameResolver.resolve(member, otherRoomMembers),
roomMembers.getNumberOfJoinedMembers() - 1)
}
name = when (memberIds.size) {
0 -> context.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMember(otherMembersSubset[0], roomMembers)
2 -> context.getString(R.string.room_displayname_two_members,
resolveRoomMember(otherMembersSubset[0], roomMembers),
resolveRoomMember(otherMembersSubset[1], roomMembers)
)
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1,
resolveRoomMember(otherMembersSubset[0], roomMembers),
roomMembers.getNumberOfJoinedMembers() - 1)
}
}
return@doWithRealm
}
return name ?: roomId
}

private fun resolveRoomMember(eventEntity: EventEntity?,
roomMembers: RoomMembers): String? {
if (eventEntity == null) return null
val roomMember = eventEntity.toRoomMember() ?: return null
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
return if (isUnique) {
roomMember.displayName
} else {
"${roomMember.displayName} ( ${eventEntity.stateKey} )"
}
}

private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.internal.session.room.membership

import im.vector.matrix.android.api.session.room.model.RoomMember
import javax.inject.Inject

internal class RoomMemberDisplayNameResolver @Inject constructor() {

fun resolve(userId: String, members: Map<String, RoomMember>): String? {
val currentMember = members[userId]
var displayName = currentMember?.displayName
// Get the user display name from the member list of the room
// Do not consider null display name

if (currentMember != null && !currentMember.displayName.isNullOrEmpty()) {
val hasNameCollision = members
.filterValues { it != currentMember && it.displayName == currentMember.displayName }
.isNotEmpty()
if (hasNameCollision) {
displayName = "${currentMember.displayName} ( $userId )"
}
}

// TODO handle invited users
/*else if (null != member && TextUtils.equals(member!!.membership, RoomMember.MEMBERSHIP_INVITE)) {
val user = (mDataHandler as MXDataHandler).getUser(userId)
if (null != user) {
displayName = user!!.displayname
}
}
*/
if (displayName == null) {
// By default, use the user ID
displayName = userId
}
return displayName
}

}

View File

@ -33,6 +33,7 @@ import io.realm.Sort
* This class is an helper around STATE_ROOM_MEMBER events.
* It allows to get the live membership of a user.
*/

internal class RoomMembers(private val realm: Realm,
private val roomId: String
) {
@ -72,27 +73,27 @@ internal class RoomMembers(private val realm: Realm,
.isNotNull(EventEntityFields.CONTENT)
}

fun queryJoinedRoomMembersEvent(): RealmQuery<EventEntity> {
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"join\"")
}

fun queryInvitedRoomMembersEvent(): RealmQuery<EventEntity> {
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"invite\"")
}

fun queryRoomMemberEvent(userId: String): RealmQuery<EventEntity> {
return queryRoomMembersEvent()
.equalTo(EventEntityFields.STATE_KEY, userId)
}

fun getLoaded(): Map<String, RoomMember> {
return queryRoomMembersEvent()
.findAll()
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.mapValues { it.value.content.toModel<RoomMember>()!! }
}

fun getNumberOfJoinedMembers(): Int {
return roomSummary?.joinedMembersCount
?: getLoaded().filterValues { it.membership == Membership.JOIN }.size
?: queryJoinedRoomMembersEvent().findAll().size
}

fun getNumberOfInvitedMembers(): Int {
return roomSummary?.invitedMembersCount
?: getLoaded().filterValues { it.membership == Membership.INVITE }.size
?: queryInvitedRoomMembersEvent().findAll().size
}

fun getNumberOfMembers(): Int {
@ -133,4 +134,4 @@ internal class RoomMembers(private val realm: Realm,
.toList()
}

}
}

View File

@ -20,16 +20,19 @@ import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvent
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
import im.vector.matrix.android.internal.database.helper.isUnlinked
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.create
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject
import timber.log.Timber
@ -153,8 +156,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
currentChunk.isLastBackward = true
} else {
Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked())

val userEntities = ArrayList<UserEntity>(receivedChunk.events.size + receivedChunk.stateEvents.size)
for (event in receivedChunk.events) {
currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked())
UserEntityFactory.create(event)?.also {
userEntities.add(it)
}
}
// Then we merge chunks if needed
if (currentChunk != prevChunk && prevChunk != null) {
currentChunk = handleMerge(roomEntity, direction, currentChunk, prevChunk)
@ -170,7 +178,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
}
}
roomEntity.addOrUpdate(currentChunk)
roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = currentChunk.isUnlinked())
for (stateEvent in receivedChunk.stateEvents) {
roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
UserEntityFactory.create(stateEvent)?.also {
userEntities.add(it)
}
}
realm.insertOrUpdate(userEntities)
}
}
.map {

View File

@ -22,14 +22,18 @@ 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.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvent
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where
@ -40,6 +44,7 @@ import im.vector.matrix.android.internal.session.notification.ProcessEventForPus
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.session.sync.model.*
import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import io.realm.Realm
@ -118,7 +123,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
Timber.v("Handle join sync for room $roomId")

val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)

if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm()
@ -133,43 +138,30 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch

// State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val userEntities = ArrayList<UserEntity>(roomSync.state.events.size)
val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset
roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex)

// Give info to crypto module
roomSync.state.events.forEach {
cryptoManager.onStateEvent(roomId, it)
roomSync.state.events.forEach { event ->
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
// Give info to crypto module
cryptoManager.onStateEvent(roomId, event)
UserEntityFactory.create(event)?.also {
userEntities.add(it)
}
}
realm.insertOrUpdate(userEntities)
}

if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val timelineStateOffset = if (isInitialSync || roomSync.timeline.limited.not()) 0 else stateIndexOffset
val chunkEntity = handleTimelineEvents(
realm,
roomId,
roomEntity,
roomSync.timeline.events,
roomSync.timeline.prevToken,
roomSync.timeline.limited,
timelineStateOffset
)
roomEntity.addOrUpdate(chunkEntity)

// Give info to crypto module
roomSync.timeline.events.forEach {
cryptoManager.onLiveEvent(roomId, it)
}

// Try to remove local echo
val transactionIds = roomSync.timeline.events.mapNotNull { it.unsignedData?.transactionId }
transactionIds.forEach {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
if (sendingEventEntity != null) {
Timber.v("Remove local echo for tx:$it")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
} else {
Timber.v("Can't find corresponding local echo for tx:$it")
}
}
}
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)

@ -189,10 +181,10 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events)
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
roomEntity.addOrUpdate(chunkEntity)
}
roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
@ -203,7 +195,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomId: String,
roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)

roomEntity.membership = Membership.LEAVE
roomEntity.chunks.deleteAllFromRealm()
@ -212,13 +204,13 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
}

private fun handleTimelineEvents(realm: Realm,
roomId: String,
roomEntity: RoomEntity,
eventList: List<Event>,
prevToken: String? = null,
isLimited: Boolean = true,
stateIndexOffset: Int = 0): ChunkEntity {

val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomEntity.roomId)
val chunkEntity = if (!isLimited && lastChunk != null) {
lastChunk
} else {
@ -226,13 +218,31 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
}
lastChunk?.isLastForward = false
chunkEntity.isLastForward = true
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS, stateIndexOffset)

//update eventAnnotationSummary here?

val userEntities = ArrayList<UserEntity>(eventList.size)
for (event in eventList) {
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
// Give info to crypto module
cryptoManager.onLiveEvent(roomEntity.roomId, event)
// Try to remove local echo
event.unsignedData?.transactionId?.also {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
if (sendingEventEntity != null) {
Timber.v("Remove local echo for tx:$it")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
} else {
Timber.v("Can't find corresponding local echo for tx:$it")
}
}
UserEntityFactory.create(event)?.also {
userEntities.add(it)
}
}
realm.insertOrUpdate(userEntities)
return chunkEntity
}


private fun handleEphemeral(realm: Realm,
roomId: String,
ephemeral: RoomSyncEphemeral) {

View File

@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
@ -38,21 +39,6 @@ internal interface UpdateUserTask : Task<UpdateUserTask.Params, Unit> {
internal class DefaultUpdateUserTask @Inject constructor(private val monarchy: Monarchy) : UpdateUserTask {

override suspend fun execute(params: UpdateUserTask.Params): Try<Unit> {
return monarchy.tryTransactionSync { realm ->
params.eventIds.forEach { eventId ->
val event = EventEntity.where(realm, eventId).findFirst()?.asDomain()
?: return@forEach
val roomId = event.roomId ?: return@forEach
val userId = event.stateKey ?: return@forEach
val roomMember = RoomMembers(realm, roomId).get(userId) ?: return@forEach
if (roomMember.membership != Membership.JOIN) return@forEach

val userEntity = UserEntity.where(realm, userId).findFirst()
?: realm.createObject(UserEntity::class.java, userId)
userEntity.displayName = roomMember.displayName ?: ""
userEntity.avatarUrl = roomMember.avatarUrl ?: ""
}
}
return Try.just(Unit)
}

}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.internal.session.user

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.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.model.UserEntity

internal object UserEntityFactory {

fun create(event: Event): UserEntity? {
if (event.type != EventType.STATE_ROOM_MEMBER) {
return null
}
val roomMember = event.content.toModel<RoomMember>() ?: return null
return UserEntity(event.stateKey ?: "",
roomMember.displayName ?: "",
roomMember.avatarUrl ?: ""
)
}


}

View File

@ -21,11 +21,14 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.tryTransactionAsync
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
@ -33,6 +36,7 @@ import io.realm.Sort
import javax.inject.Inject

internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
private val monarchy: Monarchy,
private val updateUserTask: UpdateUserTask,
private val taskExecutor: TaskExecutor)
: RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
@ -45,16 +49,19 @@ internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfi
}

override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
val roomMembersEvents = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.eventId }
.toList()

val taskParams = UpdateUserTask.Params(roomMembersEvents)
updateUserTask
.configureWith(taskParams)
.executeOn(TaskThread.IO)
.executeBy(taskExecutor)
monarchy.tryTransactionSync { realm ->
val userEntities = ArrayList<UserEntity>(changeSet.insertions.size)
for (insertion in changeSet.insertions) {
val roomMemberEvent = results[insertion] ?: continue
val roomMemberTimelineEvent = roomMemberEvent.timelineEventEntity?.firstOrNull()
?: continue
val userEntity = UserEntity(roomMemberEvent.stateKey
?: "", roomMemberTimelineEvent.senderName ?: "",
roomMemberTimelineEvent.senderAvatar ?: "")
userEntities.add(userEntity)
}
realm.insertOrUpdate(userEntities)
}
}

}