diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt new file mode 100644 index 0000000000..99f1edd854 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt @@ -0,0 +1,18 @@ +package im.vector.matrix.android.internal.database.helper + +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.query.fastContains + +fun List.addManagedToChunk(chunkEntity: ChunkEntity) { + if (!chunkEntity.isManaged) { + throw IllegalStateException("Chunk entity should be managed to use fast contains") + } + this.forEach { event -> + val eventEntity = event.asEntity() + if (!chunkEntity.events.fastContains(eventEntity)) { + chunkEntity.events.add(eventEntity) + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 750e30624d..fe4c60264f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -2,10 +2,10 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmObject import io.realm.RealmResults +import io.realm.annotations.Index import io.realm.annotations.LinkingObjects -import io.realm.annotations.PrimaryKey -open class EventEntity(@PrimaryKey var eventId: String = "", +open class EventEntity(@Index var eventId: String = "", var type: String = "", var content: String = "", var prevContent: String? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index b18b67ac25..05fa819c4c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -5,6 +5,7 @@ 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 io.realm.Realm +import io.realm.RealmList import io.realm.RealmQuery import io.realm.Sort @@ -37,6 +38,10 @@ fun RealmQuery.last(from: Long? = null): EventEntity? { .findFirst() } +fun RealmList.fastContains(eventEntity: EventEntity): Boolean { + return this.where().equalTo("eventId", eventEntity.eventId).findFirst() != null +} + fun EventEntity.Companion.findAllRoomMembers(realm: Realm, roomId: String): Map { return EventEntity .where(realm, roomId, EventType.STATE_ROOM_MEMBER) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 302d255403..254e7ec77c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -14,6 +14,7 @@ import im.vector.matrix.android.internal.database.query.last import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import io.realm.Realm +import io.realm.kotlin.createObject import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -46,28 +47,28 @@ internal class RoomSummaryUpdater(private val monarchy: Monarchy, val rooms = changeSet.realmResults.map { it.asDomain() } val indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions monarchy.writeAsync { realm -> - manageRoomList(realm, rooms, indexesToUpdate) + insertRoomList(realm, rooms, indexesToUpdate) } } - private fun manageRoomList(realm: Realm, rooms: List, indexes: IntArray) { + private fun insertRoomList(realm: Realm, rooms: List, indexes: IntArray) { indexes.forEach { val room = rooms[it] try { - manageRoom(realm, room) + insertRoom(realm, room) } catch (e: Exception) { Timber.e(e, "An error occured when updating room summaries") } } } - private fun manageRoom(realm: Realm, room: Room?) { + private fun insertRoom(realm: Realm, room: Room?) { if (room == null) { return } val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst() - ?: RoomSummaryEntity(room.roomId) + ?: realm.createObject(room.roomId) val lastMessageEvent = EventEntity.where(realm, room.roomId, EventType.MESSAGE).last() val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt index ca5613fd20..c214bced69 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt @@ -61,13 +61,17 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI, private fun insertInDb(response: RoomMembersResponse, roomId: String) { monarchy.runTransactionSync { realm -> // We ignore all the already known members + val roomEntity = RoomEntity.where(realm, roomId).findFirst() + ?: throw IllegalStateException("You shouldn't use this method without a room") + val roomMembers = EventEntity.findAllRoomMembers(realm, roomId) val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } - stateEventsChunkHandler.handle(realm, roomId, eventsToInsert) - RoomEntity - .where(realm, roomId).findFirst() - ?.let { it.areAllMembersLoaded = true } + val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert) + if (!roomEntity.chunks.contains(chunk)) { + roomEntity.chunks.add(chunk) + } + roomEntity.areAllMembersLoaded = true } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt index f0fb1be8fd..04b601499e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt @@ -8,6 +8,6 @@ import im.vector.matrix.android.api.session.events.model.Event data class TokenChunkEvent( @Json(name = "start") val nextToken: String? = null, @Json(name = "end") val prevToken: String? = null, - @Json(name = "chunk") val chunk: List = emptyList(), + @Json(name = "chunk") val events: List = emptyList(), @Json(name = "state") val stateEvents: List = emptyList() ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt index 72a836c3b3..4b57b07db1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt @@ -7,9 +7,10 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.util.Cancelable -import im.vector.matrix.android.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.helper.addManagedToChunk 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.query.fastContains import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findWithNextToken import im.vector.matrix.android.internal.database.query.findWithPrevToken @@ -22,6 +23,7 @@ import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import io.realm.kotlin.createObject import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -73,15 +75,16 @@ class PaginationRequest(private val roomAPI: RoomAPI, private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String) { monarchy.runTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: return@runTransactionSync + ?: throw IllegalStateException("You shouldn't use this method without a room") val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken) - ?: ChunkEntity() + ?: realm.createObject() + currentChunk.prevToken = receivedChunk.prevToken val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, receivedChunk.prevToken) - val eventIds = receivedChunk.chunk.filter { it.eventId != null }.map { it.eventId!! } + val eventIds = receivedChunk.events.filter { it.eventId != null }.map { it.eventId!! } val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds) val hasOverlapped = chunksOverlapped.isNotEmpty() @@ -90,14 +93,7 @@ class PaginationRequest(private val roomAPI: RoomAPI, roomEntity.chunks.add(stateEventsChunk) } - receivedChunk.chunk.forEach { event -> - val eventEntity = event.asEntity().let { - realm.copyToRealmOrUpdate(it) - } - if (!currentChunk.events.contains(eventEntity)) { - currentChunk.events.add(eventEntity) - } - } + receivedChunk.events.addManagedToChunk(currentChunk) if (prevChunk != null) { currentChunk.events.addAll(prevChunk.events) @@ -106,7 +102,7 @@ class PaginationRequest(private val roomAPI: RoomAPI, } else if (hasOverlapped) { chunksOverlapped.forEach { overlapped -> overlapped.events.forEach { event -> - if (!currentChunk.events.contains(event)) { + if (!currentChunk.events.fastContains(event)) { currentChunk.events.add(event) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt index 6c6aba6a14..a240504c19 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt @@ -17,21 +17,22 @@ class ReadReceiptHandler { if (content == null) { return emptyList() } - return content + val readReceipts = content .flatMap { (eventId, receiptDict) -> receiptDict .filterKeys { it == "m.read" } .flatMap { (_, userIdsDict) -> userIdsDict.map { (userId, paramsDict) -> val ts = paramsDict.filterKeys { it == "ts" } - .values - .firstOrNull() ?: 0.0 + .values + .firstOrNull() ?: 0.0 val primaryKey = roomId + userId ReadReceiptEntity(primaryKey, userId, eventId, roomId, ts) } } } - .apply { realm.insertOrUpdate(this) } + realm.insertOrUpdate(readReceipts) + return readReceipts } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index cac278952a..003fc721f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -4,7 +4,7 @@ import com.zhuinden.monarchy.Monarchy 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.room.model.MyMembership -import im.vector.matrix.android.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.helper.addManagedToChunk 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.RoomSummaryEntity @@ -16,6 +16,7 @@ import im.vector.matrix.android.internal.session.sync.model.RoomSync import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import io.realm.Realm +import io.realm.kotlin.createObject internal class RoomSyncHandler(private val monarchy: Monarchy, @@ -30,12 +31,12 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, fun handleRoomSync(handlingStrategy: HandlingStrategy) { monarchy.runTransactionSync { realm -> - val roomEntities = when (handlingStrategy) { - is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } + val rooms = when (handlingStrategy) { + is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) } - is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } + is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } } - realm.insertOrUpdate(roomEntities) + realm.insertOrUpdate(rooms) } if (handlingStrategy is HandlingStrategy.JOINED) { @@ -53,13 +54,13 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomId: String, roomSync: RoomSync): RoomEntity { - val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: RoomEntity(roomId) + val roomEntity = RoomEntity.where(realm, roomId).findFirst() + ?: RoomEntity(roomId) if (roomEntity.membership == MyMembership.INVITED) { roomEntity.chunks.deleteAllFromRealm() } - roomEntity.membership = MyMembership.JOINED if (roomSync.summary != null) { @@ -72,7 +73,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, } } if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { - val chunkEntity = handleListOfEvent(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited) + val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited) if (!roomEntity.chunks.contains(chunkEntity)) { roomEntity.chunks.add(chunkEntity) } @@ -88,7 +89,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomEntity.roomId = roomId roomEntity.membership = MyMembership.INVITED if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { - val chunkEntity = handleListOfEvent(realm, roomId, roomSync.inviteState.events) + val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events) if (!roomEntity.chunks.contains(chunkEntity)) { roomEntity.chunks.add(chunkEntity) } @@ -110,7 +111,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSummary: RoomSyncSummary) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: RoomSummaryEntity(roomId) + ?: RoomSummaryEntity(roomId) if (roomSummary.heroes.isNotEmpty()) { roomSummaryEntity.heroes.clear() @@ -125,30 +126,22 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, realm.insertOrUpdate(roomSummaryEntity) } - private fun handleListOfEvent(realm: Realm, - roomId: String, - eventList: List, - prevToken: String? = null, - nextToken: String? = null, - isLimited: Boolean = true): ChunkEntity { + private fun handleTimelineEvents(realm: Realm, + roomId: String, + eventList: List, + prevToken: String? = null, + nextToken: String? = null, + isLimited: Boolean = true): ChunkEntity { val chunkEntity = if (!isLimited) { ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) } else { val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! } ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull() - } ?: ChunkEntity().apply { this.prevToken = prevToken } + } ?: realm.createObject().apply { this.prevToken = prevToken } chunkEntity.nextToken = nextToken - - eventList.forEach { event -> - val eventEntity = event.asEntity().let { - realm.copyToRealmOrUpdate(it) - } - if (!chunkEntity.events.contains(eventEntity)) { - chunkEntity.events.add(eventEntity) - } - } + eventList.addManagedToChunk(chunkEntity) return chunkEntity } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt index aeb9a86e5a..daea234a5a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt @@ -2,27 +2,27 @@ package im.vector.matrix.android.internal.session.sync import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.database.DBConstants -import im.vector.matrix.android.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.helper.addManagedToChunk import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.query.findWithNextToken import io.realm.Realm +import io.realm.kotlin.createObject class StateEventsChunkHandler { fun handle(realm: Realm, roomId: String, stateEvents: List): ChunkEntity { val chunkEntity = ChunkEntity.findWithNextToken(realm, roomId, DBConstants.STATE_EVENTS_CHUNK_TOKEN) - ?: ChunkEntity(prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN, nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN) + ?: realm.createObject() + .apply { + prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN + nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN + } - stateEvents.forEach { event -> - val eventEntity = event.asEntity().let { - realm.copyToRealmOrUpdate(it) - } - if (!chunkEntity.events.contains(eventEntity)) { - chunkEntity.events.add(eventEntity) - } - } + stateEvents.addManagedToChunk(chunkEntity) return chunkEntity } -} \ No newline at end of file +} + +