Rework a bit MatrixClientHolder and reintroduce cacheIndex...

This commit is contained in:
ganfra 2023-07-17 18:34:50 +02:00
parent abe7e952a3
commit 2b679710d2
9 changed files with 255 additions and 172 deletions

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api
import io.element.android.libraries.matrix.api.core.SessionId
interface MatrixClientProvider {
/**
* Can be used to get or restore a MatrixClient with the given [SessionId].
* If a [MatrixClient] is already in memory, it'll return it. Otherwise it'll try to restore one.
* Most of the time you want to use injected constructor instead of retrieving a MatrixClient with this provider.
*/
suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient>
}

View file

@ -1,106 +0,0 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.ui.di
import com.bumble.appyx.core.state.MutableSavedStateMap
import com.bumble.appyx.core.state.SavedStateMap
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import kotlin.jvm.Throws
private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHolder.SaveInstanceKey"
@SingleIn(AppScope::class)
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) {
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
private val restoreMutex = Mutex()
private fun add(matrixClient: MatrixClient) {
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
}
fun removeAll() {
sessionIdsToMatrixClient.clear()
}
fun remove(sessionId: SessionId) {
sessionIdsToMatrixClient.remove(sessionId)
}
fun isEmpty(): Boolean = sessionIdsToMatrixClient.isEmpty()
fun knowSession(sessionId: SessionId): Boolean = sessionIdsToMatrixClient.containsKey(sessionId)
fun getOrNull(sessionId: SessionId): MatrixClient? {
return sessionIdsToMatrixClient[sessionId]
}
@Throws(AuthenticationException::class)
suspend fun requireSession(sessionId: SessionId): MatrixClient {
return restoreMutex.withLock {
when (val matrixClient = sessionIdsToMatrixClient[sessionId]) {
null -> restore(sessionId).getOrThrow()
else -> matrixClient
}
}
}
private suspend fun restore(sessionId: SessionId): Result<MatrixClient> {
Timber.d("Restore matrix session: $sessionId")
return authenticationService.restoreSession(sessionId)
.onSuccess { matrixClient ->
add(matrixClient)
}
.onFailure {
Timber.e("Fail to restore session")
}
}
@Suppress("UNCHECKED_CAST")
fun restore(state: SavedStateMap?) {
Timber.d("Restore state")
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also {
Timber.w("Restore with non-empty map")
}
val sessionIds = state[SAVE_INSTANCE_KEY] as? Array<SessionId>
Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}")
if (sessionIds.isNullOrEmpty()) return
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs.
runBlocking {
sessionIds.forEach { sessionId ->
restore(sessionId)
}
}
}
fun save(state: MutableSavedStateMap) {
val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray()
Timber.d("Save matrix session keys = ${sessionKeys.map { it.value }}")
state[SAVE_INSTANCE_KEY] = sessionKeys
}
}

View file

@ -16,7 +16,6 @@
package io.element.android.libraries.push.impl.notifications
import io.element.android.libraries.matrix.ui.di.MatrixClientsHolder
import io.element.android.libraries.androidutils.throttler.FirstThrottler
import io.element.android.libraries.core.cache.CircularCache
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@ -24,12 +23,12 @@ import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.libraries.push.api.store.PushDataStore
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
@ -60,8 +59,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
private val coroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers,
private val buildMeta: BuildMeta,
private val matrixAuthenticationService: MatrixAuthenticationService,
private val matrixClientsHolder: MatrixClientsHolder,
private val matrixClientProvider: MatrixClientProvider,
) : NotificationDrawerManager {
/**
* Lazily initializes the NotificationState as we rely on having a current session in order to fetch the persisted queue of events.
@ -257,8 +255,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
val currentUser = tryOrNull(
onError = { Timber.e(it, "Unable to retrieve info for user ${sessionId.value}") },
operation = {
val client = matrixClientsHolder.requireSession(sessionId)
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()

View file

@ -16,10 +16,8 @@
package io.element.android.libraries.push.impl.notifications
import io.element.android.libraries.matrix.ui.di.MatrixClientsHolder
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
@ -36,6 +34,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessage
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.log.pushLoggerTag
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
@ -60,26 +59,25 @@ class NotifiableEventResolver @Inject constructor(
private val stringProvider: StringProvider,
// private val noticeEventFormatter: NoticeEventFormatter,
// private val displayableEventFormatter: DisplayableEventFormatter,
private val matrixAuthenticationService: MatrixAuthenticationService,
private val buildMeta: BuildMeta,
private val clock: SystemClock,
private val matrixClientsHolder: MatrixClientsHolder,
private val matrixClientProvider: MatrixClientProvider,
) {
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
// Restore session
val client = matrixClientsHolder.requireSession(sessionId)
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
val notificationService = client.notificationService()
val notificationData = notificationService.getNotification(
userId = sessionId,
roomId = roomId,
eventId = eventId,
userId = sessionId,
roomId = roomId,
eventId = eventId,
// FIXME should be true in the future, but right now it's broken
// (https://github.com/vector-im/element-x-android/issues/640#issuecomment-1612913658)
filterByPushRules = false,
).onFailure {
Timber.tag(loggerTag.value).e(it, "Unable to resolve event: $eventId.")
}.getOrNull()
filterByPushRules = false,
).onFailure {
Timber.tag(loggerTag.value).e(it, "Unable to resolve event: $eventId.")
}.getOrNull()
// TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event
return notificationData?.asNotifiableEvent(sessionId)