Sync/pagination: make it possible to have multiple chunks in the db
This commit is contained in:
parent
1c4cef9115
commit
1aa65dad7f
@ -19,37 +19,40 @@ 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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.api.session.room.send.SendState
|
||||||
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.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
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.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
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.fastContains
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.Sort
|
|
||||||
|
|
||||||
// By default if a chunk is empty we consider it unlinked
|
|
||||||
internal fun ChunkEntity.isUnlinked(): Boolean {
|
|
||||||
assertIsManaged()
|
|
||||||
return timelineEvents.where()
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
|
|
||||||
.findAll()
|
|
||||||
.isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
|
this.stateEvents.deleteAllFromRealm()
|
||||||
this.timelineEvents.deleteAllFromRealm()
|
this.timelineEvents.deleteAllFromRealm()
|
||||||
this.deleteFromRealm()
|
this.deleteFromRealm()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ChunkEntity.addStateEvent(stateEvent: Event) {
|
||||||
|
if (stateEvent.eventId == null || stateEvents.fastContains(stateEvent.eventId)) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
val entity = stateEvent.toEntity(roomId).apply {
|
||||||
|
this.stateIndex = Int.MIN_VALUE
|
||||||
|
this.isUnlinked = true
|
||||||
|
this.sendState = SendState.SYNCED
|
||||||
|
}
|
||||||
|
stateEvents.add(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun ChunkEntity.add(localRealm: Realm,
|
internal fun ChunkEntity.add(localRealm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
@ -57,7 +60,7 @@ internal fun ChunkEntity.add(localRealm: Realm,
|
|||||||
direction: PaginationDirection,
|
direction: PaginationDirection,
|
||||||
stateIndexOffset: Int = 0,
|
stateIndexOffset: Int = 0,
|
||||||
isUnlinked: Boolean = false) {
|
isUnlinked: Boolean = false) {
|
||||||
if (event.eventId == null) {
|
if (event.eventId == null || timelineEvents.fastContains(event.eventId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||||
|
|||||||
@ -33,15 +33,13 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomEntity.addStateEvent(stateEvent: Event,
|
internal fun RoomEntity.addStateEvent(stateEvent: Event,
|
||||||
stateIndex: Int = Int.MIN_VALUE,
|
stateIndex: Int = Int.MIN_VALUE) {
|
||||||
filterDuplicates: Boolean = false,
|
if (stateEvent.eventId == null || untimelinedStateEvents.fastContains(stateEvent.eventId)) {
|
||||||
isUnlinked: Boolean = false) {
|
|
||||||
if (stateEvent.eventId == null || (filterDuplicates && untimelinedStateEvents.fastContains(stateEvent.eventId))) {
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
val entity = stateEvent.toEntity(roomId).apply {
|
val entity = stateEvent.toEntity(roomId).apply {
|
||||||
this.stateIndex = stateIndex
|
this.stateIndex = stateIndex
|
||||||
this.isUnlinked = isUnlinked
|
this.isUnlinked = false
|
||||||
this.sendState = SendState.SYNCED
|
this.sendState = SendState.SYNCED
|
||||||
}
|
}
|
||||||
untimelinedStateEvents.add(entity)
|
untimelinedStateEvents.add(entity)
|
||||||
|
|||||||
@ -32,30 +32,36 @@ internal fun TimelineEventEntity.updateSenderData(realm: Realm, chunkEntity: Chu
|
|||||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
|
||||||
val stateIndex = root?.stateIndex ?: return
|
val stateIndex = root?.stateIndex ?: return
|
||||||
val senderId = root?.sender ?: return
|
val senderId = root?.sender ?: return
|
||||||
val isUnlinked = chunkEntity.isUnlinked()
|
|
||||||
var senderMembershipEvent: EventEntity?
|
var senderMembershipEvent: EventEntity?
|
||||||
var senderRoomMemberContent: String?
|
var senderRoomMemberContent: String?
|
||||||
var senderRoomMemberPrevContent: String?
|
var senderRoomMemberPrevContent: String?
|
||||||
when {
|
when {
|
||||||
stateIndex <= 0 -> {
|
stateIndex <= 0 -> {
|
||||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
|
senderMembershipEvent = chunkEntity.timelineEvents.buildTimelineEventQuery(senderId).next(from = stateIndex)?.root
|
||||||
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
||||||
senderRoomMemberPrevContent = senderMembershipEvent?.content
|
senderRoomMemberPrevContent = senderMembershipEvent?.content
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
|
senderMembershipEvent = chunkEntity.timelineEvents.buildTimelineEventQuery(senderId).prev(since = stateIndex)?.root
|
||||||
senderRoomMemberContent = senderMembershipEvent?.content
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
|
// We fallback to chunk stateEvents if we can't find membership events in timeline
|
||||||
|
if (senderMembershipEvent == null) {
|
||||||
|
senderMembershipEvent = chunkEntity.stateEvents
|
||||||
|
.buildStateEventQuery(senderId)
|
||||||
|
.prev()
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
|
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fallback to room stateEvents if we can't find membership events in timeline and chunk
|
||||||
if (senderMembershipEvent == null) {
|
if (senderMembershipEvent == null) {
|
||||||
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
||||||
.where()
|
.buildStateEventQuery(senderId)
|
||||||
.equalTo(EventEntityFields.STATE_KEY, senderId)
|
.prev()
|
||||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
|
||||||
.prev(since = stateIndex)
|
|
||||||
senderRoomMemberContent = senderMembershipEvent?.content
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||||
}
|
}
|
||||||
@ -88,9 +94,15 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
|
private fun RealmList<EventEntity>.buildStateEventQuery(sender: String): RealmQuery<EventEntity> {
|
||||||
|
return where()
|
||||||
|
.equalTo(EventEntityFields.STATE_KEY, sender)
|
||||||
|
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun RealmList<TimelineEventEntity>.buildTimelineEventQuery(sender: String): RealmQuery<TimelineEventEntity> {
|
||||||
return where()
|
return where()
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,8 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
|
|||||||
@Index var nextToken: String? = null,
|
@Index var nextToken: String? = null,
|
||||||
@Index var roomId: String = "",
|
@Index var roomId: String = "",
|
||||||
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||||
|
// These are state events for chunks other than the live one (isLastForward=true)
|
||||||
|
var stateEvents: RealmList<EventEntity> = RealmList(),
|
||||||
@Index var isLastForward: Boolean = false,
|
@Index var isLastForward: Boolean = false,
|
||||||
@Index var isLastBackward: Boolean = false,
|
@Index var isLastBackward: Boolean = false,
|
||||||
var backwardsDisplayIndex: Int? = null,
|
var backwardsDisplayIndex: Int? = null,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import io.realm.annotations.PrimaryKey
|
|||||||
|
|
||||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||||
|
// These are live state events coming from sync only
|
||||||
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
|
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
|
||||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||||
var areAllMembersLoaded: Boolean = false
|
var areAllMembersLoaded: Boolean = false
|
||||||
|
|||||||
@ -1,38 +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.database.model
|
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
|
||||||
import io.realm.RealmObject
|
|
||||||
import io.realm.annotations.PrimaryKey
|
|
||||||
|
|
||||||
internal open class RoomStateMemberEntity(@PrimaryKey var primaryKey: String = "",
|
|
||||||
var displayName: String? = null,
|
|
||||||
var avatarUrl: String? = null
|
|
||||||
) : RealmObject() {
|
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
|
||||||
var membership: Membership
|
|
||||||
get() {
|
|
||||||
return Membership.valueOf(membershipStr)
|
|
||||||
}
|
|
||||||
set(value) {
|
|
||||||
membershipStr = value.name
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object
|
|
||||||
}
|
|
||||||
@ -115,6 +115,10 @@ internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEvent
|
|||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RealmList<TimelineEventEntity>.fastContains(eventId: String): Boolean {
|
||||||
|
return find(eventId) != null
|
||||||
|
}
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Realm,
|
internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
sendStates: List<SendState>)
|
sendStates: List<SendState>)
|
||||||
|
|||||||
@ -122,9 +122,6 @@ internal abstract class RoomModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
|
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindClearUnlinkedEventsTask(clearUnlinkedEventsTask: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
|
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.room.membership
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
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.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
|||||||
@ -1,48 +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.state
|
|
||||||
|
|
||||||
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.RoomAvatarContent
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
|
||||||
import io.realm.Realm
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is responsible for mutating the "room state" entities, so
|
|
||||||
*/
|
|
||||||
internal class RoomStateMutator(private val realm: Realm, private val roomId: String) {
|
|
||||||
|
|
||||||
fun mutate(event: Event): Boolean {
|
|
||||||
return when (event.type) {
|
|
||||||
EventType.STATE_ROOM_AVATAR -> mutateRoomAvatar(event.content?.toModel())
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mutateRoomAvatar(model: RoomAvatarContent?): Boolean {
|
|
||||||
if (model == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
|
||||||
roomSummaryEntity.avatarUrl = model.avatarUrl
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,52 +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.timeline
|
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
|
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
|
||||||
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.query.where
|
|
||||||
import im.vector.matrix.android.internal.task.Task
|
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal interface ClearUnlinkedEventsTask : Task<ClearUnlinkedEventsTask.Params, Unit> {
|
|
||||||
|
|
||||||
data class Params(val roomId: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DefaultClearUnlinkedEventsTask @Inject constructor(private val monarchy: Monarchy) : ClearUnlinkedEventsTask {
|
|
||||||
|
|
||||||
override suspend fun execute(params: ClearUnlinkedEventsTask.Params) {
|
|
||||||
monarchy.awaitTransaction { localRealm ->
|
|
||||||
val unlinkedChunks = ChunkEntity
|
|
||||||
.where(localRealm, roomId = params.roomId)
|
|
||||||
.equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true)
|
|
||||||
.findAll()
|
|
||||||
unlinkedChunks.forEach {
|
|
||||||
it.deleteOnCascade()
|
|
||||||
}
|
|
||||||
val roomEntity = RoomEntity.where(localRealm, roomId = params.roomId).findFirst()
|
|
||||||
?: return@awaitTransaction
|
|
||||||
roomEntity.untimelinedStateEvents.where().equalTo(EventEntityFields.IS_UNLINKED, true).findAll().deleteAllFromRealm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -41,6 +41,6 @@ internal class DefaultGetContextOfEventTask @Inject constructor(private val room
|
|||||||
val response = executeRequest<EventContextResponse> {
|
val response = executeRequest<EventContextResponse> {
|
||||||
apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, params.limit, filter)
|
apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, params.limit, filter)
|
||||||
}
|
}
|
||||||
return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.FORWARDS)
|
return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,6 @@ internal class DefaultTimeline(
|
|||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val clearUnlinkedEventsTask: ClearUnlinkedEventsTask,
|
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
@ -217,9 +216,6 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
eventDecryptor.destroy()
|
eventDecryptor.destroy()
|
||||||
}
|
}
|
||||||
clearUnlinkedEventsTask
|
|
||||||
.configureWith(ClearUnlinkedEventsTask.Params(roomId))
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +331,7 @@ internal class DefaultTimeline(
|
|||||||
val lastBuiltEvent = builtEvents.lastOrNull()
|
val lastBuiltEvent = builtEvents.lastOrNull()
|
||||||
val firstCacheEvent = results.firstOrNull()
|
val firstCacheEvent = results.firstOrNull()
|
||||||
val firstBuiltEvent = builtEvents.firstOrNull()
|
val firstBuiltEvent = builtEvents.firstOrNull()
|
||||||
val chunkEntity = getLiveChunk()
|
val chunkEntity = getCurrentChunk()
|
||||||
|
|
||||||
updateState(Timeline.Direction.FORWARDS) {
|
updateState(Timeline.Direction.FORWARDS) {
|
||||||
it.copy(
|
it.copy(
|
||||||
@ -371,7 +367,6 @@ internal class DefaultTimeline(
|
|||||||
} else {
|
} else {
|
||||||
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return !shouldFetchMore
|
return !shouldFetchMore
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,7 +483,7 @@ internal class DefaultTimeline(
|
|||||||
* This has to be called on TimelineThread as it access realm live results
|
* This has to be called on TimelineThread as it access realm live results
|
||||||
*/
|
*/
|
||||||
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
||||||
val token = getTokenLive(direction)
|
val token = getCurrentToken(direction)
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
||||||
return
|
return
|
||||||
@ -510,6 +505,7 @@ internal class DefaultTimeline(
|
|||||||
Timber.v("Success fetching $limit items $direction from pagination request")
|
Timber.v("Success fetching $limit items $direction from pagination request")
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.REACHED_END -> {
|
TokenChunkEventPersistor.Result.REACHED_END -> {
|
||||||
|
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
||||||
@ -532,15 +528,15 @@ internal class DefaultTimeline(
|
|||||||
* This has to be called on TimelineThread as it access realm live results
|
* This has to be called on TimelineThread as it access realm live results
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private fun getTokenLive(direction: Timeline.Direction): String? {
|
private fun getCurrentToken(direction: Timeline.Direction): String? {
|
||||||
val chunkEntity = getLiveChunk() ?: return null
|
val chunkEntity = getCurrentChunk() ?: return null
|
||||||
return if (direction == Timeline.Direction.BACKWARDS) chunkEntity.prevToken else chunkEntity.nextToken
|
return if (direction == Timeline.Direction.BACKWARDS) chunkEntity.prevToken else chunkEntity.nextToken
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This has to be called on TimelineThread as it access realm live results
|
* This has to be called on TimelineThread as it access realm live results
|
||||||
*/
|
*/
|
||||||
private fun getLiveChunk(): ChunkEntity? {
|
private fun getCurrentChunk(): ChunkEntity? {
|
||||||
return filteredEvents.firstOrNull()?.chunk?.firstOrNull()
|
return filteredEvents.firstOrNull()?.chunk?.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,7 +619,7 @@ internal class DefaultTimeline(
|
|||||||
private fun buildEventQuery(realm: Realm): RealmQuery<TimelineEventEntity> {
|
private fun buildEventQuery(realm: Realm): RealmQuery<TimelineEventEntity> {
|
||||||
return if (initialEventId == null) {
|
return if (initialEventId == null) {
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY)
|
.where(realm, roomId = roomId)
|
||||||
.equalTo("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true)
|
.equalTo("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true)
|
||||||
} else {
|
} else {
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
|
|||||||
@ -42,8 +42,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||||
private val clearUnlinkedEventsTask: ClearUnlinkedEventsTask
|
|
||||||
) : TimelineService {
|
) : TimelineService {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
@ -57,7 +56,6 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||||||
monarchy.realmConfiguration,
|
monarchy.realmConfiguration,
|
||||||
taskExecutor,
|
taskExecutor,
|
||||||
contextOfEventTask,
|
contextOfEventTask,
|
||||||
clearUnlinkedEventsTask,
|
|
||||||
paginationTask,
|
paginationTask,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
timelineEventMapper,
|
timelineEventMapper,
|
||||||
|
|||||||
@ -23,14 +23,15 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class EventContextResponse(
|
data class EventContextResponse(
|
||||||
@Json(name = "event") val event: Event,
|
@Json(name = "event") val event: Event,
|
||||||
@Json(name = "start") override val start: String? = null,
|
// Reversed start and end on purpose
|
||||||
|
@Json(name = "start") override val end: String? = null,
|
||||||
|
@Json(name = "end") override val start: String? = null,
|
||||||
@Json(name = "events_before") val eventsBefore: List<Event> = emptyList(),
|
@Json(name = "events_before") val eventsBefore: List<Event> = emptyList(),
|
||||||
@Json(name = "events_after") val eventsAfter: List<Event> = emptyList(),
|
@Json(name = "events_after") val eventsAfter: List<Event> = emptyList(),
|
||||||
@Json(name = "end") override val end: String? = null,
|
|
||||||
@Json(name = "state") override val stateEvents: List<Event> = emptyList()
|
@Json(name = "state") override val stateEvents: List<Event> = emptyList()
|
||||||
) : TokenChunkEvent {
|
) : TokenChunkEvent {
|
||||||
|
|
||||||
override val events: List<Event> by lazy {
|
override val events: List<Event> by lazy {
|
||||||
eventsBefore.reversed() + listOf(event) + eventsAfter
|
eventsAfter.reversed() + listOf(event) + eventsBefore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,17 +17,20 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.timeline
|
package im.vector.matrix.android.internal.session.room.timeline
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.internal.database.helper.*
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.internal.database.helper.add
|
||||||
|
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.deleteOnCascade
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
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.RoomEntity
|
||||||
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.find
|
||||||
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
|
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
|
import io.realm.Realm
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -110,8 +113,21 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
suspend fun insertInDb(receivedChunk: TokenChunkEvent,
|
suspend fun insertInDb(receivedChunk: TokenChunkEvent,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
direction: PaginationDirection): Result {
|
direction: PaginationDirection): Result {
|
||||||
monarchy
|
monarchy.awaitTransaction { realm ->
|
||||||
.awaitTransaction { realm ->
|
handleChunk(realm, receivedChunk, roomId, direction)
|
||||||
|
}
|
||||||
|
return if (receivedChunk.events.isEmpty()) {
|
||||||
|
if (receivedChunk.start != receivedChunk.end) {
|
||||||
|
Result.SHOULD_FETCH_MORE
|
||||||
|
} else {
|
||||||
|
Result.REACHED_END
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Result.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleChunk(realm: Realm, receivedChunk: TokenChunkEvent, roomId: String, direction: PaginationDirection) {
|
||||||
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
||||||
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||||
@ -144,6 +160,20 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
this.nextToken = nextToken
|
this.nextToken = nextToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We are saving the state events in the chunk, it will allow us to keep multiple chunks alive
|
||||||
|
receivedChunk.stateEvents.forEach { stateEvent ->
|
||||||
|
currentChunk.addStateEvent(stateEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
val eventIds = receivedChunk.events.mapNotNull { it.eventId }
|
||||||
|
// If we are overlapping with a chunk other than the live one we remove it to avoid the merging processes
|
||||||
|
ChunkEntity.findAllIncludingEvents(realm, eventIds)
|
||||||
|
.asSequence()
|
||||||
|
.filterNot { it.isLastForward }
|
||||||
|
.forEach {
|
||||||
|
it.deleteOnCascade()
|
||||||
|
}
|
||||||
|
|
||||||
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
|
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
Timber.v("Reach live state of $roomId")
|
Timber.v("Reach live state of $roomId")
|
||||||
@ -154,7 +184,6 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
currentChunk.timelineEvents.forEach {
|
currentChunk.timelineEvents.forEach {
|
||||||
it.root?.isUnlinked = false
|
it.root?.isUnlinked = false
|
||||||
}
|
}
|
||||||
roomSummaryUpdater.update(realm, roomId, updateMembers = false)
|
|
||||||
} else {
|
} else {
|
||||||
Timber.v("Reach end of $roomId")
|
Timber.v("Reach end of $roomId")
|
||||||
currentChunk.isLastBackward = true
|
currentChunk.isLastBackward = true
|
||||||
@ -166,15 +195,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomEntity.addOrUpdate(currentChunk)
|
roomEntity.addOrUpdate(currentChunk)
|
||||||
|
roomSummaryUpdater.update(realm, roomId, updateMembers = false)
|
||||||
}
|
}
|
||||||
return if (receivedChunk.events.isEmpty()) {
|
|
||||||
if (receivedChunk.start != receivedChunk.end) {
|
|
||||||
Result.SHOULD_FETCH_MORE
|
|
||||||
} else {
|
|
||||||
Result.REACHED_END
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Result.SUCCESS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,7 @@ internal class RoomEntityFactory @Inject constructor(private val cryptoService:
|
|||||||
// State events
|
// State events
|
||||||
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
||||||
roomSync.state.events.forEach { event ->
|
roomSync.state.events.forEach { event ->
|
||||||
roomEntity.addStateEvent(event, filterDuplicates = false, stateIndex = Int.MIN_VALUE)
|
roomEntity.addStateEvent(event)
|
||||||
// Give info to crypto module
|
// Give info to crypto module
|
||||||
cryptoService.onStateEvent(roomId, event)
|
cryptoService.onStateEvent(roomId, event)
|
||||||
UserEntityFactory.createOrNull(event)?.also {
|
UserEntityFactory.createOrNull(event)?.also {
|
||||||
@ -116,7 +116,7 @@ internal class RoomEntityFactory @Inject constructor(private val cryptoService:
|
|||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val untimelinedStateIndex = minStateIndex + 1
|
val untimelinedStateIndex = minStateIndex + 1
|
||||||
roomSync.state.events.forEach { event ->
|
roomSync.state.events.forEach { event ->
|
||||||
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
roomEntity.addStateEvent(event, stateIndex = untimelinedStateIndex)
|
||||||
// Give info to crypto module
|
// Give info to crypto module
|
||||||
cryptoService.onStateEvent(roomId, event)
|
cryptoService.onStateEvent(roomId, event)
|
||||||
UserEntityFactory.createOrNull(event)?.also {
|
UserEntityFactory.createOrNull(event)?.also {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user