diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt index 951bc6385a..b44ff8ed1c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt @@ -19,7 +19,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult -import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitCallback import javax.inject.Inject @@ -35,7 +35,7 @@ internal interface EncryptEventTask : Task { internal class DefaultEncryptEventTask @Inject constructor( // private val crypto: CryptoService - private val localEchoUpdater: LocalEchoUpdater + private val localEchoRepository: LocalEchoRepository ) : EncryptEventTask { override suspend fun execute(params: EncryptEventTask.Params): Event { if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event @@ -44,7 +44,7 @@ internal class DefaultEncryptEventTask @Inject constructor( throw IllegalArgumentException() } - localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING) + localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING) val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf() params.keepKeys?.forEach { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendEventTask.kt index 637db1790e..2cffdcf267 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendEventTask.kt @@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI -import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.task.Task import org.greenrobot.eventbus.EventBus @@ -34,7 +34,7 @@ internal interface SendEventTask : Task { } internal class DefaultSendEventTask @Inject constructor( - private val localEchoUpdater: LocalEchoUpdater, + private val localEchoRepository: LocalEchoRepository, private val encryptEventTask: DefaultEncryptEventTask, private val roomAPI: RoomAPI, private val eventBus: EventBus) : SendEventTask { @@ -44,7 +44,7 @@ internal class DefaultSendEventTask @Inject constructor( val localId = event.eventId!! try { - localEchoUpdater.updateSendState(localId, SendState.SENDING) + localEchoRepository.updateSendState(localId, SendState.SENDING) val executeRequest = executeRequest(eventBus) { apiCall = roomAPI.send( localId, @@ -53,10 +53,10 @@ internal class DefaultSendEventTask @Inject constructor( eventType = event.type ) } - localEchoUpdater.updateSendState(localId, SendState.SENT) + localEchoRepository.updateSendState(localId, SendState.SENT) return executeRequest.eventId } catch (e: Throwable) { - localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED) + localEchoRepository.updateSendState(localId, SendState.UNDELIVERED) throw e } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt index fef6c25ff7..aede23f795 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI -import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.task.Task import org.greenrobot.eventbus.EventBus @@ -34,7 +34,7 @@ internal interface SendVerificationMessageTask : Task(eventBus) { apiCall = roomAPI.send( localId, @@ -53,10 +53,10 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( eventType = event.type ) } - localEchoUpdater.updateSendState(localId, SendState.SENT) + localEchoRepository.updateSendState(localId, SendState.SENT) return executeRequest.eventId } catch (e: Throwable) { - localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED) + localEchoRepository.updateSendState(localId, SendState.UNDELIVERED) throw e } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 6db44314e5..73b35c268a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -128,6 +128,7 @@ internal class DefaultSendService @AssistedInject constructor( override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? { if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) { + localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) return sendEvent(localEcho.root) } return null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt index 6af2f8dab6..3d8e63cb97 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt @@ -52,7 +52,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) ) : SessionWorkerParams @Inject lateinit var crypto: CryptoService - @Inject lateinit var localEchoUpdater: LocalEchoUpdater + @Inject lateinit var localEchoRepository: LocalEchoRepository override suspend fun doWork(): Result { Timber.v("Start Encrypt work") @@ -74,7 +74,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) if (localEvent.eventId == null) { return Result.success() } - localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING) + localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING) val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf() params.keepKeys?.forEach { @@ -116,7 +116,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) senderCurve25519Key = result.eventContent["sender_key"] as? String, claimedEd25519Key = crypto.getMyDevice().fingerprint() ) - localEchoUpdater.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho) + localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho) } val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent) @@ -126,7 +126,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES else -> SendState.UNDELIVERED } - localEchoUpdater.updateSendState(localEvent.eventId, sendState) + localEchoRepository.updateSendState(localEvent.eventId, sendState) // always return success, or the chain will be stuck for ever! val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage ?: "Error") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt index 9ebced26e0..3c6ce786a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room.send import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.Content 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 @@ -24,7 +25,9 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.database.helper.nextId +import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity @@ -36,8 +39,8 @@ import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper +import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm @@ -83,6 +86,31 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } } + fun updateSendState(eventId: String, sendState: SendState) { + Timber.v("Update local state of $eventId to ${sendState.name}") + monarchy.writeAsync { realm -> + val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() + if (sendingEventEntity != null) { + if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { + // If already synced, do not put as sent + } else { + sendingEventEntity.sendState = sendState + } + } + } + } + + fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) { + monarchy.writeAsync { realm -> + val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() + if (sendingEventEntity != null) { + sendingEventEntity.type = EventType.ENCRYPTED + sendingEventEntity.content = ContentMapper.map(encryptedContent) + sendingEventEntity.setDecryptionResult(mxEventDecryptionResult) + } + } + } + suspend fun deleteFailedEcho(roomId: String, localEcho: TimelineEvent) { monarchy.awaitTransaction { realm -> TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm() @@ -92,11 +120,11 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private suspend fun clearSendingQueue(roomId: String) { monarchy.awaitTransaction { realm -> - RoomEntity.where(realm, roomId).findFirst()?.let { room -> - room.sendingTimelineEvents.forEach { - it.root?.sendState = SendState.UNDELIVERED - } - } + TimelineEventEntity + .findAllInRoomWithSendStates(realm, roomId, SendState.IS_SENDING_STATES) + .forEach { + it.root?.sendState = SendState.UNSENT + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt deleted file mode 100644 index cd074a7548..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt +++ /dev/null @@ -1,57 +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.send - -import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.events.model.Content -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.internal.crypto.MXEventDecryptionResult -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.query.where -import im.vector.matrix.android.internal.di.SessionDatabase -import timber.log.Timber -import javax.inject.Inject - -internal class LocalEchoUpdater @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { - - fun updateSendState(eventId: String, sendState: SendState) { - Timber.v("Update local state of $eventId to ${sendState.name}") - monarchy.writeAsync { realm -> - val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() - if (sendingEventEntity != null) { - if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { - // If already synced, do not put as sent - } else { - sendingEventEntity.sendState = sendState - } - } - } - } - - fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) { - monarchy.writeAsync { realm -> - val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() - if (sendingEventEntity != null) { - sendingEventEntity.type = EventType.ENCRYPTED - sendingEventEntity.content = ContentMapper.map(encryptedContent) - sendingEventEntity.setDecryptionResult(mxEventDecryptionResult) - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt index aec7cb3c5c..5e272ceff3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt @@ -54,7 +54,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo @Inject lateinit var workManagerProvider: WorkManagerProvider @Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon - @Inject lateinit var localEchoUpdater: LocalEchoUpdater + @Inject lateinit var localEchoRepository: LocalEchoRepository override suspend fun doWork(): Result { Timber.v("Start dispatch sending multiple event work") @@ -67,7 +67,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo if (params.lastFailureMessage != null) { params.events.forEach { event -> - event.eventId?.let { localEchoUpdater.updateSendState(it, SendState.UNDELIVERED) } + event.eventId?.let { localEchoRepository.updateSendState(it, SendState.UNDELIVERED) } } // Transmit the error if needed? return Result.success(inputData) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index ff128eb96b..8563d5959b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -33,6 +33,8 @@ import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject +private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3 + /** * Possible previous worker: [EncryptEventWorker] or first worker * Possible next worker : None @@ -63,7 +65,7 @@ internal class SendEventWorker(context: Context, ) } - @Inject lateinit var localEchoUpdater: LocalEchoUpdater + @Inject lateinit var localEchoRepository: LocalEchoRepository @Inject lateinit var roomAPI: RoomAPI @Inject lateinit var eventBus: EventBus @@ -74,16 +76,15 @@ internal class SendEventWorker(context: Context, val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) - if (params.eventId == null || params.roomId == null || params.type == null) { // compat with old params, make it fail if any if (params.event?.eventId != null) { - localEchoUpdater.updateSendState(params.event.eventId, SendState.UNDELIVERED) + localEchoRepository.updateSendState(params.event.eventId, SendState.UNDELIVERED) } return Result.success() } if (params.lastFailureMessage != null) { - localEchoUpdater.updateSendState(params.eventId, SendState.UNDELIVERED) + localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED) // Transmit the error return Result.success(inputData) .also { Timber.e("Work cancelled due to input error from parent") } @@ -92,21 +93,22 @@ internal class SendEventWorker(context: Context, sendEvent(params.eventId, params.roomId, params.type, params.contentStr) Result.success() } catch (exception: Throwable) { - if (exception.shouldBeRetried()) { - Result.retry() + // It does start from 0, we want it to stop if it fails the third time + val currentAttemptCount = runAttemptCount + 1 + if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) { + localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED) + return Result.success() } else { - localEchoUpdater.updateSendState(params.eventId, SendState.UNDELIVERED) - // always return success, or the chain will be stuck for ever! - Result.success() + Result.retry() } } } private suspend fun sendEvent(eventId: String, roomId: String, type: String, contentStr: String?) { - localEchoUpdater.updateSendState(eventId, SendState.SENDING) + localEchoRepository.updateSendState(eventId, SendState.SENDING) executeRequest(eventBus) { apiCall = roomAPI.send(eventId, roomId, type, contentStr) } - localEchoUpdater.updateSendState(eventId, SendState.SENT) + localEchoRepository.updateSendState(eventId, SendState.SENT) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 567698668b..3bb9eca766 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -342,21 +342,18 @@ internal class DefaultTimeline( private fun updateLoadingStates(results: RealmResults) { val lastCacheEvent = results.lastOrNull() - val lastBuiltEvent = builtEvents.lastOrNull() val firstCacheEvent = results.firstOrNull() - val firstBuiltEvent = builtEvents.firstOrNull() val chunkEntity = getLiveChunk() updateState(Timeline.Direction.FORWARDS) { it.copy( - hasMoreInCache = firstBuiltEvent != null && firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE, + hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId), hasReachedEnd = chunkEntity?.isLastForward ?: false ) } - updateState(Timeline.Direction.BACKWARDS) { it.copy( - hasMoreInCache = lastBuiltEvent == null || lastBuiltEvent.displayIndex > lastCacheEvent?.displayIndex ?: Int.MAX_VALUE, + hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId), hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE ) }