From d7558902f756a14e9d5f3e68a33dc89cce56bd2e Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 22 Jul 2020 11:12:37 +0200 Subject: [PATCH 1/6] Sending: limit to 3 retry before failing --- .../internal/crypto/tasks/EncryptEventTask.kt | 6 +- .../internal/crypto/tasks/SendEventTask.kt | 10 ++-- .../tasks/SendVerificationMessageTask.kt | 10 ++-- .../session/room/send/DefaultSendService.kt | 1 + .../session/room/send/EncryptEventWorker.kt | 8 +-- .../session/room/send/LocalEchoRepository.kt | 40 +++++++++++-- .../session/room/send/LocalEchoUpdater.kt | 57 ------------------- .../MultipleEventSendingDispatcherWorker.kt | 4 +- .../session/room/send/SendEventWorker.kt | 24 ++++---- .../session/room/timeline/DefaultTimeline.kt | 7 +-- 10 files changed, 69 insertions(+), 98 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt 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 ) } From aa5ae45a0c54507f3ff9d41e3fa891abe1c95ff9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 22 Jul 2020 15:21:48 +0200 Subject: [PATCH 2/6] Add hasFailedSending in RoomSummary and a small warning icon on room list --- .../android/api/session/room/model/RoomSummary.kt | 3 ++- .../internal/database/mapper/RoomSummaryMapper.kt | 3 ++- .../internal/database/model/RoomSummaryEntity.kt | 3 ++- .../database/query/TimelineEventEntityQueries.kt | 11 +++++++---- .../session/room/send/LocalEchoRepository.kt | 6 +++++- .../session/room/summary/RoomSummaryUpdater.kt | 14 ++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 7 ++++--- .../features/home/room/list/RoomSummaryItem.kt | 3 +++ .../home/room/list/RoomSummaryItemFactory.kt | 1 + vector/src/main/res/layout/item_room.xml | 11 +++++++++++ 10 files changed, 51 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index ba34e0e01d..b6b0555433 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -53,7 +53,8 @@ data class RoomSummary constructor( val typingUsers: List, val inviterId: String? = null, val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, - val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null + val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null, + val hasFailedSending: Boolean = false ) { val isVersioned: Boolean diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index fb155316ee..cdd38ad2f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -62,7 +62,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa encryptionEventTs = roomSummaryEntity.encryptionEventTs, breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex, roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel, - inviterId = roomSummaryEntity.inviterId + inviterId = roomSummaryEntity.inviterId, + hasFailedSending = roomSummaryEntity.hasFailedSending ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index acfd484deb..eb49844ed6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -51,7 +51,8 @@ internal open class RoomSummaryEntity( var isEncrypted: Boolean = false, var encryptionEventTs: Long? = 0, var roomEncryptionTrustLevelStr: String? = null, - var inviterId: String? = null + var inviterId: String? = null, + var hasFailedSending: Boolean = false ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index fb1cc8136a..db1b01fd7a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -93,9 +93,12 @@ internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Re roomId: String, sendStates: List) : RealmResults { - val sendStatesStr = sendStates.map { it.name }.toTypedArray() - return realm.where() - .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) - .`in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr) + return whereRoomId(realm, roomId) + .filterSendStates(sendStates) .findAll() } + +internal fun RealmQuery.filterSendStates(sendStates: List): RealmQuery { + val sendStatesStr = sendStates.map { it.name }.toTypedArray() + return `in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr) +} 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 3c6ce786a5..bd6927032a 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 @@ -82,7 +82,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private realm.insert(eventInsertEntity) val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync roomEntity.sendingTimelineEvents.add(0, timelineEventEntity) - roomSummaryUpdater.update(realm, roomId) + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } @@ -96,6 +96,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } else { sendingEventEntity.sendState = sendState } + roomSummaryUpdater.update(realm, sendingEventEntity.roomId) } } } @@ -115,6 +116,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private monarchy.awaitTransaction { realm -> TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm() EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm() + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } @@ -125,6 +127,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private .forEach { it.root?.sendState = SendState.UNSENT } + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } @@ -134,6 +137,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private timelineEvents.forEach { it.root?.sendState = sendState } + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt index d61ebc4fc1..342926f65f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate import im.vector.matrix.android.internal.database.mapper.ContentMapper @@ -34,6 +35,7 @@ import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntity 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.getOrCreate import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.isEventRead @@ -145,6 +147,7 @@ internal class RoomSummaryUpdater @Inject constructor( } else if (roomSummaryEntity.membership != Membership.INVITE) { roomSummaryEntity.inviterId = null } + roomSummaryEntity.updateHasFailedSending() if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) { Timber.v("Should decrypt ${latestPreviewableEvent.eventId}") @@ -167,6 +170,17 @@ internal class RoomSummaryUpdater @Inject constructor( } } + private fun RoomSummaryEntity.updateHasFailedSending() { + hasFailedSending = TimelineEventEntity.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES).isNotEmpty() + } + + fun updateSendingInformation(realm: Realm, roomId: String) { + val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) + roomSummaryEntity.updateHasFailedSending() + roomSummaryEntity.latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, + filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true) + } + fun updateShieldTrust(realm: Realm, roomId: String, trust: RoomEncryptionTrustLevel?) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index a396152f6b..912eb90a0f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -30,6 +30,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.MXCryptoError @@ -415,11 +416,11 @@ class RoomDetailViewModel @AssistedInject constructor( R.id.clear_message_queue -> // For now always disable when not in developer mode, worker cancellation is not working properly timeline.pendingEventCount() > 0 && vectorPreferences.developerMode() - R.id.resend_all -> timeline.failedToDeliverEventCount() > 0 - R.id.clear_all -> timeline.failedToDeliverEventCount() > 0 + R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.open_matrix_apps -> true R.id.voice_call, - R.id.video_call -> room.canStartCall() && webRtcPeerConnectionManager.currentCall == null + R.id.video_call -> state.asyncRoomSummary()?.canStartCall == true && webRtcPeerConnectionManager.currentCall == null R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null else -> false } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt index 04193dba0d..0eebd12ff4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt @@ -52,6 +52,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { @EpoxyAttribute var hasUnreadMessage: Boolean = false @EpoxyAttribute var hasDraft: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false + @EpoxyAttribute var hasFailedSending: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null @EpoxyAttribute var showSelected: Boolean = false @@ -72,6 +73,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { avatarRenderer.render(matrixItem, holder.avatarImageView) holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes()) + holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending renderSelection(holder, showSelected) holder.typingView.setTextOrHide(typingMessage) holder.lastEventView.isInvisible = holder.typingView.isVisible @@ -106,6 +108,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { val avatarCheckedImageView by bind(R.id.roomAvatarCheckedImageView) val avatarImageView by bind(R.id.roomAvatarImageView) val roomAvatarDecorationImageView by bind(R.id.roomAvatarDecorationImageView) + val roomAvatarFailSendingImageView by bind(R.id.roomAvatarFailSendingImageView) val rootView by bind(R.id.itemRoomLayout) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index f33166504d..cd2bb2d7d0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -109,6 +109,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor .lastFormattedEvent(latestFormattedEvent) .showHighlighted(showHighlighted) .showSelected(showSelected) + .hasFailedSending(roomSummary.hasFailedSending) .unreadNotificationCount(unreadCount) .hasUnreadMessage(roomSummary.hasUnreadMessages) .hasDraft(roomSummary.userDrafts.isNotEmpty()) diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 84fc0956a2..cf148043ec 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -47,6 +47,17 @@ + + Date: Wed, 22 Jul 2020 16:02:38 +0200 Subject: [PATCH 3/6] Composer: set max lines to 6 so it can scroll --- .../layout/constraint_set_composer_layout_compact.xml | 7 +------ .../layout/constraint_set_composer_layout_expanded.xml | 7 +------ vector/src/main/res/layout/merge_composer_layout.xml | 6 +----- vector/src/main/res/values/styles_riot.xml | 10 ++++++++++ 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml index a2bf24551d..070e9ee575 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml @@ -158,17 +158,12 @@ diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml index d36575df1f..72801f02c4 100644 --- a/vector/src/main/res/values/styles_riot.xml +++ b/vector/src/main/res/values/styles_riot.xml @@ -385,4 +385,14 @@ 40dp + + \ No newline at end of file From 3e429490e7e4f98cad33dbc8dd307d71d350d87d Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 22 Jul 2020 17:04:48 +0200 Subject: [PATCH 4/6] Update CHANGES --- CHANGES.md | 3 ++- .../riotx/features/home/room/detail/RoomDetailViewModel.kt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index acc0a74055..b1e85e239d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,8 @@ Features ✨: - Improvements 🙌: - - + - Sending events is now retried only 3 times, so we avoid blocking the sending queue too long. + - Display warning when fail to send events in room list Bugfix 🐛: - Fix theme issue on Room directory screen (#1613) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 912eb90a0f..2bec8017f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -30,7 +30,6 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.NoOpMatrixCallback -import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.MXCryptoError From cac80991178f958f62de7651ba2aa13725722afa Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 23 Jul 2020 16:38:17 +0200 Subject: [PATCH 5/6] Local echo: use missing updateSendingInformation on RoomSummaryUpdater --- .../android/internal/session/room/send/LocalEchoRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bd6927032a..bbb20802f5 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 @@ -96,7 +96,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } else { sendingEventEntity.sendState = sendState } - roomSummaryUpdater.update(realm, sendingEventEntity.roomId) + roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId) } } } From 407595e6133fcde8696bd74405fb0900bcb7564a Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 23 Jul 2020 16:38:42 +0200 Subject: [PATCH 6/6] Change warning badge (white background) --- .../action/MessageActionsEpoxyController.kt | 2 +- vector/src/main/res/drawable/ic_warning_badge.xml | 15 +++++++++++++++ vector/src/main/res/drawable/ic_warning_small.xml | 14 -------------- .../layout/fragment_bootstrap_setup_recovery.xml | 2 +- .../layout/item_bottom_sheet_message_status.xml | 2 +- vector/src/main/res/layout/item_room.xml | 2 +- .../res/layout/item_timeline_event_base_state.xml | 2 +- .../item_timeline_event_media_message_stub.xml | 2 +- 8 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_warning_badge.xml delete mode 100644 vector/src/main/res/drawable/ic_warning_small.xml diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index e77d9ec73f..e063016be1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -69,7 +69,7 @@ class MessageActionsEpoxyController @Inject constructor( id("send_state") showProgress(false) text(stringProvider.getString(R.string.unable_to_send_message)) - drawableStart(R.drawable.ic_warning_small) + drawableStart(R.drawable.ic_warning_badge) } } diff --git a/vector/src/main/res/drawable/ic_warning_badge.xml b/vector/src/main/res/drawable/ic_warning_badge.xml new file mode 100644 index 0000000000..66c2422b4a --- /dev/null +++ b/vector/src/main/res/drawable/ic_warning_badge.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_warning_small.xml b/vector/src/main/res/drawable/ic_warning_small.xml deleted file mode 100644 index 456491ec82..0000000000 --- a/vector/src/main/res/drawable/ic_warning_small.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml b/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml index ffe1e4680c..60d20942e1 100644 --- a/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml +++ b/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml @@ -83,7 +83,7 @@ android:layout_marginEnd="16dp" android:text="@string/reset_secure_backup_warning" android:textColor="@color/riotx_destructive_accent" - android:drawableStart="@drawable/ic_warning_small" + android:drawableStart="@drawable/ic_warning_badge" android:drawablePadding="4dp" android:textSize="14sp" /> diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_status.xml b/vector/src/main/res/layout/item_bottom_sheet_message_status.xml index 10c129cf58..742fa69a7b 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_message_status.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_message_status.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_weight="1" - android:drawableStart="@drawable/ic_warning_small" + android:drawableStart="@drawable/ic_warning_badge" android:drawablePadding="4dp" android:textColor="?riotx_text_secondary" android:textStyle="bold" diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index cf148043ec..9387f9ba75 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -55,7 +55,7 @@ app:layout_constraintCircleAngle="45" app:layout_constraintCircleRadius="30dp" tools:ignore="MissingConstraints" - android:src="@drawable/ic_warning_small" + android:src="@drawable/ic_warning_badge" />