TimelineEvent : update sender data when loading room members and prune event (+ remove RoomSummaryMapper param)

This commit is contained in:
ganfra 2019-07-08 15:32:24 +02:00
parent 7e6e09bc19
commit dd07f5c2a6
21 changed files with 165 additions and 109 deletions

View file

@ -25,8 +25,8 @@ import io.reactivex.schedulers.Schedulers
class RxRoom(private val room: Room) {
fun liveRoomSummary(fetchLastEvent: Boolean): Observable<RoomSummary> {
return room.liveRoomSummary(fetchLastEvent).asObservable().observeOn(Schedulers.computation())
fun liveRoomSummary(): Observable<RoomSummary> {
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
}
fun liveRoomMemberIds(): Observable<List<String>> {

View file

@ -26,8 +26,8 @@ import io.reactivex.schedulers.Schedulers
class RxSession(private val session: Session) {
fun liveRoomSummaries(fetchLastEvents: Boolean): Observable<List<RoomSummary>> {
return session.liveRoomSummaries(fetchLastEvents).asObservable().observeOn(Schedulers.computation())
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
}
fun liveGroupSummaries(): Observable<List<GroupSummary>> {

View file

@ -47,8 +47,8 @@ interface Room :
* A live [RoomSummary] associated with the room
* You can observe this summary to get dynamic data from this room.
*/
fun liveRoomSummary(fetchLastEvent: Boolean = false): LiveData<RoomSummary>
fun liveRoomSummary(): LiveData<RoomSummary>
fun roomSummary(fetchLastEvent: Boolean = false): RoomSummary?
fun roomSummary(): RoomSummary?
}

View file

@ -43,6 +43,6 @@ interface RoomService {
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [RoomSummary]
*/
fun liveRoomSummaries(fetchLastEvents: Boolean = true): LiveData<List<RoomSummary>>
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
}

View file

@ -31,8 +31,7 @@ class RealmLiveData<T : RealmModel>(private val realmConfiguration: RealmConfigu
override fun onActive() {
val realm = Realm.getInstance(realmConfiguration)
val results = query.invoke(realm).findAll()
value = results
val results = query.invoke(realm).findAllAsync()
results.addChangeListener(listener)
this.realm = realm
this.results = results

View file

@ -18,28 +18,17 @@ package im.vector.matrix.android.internal.database.helper
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.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
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.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.find
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.session.room.timeline.PaginationDirection
import io.realm.RealmList
import io.realm.RealmQuery
import io.realm.Sort
// By default if a chunk is empty we consider it unlinked
@ -76,10 +65,14 @@ internal fun ChunkEntity.merge(roomId: String,
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
}
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
val eventIds = ArrayList<String>()
events.forEach { event ->
add(roomId, event, direction, isUnlinked = isUnlinked)
if (event.eventId != null) {
eventIds.add(event.eventId)
}
updateSenderDataFor(roomId, isUnlinked, events)
}
updateSenderDataFor(eventIds)
}
internal fun ChunkEntity.addAll(roomId: String,
@ -89,30 +82,20 @@ internal fun ChunkEntity.addAll(roomId: String,
// Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
isUnlinked: Boolean = false) {
assertIsManaged()
val eventIds = ArrayList<String>()
events.forEach { event ->
add(roomId, event, direction, stateIndexOffset, isUnlinked)
if (event.eventId != null) {
eventIds.add(event.eventId)
}
updateSenderDataFor(roomId, isUnlinked, events)
}
updateSenderDataFor(eventIds)
}
private fun ChunkEntity.updateSenderDataFor(roomId: String, isUnlinked: Boolean, events: List<Event>) {
for (event in events) {
val eventId = event.eventId ?: continue
internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
for (eventId in eventIds) {
val timelineEventEntity = timelineEvents.find(eventId) ?: continue
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: continue
val stateIndex = timelineEventEntity.root?.stateIndex ?: continue
val senderId = timelineEventEntity.root?.sender ?: continue
val senderRoomMemberContent = when {
stateIndex <= 0 -> timelineEvents.build(senderId, isUnlinked).next(from = stateIndex)?.root?.prevContent
else -> timelineEvents.build(senderId, isUnlinked).prev(since = stateIndex)?.root?.content
}
val fallbackContent = senderRoomMemberContent
?: roomEntity.untimelinedStateEvents.build(senderId).prev(since = stateIndex)?.content
val senderRoomMember: RoomMember? = ContentMapper.map(fallbackContent).toModel()
timelineEventEntity.senderAvatar = senderRoomMember?.avatarUrl
timelineEventEntity.senderName = senderRoomMember?.displayName
timelineEventEntity.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName)
timelineEventEntity.updateSenderData()
}
}
@ -161,19 +144,6 @@ private fun ChunkEntity.add(roomId: String,
timelineEvents.add(position, eventEntity)
}
private fun RealmList<TimelineEventEntity>.build(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)
}
private fun RealmList<EventEntity>.build(sender: String): RealmQuery<EventEntity> {
return where()
.equalTo(EventEntityFields.STATE_KEY, sender)
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
}
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsDisplayIndex

View file

@ -72,6 +72,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
it.senderName = myUser?.displayName
it.senderAvatar = myUser?.avatarUrl
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst()
}
sendingTimelineEvents.add(0, timelineEventEntity)
}

View file

@ -0,0 +1,78 @@
/*
* 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.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.RoomMember
import im.vector.matrix.android.internal.database.mapper.ContentMapper
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.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
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 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?
when {
stateIndex <= 0 -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.prevContent
}
else -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.content
}
}
// 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
}
val senderRoomMember: RoomMember? = ContentMapper.map(senderRoomMemberContent).toModel()
this.senderAvatar = senderRoomMember?.avatarUrl
this.senderName = senderRoomMember?.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName)
this.senderMembershipEvent = senderMembershipEvent
}
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

@ -29,7 +29,7 @@ internal class RoomSummaryMapper @Inject constructor(
val cryptoService: CryptoService
) {
fun map(roomSummaryEntity: RoomSummaryEntity, getLatestEvent: Boolean = false): RoomSummary {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder)
}

View file

@ -35,7 +35,8 @@ internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.r
var annotations: EventAnnotationsSummaryEntity? = null,
var senderName: String? = null,
var isUniqueDisplayName: Boolean = false,
var senderAvatar: String? = null
var senderAvatar: String? = null,
var senderMembershipEvent: EventEntity? = null
) : RealmObject() {
@LinkingObjects("timelineEvents")

View file

@ -54,6 +54,12 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm,
}
}
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
return realm.where<TimelineEventEntity>()
.equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT.EVENT_ID, senderMembershipEventId)
.findAll()
}
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
roomId: String,

View file

@ -33,8 +33,6 @@ import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.fetchCopied
import im.vector.matrix.android.internal.util.fetchCopyMap
import javax.inject.Inject
internal class DefaultRoom @Inject constructor(override val roomId: String,
@ -55,12 +53,12 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
RelationService by relationService,
MembershipService by roomMembersService {
override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData<RoomSummary> {
override fun liveRoomSummary(): LiveData<RoomSummary> {
val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm ->
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
}
return Transformations.map(liveRealmData) { results ->
val roomSummaries = results.map { roomSummaryMapper.map(it, fetchLastEvent) }
val roomSummaries = results.map { roomSummaryMapper.map(it) }
if (roomSummaries.isEmpty()) {
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
@ -71,10 +69,10 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
}
}
override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? {
override fun roomSummary(): RoomSummary? {
return monarchy.fetchAllMappedSync(
{ realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
{ roomSummaryMapper.map(it, fetchLastEvent) }
{ roomSummaryMapper.map(it) }
).firstOrNull()
}

View file

@ -52,10 +52,10 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
return roomFactory.create(roomId)
}
override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData<List<RoomSummary>> {
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return monarchy.findAllMappedWithChanges(
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
{ roomSummaryMapper.map(it, fetchLastEvents) }
{ roomSummaryMapper.map(it) }
)
}
}

View file

@ -20,15 +20,16 @@ import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership
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.query.where
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
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.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.Realm
import io.realm.kotlin.createObject
import javax.inject.Inject
@ -68,20 +69,20 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
roomEntity.addStateEvents(eventsToInsert)
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
it.updateSenderData()
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId)
}
.map { response }
}
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
return monarchy
.fetchAllCopiedSync { RoomEntity.where(it, roomId) }
.firstOrNull()
?.areAllMembersLoaded ?: false
return Realm.getInstance(monarchy.realmConfiguration).use {
RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false
}
}
}

View file

@ -29,6 +29,10 @@ import io.realm.Realm
import io.realm.RealmQuery
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
) {

View file

@ -21,12 +21,14 @@ 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.UnsignedData
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.helper.updateSenderData
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
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.findWithSenderMembershipEvent
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.Realm
@ -94,6 +96,12 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
// }
}
}
if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) {
val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId)
for (timelineEvent in timelineEventsToUpdate) {
timelineEvent.updateSenderData()
}
}
}

View file

@ -179,7 +179,7 @@ class PushrulesConditionTest {
}
}
override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData<List<RoomSummary>> {
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return MutableLiveData()
}
}
@ -194,11 +194,11 @@ class PushrulesConditionTest {
return _numberOfJoinedMembers
}
override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData<RoomSummary> {
override fun liveRoomSummary(): LiveData<RoomSummary> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? {
override fun roomSummary(): RoomSummary? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

View file

@ -73,7 +73,7 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState:
private fun observeRoomAndGroup() {
Observable
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
session.rx().liveRoomSummaries(fetchLastEvents = true).throttleLast(300, TimeUnit.MILLISECONDS),
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS),
selectedGroupStore.observe(),
BiFunction { rooms, selectedGroupOption ->
val selectedGroup = selectedGroupOption.orNull()

View file

@ -500,17 +500,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary(false)
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
if (it.membership != Membership.INVITE || it.latestEvent != null) {
// Not an invitation, or already fetching last event
Observable.just(it)
} else {
// We need the last event
room.rx().liveRoomSummary(true)
}
}
room.rx().liveRoomSummary()
.execute { async ->
copy(
asyncRoomSummary = async,

View file

@ -86,7 +86,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
private fun observeJoinedRooms() {
session
.rx()
.liveRoomSummaries(fetchLastEvents = false)
.liveRoomSummaries()
.subscribe { list ->
val joinedRoomIds = list
// Keep only joined room

View file

@ -54,7 +54,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
private fun observeJoinedRooms() {
session
.rx()
.liveRoomSummaries(fetchLastEvents = false)
.liveRoomSummaries()
.subscribe { list ->
withState { state ->
val isRoomJoined = list