Timeline rework: add some comments and fix pagination when having overlapping events
This commit is contained in:
parent
63aa5b4015
commit
b370f84e08
@ -31,6 +31,13 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
* This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy.
|
||||
* There is 2 different mode: Default and Permalink.
|
||||
* In Default, we will query for the live chunk (isLastForward = true).
|
||||
* In Permalink, we will query for the chunk including the eventId we are looking for.
|
||||
* Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data.
|
||||
*/
|
||||
internal class LoadTimelineStrategy(
|
||||
private val roomId: String,
|
||||
private val timelineId: String,
|
||||
|
@ -38,6 +38,11 @@ import java.util.Collections
|
||||
*/
|
||||
private const val PAGINATION_COUNT = 50
|
||||
|
||||
/**
|
||||
* This is a wrapper around a ChunkEntity in the database.
|
||||
* It does mainly listen to the db timeline events.
|
||||
* It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
|
||||
*/
|
||||
internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
|
||||
private val roomId: String,
|
||||
private val timelineId: String,
|
||||
@ -56,7 +61,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
|
||||
private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet ->
|
||||
val frozenResults = results.freeze()
|
||||
Timber.v("on timeline event changed: $changeSet")
|
||||
handleChangeSet(frozenResults, changeSet)
|
||||
handleDatabaseChangeSet(frozenResults, changeSet)
|
||||
}
|
||||
|
||||
private var timelineEventEntities: RealmResults<TimelineEventEntity> = chunkEntity.sortedTimelineEvents()
|
||||
@ -130,6 +135,82 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
|
||||
}
|
||||
}
|
||||
|
||||
fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? {
|
||||
val builtEventIndex = builtEventsIndexes[eventId]
|
||||
if (builtEventIndex != null) {
|
||||
return getOffsetIndex() + builtEventIndex
|
||||
}
|
||||
if (searchInNext) {
|
||||
val nextBuiltEventIndex = nextChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = false)
|
||||
if (nextBuiltEventIndex != null) {
|
||||
return nextBuiltEventIndex
|
||||
}
|
||||
}
|
||||
if (searchInPrev) {
|
||||
val prevBuiltEventIndex = prevChunk?.getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = true)
|
||||
if (prevBuiltEventIndex != null) {
|
||||
return prevBuiltEventIndex
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getBuiltEvent(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): TimelineEvent? {
|
||||
val builtEventIndex = builtEventsIndexes[eventId]
|
||||
if (builtEventIndex != null) {
|
||||
return builtEvents.getOrNull(builtEventIndex)
|
||||
}
|
||||
if (searchInNext) {
|
||||
val nextBuiltEvent = nextChunk?.getBuiltEvent(eventId, searchInNext = true, searchInPrev = false)
|
||||
if (nextBuiltEvent != null) {
|
||||
return nextBuiltEvent
|
||||
}
|
||||
}
|
||||
if (searchInPrev) {
|
||||
val prevBuiltEvent = prevChunk?.getBuiltEvent(eventId, searchInNext = false, searchInPrev = true)
|
||||
if (prevBuiltEvent != null) {
|
||||
return prevBuiltEvent
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?, searchInNext: Boolean, searchInPrev: Boolean): Boolean {
|
||||
return tryOrNull {
|
||||
val builtIndex = getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = false)
|
||||
if (builtIndex == null) {
|
||||
val foundInPrev = searchInPrev && prevChunk?.rebuildEvent(eventId, builder, searchInNext = false, searchInPrev = true).orFalse()
|
||||
if (foundInPrev) {
|
||||
return true
|
||||
}
|
||||
if (searchInNext) {
|
||||
return prevChunk?.rebuildEvent(eventId, builder, searchInPrev = false, searchInNext = true).orFalse()
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Update the relation of existing event
|
||||
builtEvents.getOrNull(builtIndex)?.let { te ->
|
||||
val rebuiltEvent = builder(te)
|
||||
builtEvents[builtIndex] = rebuiltEvent!!
|
||||
true
|
||||
}
|
||||
}
|
||||
?: false
|
||||
}
|
||||
|
||||
fun close(closeNext: Boolean, closePrev: Boolean) {
|
||||
if (closeNext) {
|
||||
nextChunk?.close(closeNext = true, closePrev = false)
|
||||
}
|
||||
if (closePrev) {
|
||||
prevChunk?.close(closeNext = false, closePrev = true)
|
||||
}
|
||||
nextChunk = null
|
||||
prevChunk = null
|
||||
chunkEntity.removeChangeListener(chunkObjectListener)
|
||||
timelineEventEntities.removeChangeListener(timelineEventCollectionListener)
|
||||
}
|
||||
|
||||
private fun loadFromDb(count: Long, direction: Timeline.Direction): Long {
|
||||
val displayIndex = getNextDisplayIndex(direction) ?: return 0
|
||||
val baseQuery = timelineEventEntities.where()
|
||||
@ -211,46 +292,6 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
|
||||
}
|
||||
}
|
||||
|
||||
fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? {
|
||||
val builtEventIndex = builtEventsIndexes[eventId]
|
||||
if (builtEventIndex != null) {
|
||||
return getOffsetIndex() + builtEventIndex
|
||||
}
|
||||
if (searchInNext) {
|
||||
val nextBuiltEventIndex = nextChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = false)
|
||||
if (nextBuiltEventIndex != null) {
|
||||
return nextBuiltEventIndex
|
||||
}
|
||||
}
|
||||
if (searchInPrev) {
|
||||
val prevBuiltEventIndex = prevChunk?.getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = true)
|
||||
if (prevBuiltEventIndex != null) {
|
||||
return prevBuiltEventIndex
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getBuiltEvent(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): TimelineEvent? {
|
||||
val builtEventIndex = builtEventsIndexes[eventId]
|
||||
if (builtEventIndex != null) {
|
||||
return builtEvents.getOrNull(builtEventIndex)
|
||||
}
|
||||
if (searchInNext) {
|
||||
val nextBuiltEvent = nextChunk?.getBuiltEvent(eventId, searchInNext = true, searchInPrev = false)
|
||||
if (nextBuiltEvent != null) {
|
||||
return nextBuiltEvent
|
||||
}
|
||||
}
|
||||
if (searchInPrev) {
|
||||
val prevBuiltEvent = prevChunk?.getBuiltEvent(eventId, searchInNext = false, searchInPrev = true)
|
||||
if (prevBuiltEvent != null) {
|
||||
return prevBuiltEvent
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getOffsetIndex(): Int {
|
||||
var offset = 0
|
||||
var currentNextChunk = nextChunk
|
||||
@ -261,7 +302,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
|
||||
return offset
|
||||
}
|
||||
|
||||
private fun handleChangeSet(frozenResults: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) {
|
||||
private fun handleDatabaseChangeSet(frozenResults: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) {
|
||||
val insertions = changeSet.insertionRanges
|
||||
for (range in insertions) {
|
||||
val newItems = frozenResults
|
||||
@ -290,42 +331,6 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
|
||||
}
|
||||
}
|
||||
|
||||
fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?, searchInNext: Boolean, searchInPrev: Boolean): Boolean {
|
||||
return tryOrNull {
|
||||
val builtIndex = getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = false)
|
||||
if (builtIndex == null) {
|
||||
val foundInPrev = searchInPrev && prevChunk?.rebuildEvent(eventId, builder, searchInNext = false, searchInPrev = true).orFalse()
|
||||
if (foundInPrev) {
|
||||
return true
|
||||
}
|
||||
if (searchInNext) {
|
||||
return prevChunk?.rebuildEvent(eventId, builder, searchInPrev = false, searchInNext = true).orFalse()
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Update the relation of existing event
|
||||
builtEvents.getOrNull(builtIndex)?.let { te ->
|
||||
val rebuiltEvent = builder(te)
|
||||
builtEvents[builtIndex] = rebuiltEvent!!
|
||||
true
|
||||
}
|
||||
}
|
||||
?: false
|
||||
}
|
||||
|
||||
fun close(closeNext: Boolean, closePrev: Boolean) {
|
||||
if (closeNext) {
|
||||
nextChunk?.close(closeNext = true, closePrev = false)
|
||||
}
|
||||
if (closePrev) {
|
||||
prevChunk?.close(closeNext = false, closePrev = true)
|
||||
}
|
||||
nextChunk = null
|
||||
prevChunk = null
|
||||
chunkEntity.removeChangeListener(chunkObjectListener)
|
||||
timelineEventEntities.removeChangeListener(timelineEventCollectionListener)
|
||||
}
|
||||
|
||||
private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
|
||||
val frozenTimelineEvents = timelineEventEntities.freeze()
|
||||
if (frozenTimelineEvents.isEmpty()) {
|
||||
|
@ -40,73 +40,10 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Insert Chunk in DB, and eventually merge with existing chunk event
|
||||
* Insert Chunk in DB, and eventually link next and previous chunk in db.
|
||||
*/
|
||||
internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* ========================================================================================================
|
||||
* | Backward case |
|
||||
* ========================================================================================================
|
||||
*
|
||||
* *--------------------------* *--------------------------*
|
||||
* | startToken1 | | startToken1 |
|
||||
* *--------------------------* *--------------------------*
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | receivedChunk backward | | |
|
||||
* | Events | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* *--------------------------* *--------------------------* | |
|
||||
* | startToken0 | | endToken1 | => | Merged chunk |
|
||||
* *--------------------------* *--------------------------* | Events |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | Current Chunk | | |
|
||||
* | Events | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* *--------------------------* *--------------------------*
|
||||
* | endToken0 | | endToken0 |
|
||||
* *--------------------------* *--------------------------*
|
||||
*
|
||||
*
|
||||
* ========================================================================================================
|
||||
* | Forward case |
|
||||
* ========================================================================================================
|
||||
*
|
||||
* *--------------------------* *--------------------------*
|
||||
* | startToken0 | | startToken0 |
|
||||
* *--------------------------* *--------------------------*
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | Current Chunk | | |
|
||||
* | Events | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* *--------------------------* *--------------------------* | |
|
||||
* | endToken0 | | startToken1 | => | Merged chunk |
|
||||
* *--------------------------* *--------------------------* | Events |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | receivedChunk forward | | |
|
||||
* | Events | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* *--------------------------* *--------------------------*
|
||||
* | endToken1 | | endToken1 |
|
||||
* *--------------------------* *--------------------------*
|
||||
*
|
||||
* ========================================================================================================
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
enum class Result {
|
||||
SHOULD_FETCH_MORE,
|
||||
REACHED_END,
|
||||
@ -191,38 +128,39 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
||||
roomMemberContentsByUser[stateEvent.stateKey] = stateEvent.content.toModel<RoomMemberContent>()
|
||||
}
|
||||
}
|
||||
val eventIds = ArrayList<String>(eventList.size)
|
||||
eventList.forEach { event ->
|
||||
if (event.eventId == null || event.senderId == null) {
|
||||
return@forEach
|
||||
}
|
||||
//We check for the timeline event with this id
|
||||
val eventId = event.eventId
|
||||
val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst()
|
||||
// If it exists, we want to skip here
|
||||
val existingChunk = existingTimelineEvent?.chunk?.firstOrNull()
|
||||
if (existingChunk != null) {
|
||||
if (direction == PaginationDirection.BACKWARDS) {
|
||||
currentChunk.prevChunk = existingChunk
|
||||
existingChunk.nextChunk = currentChunk
|
||||
} else if (direction == PaginationDirection.FORWARDS) {
|
||||
currentChunk.nextChunk = existingChunk
|
||||
existingChunk.prevChunk = currentChunk
|
||||
run processTimelineEvents@ {
|
||||
eventList.forEach { event ->
|
||||
if (event.eventId == null || event.senderId == null) {
|
||||
return@forEach
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
val ageLocalTs = event.unsignedData?.age?.let { now - it }
|
||||
eventIds.add(event.eventId)
|
||||
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
|
||||
if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) {
|
||||
val contentToUse = if (direction == PaginationDirection.BACKWARDS) {
|
||||
event.prevContent
|
||||
} else {
|
||||
event.content
|
||||
//We check for the timeline event with this id
|
||||
val eventId = event.eventId
|
||||
val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst()
|
||||
// If it exists, we want to stop here, just link the prevChunk
|
||||
val existingChunk = existingTimelineEvent?.chunk?.firstOrNull()
|
||||
if (existingChunk != null) {
|
||||
if (direction == PaginationDirection.BACKWARDS) {
|
||||
currentChunk.prevChunk = existingChunk
|
||||
existingChunk.nextChunk = currentChunk
|
||||
} else if (direction == PaginationDirection.FORWARDS) {
|
||||
currentChunk.nextChunk = existingChunk
|
||||
existingChunk.prevChunk = currentChunk
|
||||
}
|
||||
// Stop processing here
|
||||
return@processTimelineEvents
|
||||
}
|
||||
roomMemberContentsByUser[event.stateKey] = contentToUse.toModel<RoomMemberContent>()
|
||||
val ageLocalTs = event.unsignedData?.age?.let { now - it }
|
||||
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
|
||||
if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) {
|
||||
val contentToUse = if (direction == PaginationDirection.BACKWARDS) {
|
||||
event.prevContent
|
||||
} else {
|
||||
event.content
|
||||
}
|
||||
roomMemberContentsByUser[event.stateKey] = contentToUse.toModel<RoomMemberContent>()
|
||||
}
|
||||
currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser)
|
||||
}
|
||||
currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser)
|
||||
}
|
||||
if (currentChunk.isValid) {
|
||||
RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk)
|
||||
|
Loading…
x
Reference in New Issue
Block a user