/* * 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.sync 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.query.createUnmanaged import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import io.realm.Realm import timber.log.Timber import javax.inject.Inject // the receipts dictionnaries // key : $EventId // value : dict key $UserId // value dict key ts // dict value ts value typealias ReadReceiptContent = Map>>> private const val READ_KEY = "m.read" private const val TIMESTAMP_KEY = "ts" internal class ReadReceiptHandler @Inject constructor() { fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) { if (content == null) { return } try { handleReadReceiptContent(realm, roomId, content, isInitialSync) } catch (exception: Exception) { Timber.e("Fail to handle read receipt for room $roomId") } } private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) { if (isInitialSync) { initialSyncStrategy(realm, roomId, content) } else { incrementalSyncStrategy(realm, roomId, content) } } private fun initialSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { val readReceiptSummaries = ArrayList() for ((eventId, receiptDict) in content) { val userIdsDict = receiptDict[READ_KEY] ?: continue val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId) for ((userId, paramsDict) in userIdsDict) { val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0 val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, ts) readReceiptsSummary.readReceipts.add(receiptEntity) } readReceiptSummaries.add(readReceiptsSummary) } realm.insertOrUpdate(readReceiptSummaries) } private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { for ((eventId, receiptDict) in content) { val userIdsDict = receiptDict[READ_KEY] ?: continue val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() ?: realm.createObject(ReadReceiptsSummaryEntity::class.java, eventId).apply { this.roomId = roomId } for ((userId, paramsDict) in userIdsDict) { val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0 val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId) // ensure new ts is superior to the previous one if (ts > receiptEntity.originServerTs) { ReadReceiptsSummaryEntity.where(realm, receiptEntity.eventId).findFirst()?.also { it.readReceipts.remove(receiptEntity) } receiptEntity.eventId = eventId receiptEntity.originServerTs = ts readReceiptsSummary.readReceipts.add(receiptEntity) } } } } }