diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 2dd28a1ae0..4319077946 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -16,7 +16,6 @@ package io.element.android.libraries.matrix.impl -import io.element.android.appconfig.TimelineConfig import io.element.android.libraries.androidutils.file.getSizeOfFiles import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -42,7 +41,6 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.preview.RoomPreview import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService -import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults @@ -56,17 +54,12 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService import io.element.android.libraries.matrix.impl.oidc.toRustAction import io.element.android.libraries.matrix.impl.pushers.RustPushersService -import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper import io.element.android.libraries.matrix.impl.room.RoomContentForwarder -import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber -import io.element.android.libraries.matrix.impl.room.RustMatrixRoom -import io.element.android.libraries.matrix.impl.room.map +import io.element.android.libraries.matrix.impl.room.RustRoomFactory import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewMapper import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService -import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline -import io.element.android.libraries.matrix.impl.roomlist.roomOrNull import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper @@ -102,14 +95,10 @@ import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.BackupState import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate -import org.matrix.rustcomponents.sdk.FilterTimelineEventType import org.matrix.rustcomponents.sdk.IgnoredUsersListener import org.matrix.rustcomponents.sdk.NotificationProcessSetup import org.matrix.rustcomponents.sdk.PowerLevels -import org.matrix.rustcomponents.sdk.Room -import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.TaskHandle -import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File @@ -150,7 +139,6 @@ class RustMatrixClient( private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock) private val notificationSettingsService = RustNotificationSettingsService(client, dispatchers) .apply { start() } - private val roomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers) private val encryptionService = RustEncryptionService( client = client, syncService = rustSyncService, @@ -237,15 +225,18 @@ class RustMatrixClient( sessionCoroutineScope = sessionCoroutineScope, ) - private val eventFilters = TimelineConfig.excludedEvents - .takeIf { it.isNotEmpty() } - ?.let { listStateEventType -> - TimelineEventTypeFilter.exclude( - listStateEventType.map { stateEventType -> - FilterTimelineEventType.State(stateEventType.map()) - } - ) - } + private val roomFactory = RustRoomFactory( + roomListService = roomListService, + innerRoomListService = innerRoomListService, + sessionId = sessionId, + notificationSettingsService = notificationSettingsService, + sessionCoroutineScope = sessionCoroutineScope, + dispatchers = dispatchers, + systemClock = clock, + roomContentForwarder = RoomContentForwarder(innerRoomListService), + isKeyBackupEnabled = { client.encryption().use { it.backupState() == BackupState.ENABLED } }, + getSessionData = { sessionStore.getSession(sessionId.value)!! }, + ) override val mediaLoader: MatrixMediaLoader = RustMediaLoader( baseCacheDirectory = baseCacheDirectory, @@ -255,8 +246,6 @@ class RustMatrixClient( private val roomMembershipObserver = RoomMembershipObserver() - private val roomContentForwarder = RoomContentForwarder(innerRoomListService) - private val clientDelegateTaskHandle: TaskHandle? = client.setDelegate(clientDelegate) private val _userProfile: MutableStateFlow = MutableStateFlow( @@ -287,31 +276,8 @@ class RustMatrixClient( } } - override suspend fun getRoom(roomId: RoomId): MatrixRoom? = withContext(sessionDispatcher) { - // Check if already in memory... - var cachedPairOfRoom = pairOfRoom(roomId) - if (cachedPairOfRoom == null) { - // ... otherwise, lets wait for the SS to load all rooms and check again. - roomListService.allRooms.awaitLoaded() - cachedPairOfRoom = pairOfRoom(roomId) - } - cachedPairOfRoom?.let { (roomListItem, fullRoom) -> - RustMatrixRoom( - sessionId = sessionId, - isKeyBackupEnabled = client.encryption().backupState() == BackupState.ENABLED, - roomListItem = roomListItem, - innerRoom = fullRoom, - innerTimeline = fullRoom.timeline(), - roomNotificationSettingsService = notificationSettingsService, - sessionCoroutineScope = sessionCoroutineScope, - coroutineDispatchers = dispatchers, - systemClock = clock, - roomContentForwarder = roomContentForwarder, - sessionData = sessionStore.getSession(sessionId.value)!!, - roomSyncSubscriber = roomSyncSubscriber, - matrixRoomInfoMapper = MatrixRoomInfoMapper(), - ) - } + override suspend fun getRoom(roomId: RoomId): MatrixRoom? { + return roomFactory.create(roomId) } /** @@ -330,18 +296,6 @@ class RustMatrixClient( } } - private suspend fun pairOfRoom(roomId: RoomId): Pair? { - val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value) - val fullRoom = cachedRoomListItem?.fullRoomWithTimeline(filter = eventFilters) - return if (cachedRoomListItem == null || fullRoom == null) { - Timber.d("No room cached for $roomId") - null - } else { - Timber.d("Found room cached for $roomId") - Pair(cachedRoomListItem, fullRoom) - } - } - override suspend fun findDM(userId: UserId): RoomId? { return client.getDmRoom(userId.value)?.use { RoomId(it.id()) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 8778fa6f4f..2827ad366c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo @@ -48,7 +49,6 @@ import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings -import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper @@ -96,7 +96,7 @@ class RustMatrixRoom( private val roomListItem: RoomListItem, private val innerRoom: InnerRoom, innerTimeline: InnerTimeline, - private val roomNotificationSettingsService: RustNotificationSettingsService, + private val notificationSettingsService: NotificationSettingsService, sessionCoroutineScope: CoroutineScope, private val coroutineDispatchers: CoroutineDispatchers, private val systemClock: SystemClock, @@ -262,7 +262,7 @@ class RustMatrixRoom( val currentRoomNotificationSettings = currentState.roomNotificationSettings() _roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings) runCatching { - roomNotificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() + notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() }.map { _roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(it) }.onFailure { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt new file mode 100644 index 0000000000..19d9401a27 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024 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.impl.room + +import io.element.android.appconfig.TimelineConfig +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +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.notificationsettings.NotificationSettingsService +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.awaitLoaded +import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline +import io.element.android.libraries.matrix.impl.roomlist.roomOrNull +import io.element.android.libraries.sessionstorage.api.SessionData +import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.FilterTimelineEventType +import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.RoomListException +import org.matrix.rustcomponents.sdk.RoomListItem +import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter +import timber.log.Timber +import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService + +class RustRoomFactory( + private val sessionId: SessionId, + private val notificationSettingsService: NotificationSettingsService, + private val sessionCoroutineScope: CoroutineScope, + private val dispatchers: CoroutineDispatchers, + private val systemClock: SystemClock, + private val roomContentForwarder: RoomContentForwarder, + private val roomListService: RoomListService, + private val innerRoomListService: InnerRoomListService, + private val isKeyBackupEnabled: suspend () -> Boolean, + private val getSessionData: suspend () -> SessionData, +) { + @OptIn(ExperimentalCoroutinesApi::class) + private val createRoomDispatcher = dispatchers.io.limitedParallelism(1) + private val mutex = Mutex() + + private val matrixRoomInfoMapper = MatrixRoomInfoMapper() + + private val roomSyncSubscriber: RoomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers) + + private val eventFilters = TimelineConfig.excludedEvents + .takeIf { it.isNotEmpty() } + ?.let { listStateEventType -> + TimelineEventTypeFilter.exclude( + listStateEventType.map { stateEventType -> + FilterTimelineEventType.State(stateEventType.map()) + } + ) + } + + suspend fun create(roomId: RoomId): MatrixRoom? = withContext(createRoomDispatcher) { + var cachedPairOfRoom: Pair? + mutex.withLock { + // Check if already in memory... + cachedPairOfRoom = pairOfRoom(roomId) + if (cachedPairOfRoom == null) { + // ... otherwise, lets wait for the SS to load all rooms and check again. + roomListService.allRooms.awaitLoaded() + cachedPairOfRoom = pairOfRoom(roomId) + } + } + if (cachedPairOfRoom == null) { + Timber.d("No room found for $roomId") + return@withContext null + } + cachedPairOfRoom?.let { (roomListItem, fullRoom) -> + RustMatrixRoom( + sessionId = sessionId, + isKeyBackupEnabled = isKeyBackupEnabled(), + roomListItem = roomListItem, + innerRoom = fullRoom, + innerTimeline = fullRoom.timeline(), + notificationSettingsService = notificationSettingsService, + sessionCoroutineScope = sessionCoroutineScope, + coroutineDispatchers = dispatchers, + systemClock = systemClock, + roomContentForwarder = roomContentForwarder, + sessionData = getSessionData(), + roomSyncSubscriber = roomSyncSubscriber, + matrixRoomInfoMapper = matrixRoomInfoMapper, + ) + } + } + + private suspend fun pairOfRoom(roomId: RoomId): Pair? { + val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value) + val fullRoom = try { + cachedRoomListItem?.fullRoomWithTimeline(filter = eventFilters) + } catch (e: RoomListException) { + Timber.e(e, "Failed to get full room with timeline for $roomId") + null + } + return if (cachedRoomListItem == null || fullRoom == null) { + Timber.d("No room cached for $roomId") + null + } else { + Timber.d("Found room cached for $roomId") + Pair(cachedRoomListItem, fullRoom) + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index 8094f0bee7..d987e332c8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.onEach import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind import org.matrix.rustcomponents.sdk.RoomListEntriesListener import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate +import org.matrix.rustcomponents.sdk.RoomListException import org.matrix.rustcomponents.sdk.RoomListInterface import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListLoadingState @@ -129,7 +130,7 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow