From ca080fd6af312cade9b33b10e8176c9997f7b4ba Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 22 Jun 2023 16:47:32 +0200 Subject: [PATCH] RoomList: introduces a SyncService --- .../android/appnav/LoggedInFlowNode.kt | 5 +- .../libraries/matrix/api/MatrixClient.kt | 5 +- .../libraries/matrix/api/sync/SyncService.kt | 36 ++++++++++++ .../libraries/matrix/api/sync/SyncState.kt | 24 ++++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 37 +++++------- .../matrix/impl/room/RoomListExtensions.kt | 3 +- .../matrix/impl/sync/RoomListStateMapper.kt | 30 ++++++++++ .../matrix/impl/sync/RustSyncService.kt | 58 +++++++++++++++++++ .../libraries/matrix/test/FakeMatrixClient.kt | 9 +-- .../matrix/test/sync/FakeSyncService.kt | 43 ++++++++++++++ 10 files changed, 214 insertions(+), 36 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 97e2e8b7a2..83465bfcd4 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -127,6 +127,7 @@ class LoggedInFlowNode @AssistedInject constructor( ) : NodeInputs private val inputs: Inputs = inputs() + private val syncService = inputs.matrixClient.syncService() private val loggedInFlowProcessor = LoggedInEventProcessor( snackbarDispatcher, inputs.matrixClient.roomMembershipObserver(), @@ -147,10 +148,10 @@ class LoggedInFlowNode @AssistedInject constructor( loggedInFlowProcessor.observeEvents(coroutineScope) }, onResume = { - inputs.matrixClient.startSync() + syncService.startSync() }, onPause = { - inputs.matrixClient.stopSync() + syncService.stopSync() }, onDestroy = { val imageLoaderFactory = bindings().notLoggedInImageLoaderFactory() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 485049585c..93f21c226e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -44,8 +45,7 @@ interface MatrixClient : Closeable { suspend fun createDM(userId: UserId): Result suspend fun getProfile(userId: UserId): Result suspend fun searchUsers(searchTerm: String, limit: Long): Result - fun startSync() - fun stopSync() + fun syncService(): SyncService fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService fun notificationService(): NotificationService @@ -53,6 +53,5 @@ interface MatrixClient : Closeable { suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result - fun onSlidingSyncUpdate() fun roomMembershipObserver(): RoomMembershipObserver } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt new file mode 100644 index 0000000000..616f4fd859 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -0,0 +1,36 @@ +/* + * 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.sync + +import kotlinx.coroutines.flow.StateFlow + +interface SyncService { + /** + * Tries to start the sync. If already syncing it has no effect. + */ + fun startSync() + + /** + * Tries to stop the sync. If service is not syncing it has no effect. + */ + fun stopSync() + + /** + * + */ + fun syncState(): StateFlow +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt new file mode 100644 index 0000000000..596c131420 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt @@ -0,0 +1,24 @@ +/* + * 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.sync + +enum class SyncState { + Idle, + Syncing, + InError, + Terminated, +} 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 17b36f49a9..0c7f9c75de 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 @@ -31,6 +31,8 @@ import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +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 import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -41,7 +43,7 @@ import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.room.RustMatrixRoom import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.room.roomOrNull -import io.element.android.libraries.matrix.impl.room.stateFlow +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 import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService @@ -49,7 +51,6 @@ import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -78,8 +79,10 @@ class RustMatrixClient constructor( override val sessionId: UserId = UserId(client.userId()) + private val roomListService = client.roomList() private val sessionCoroutineScope = childScopeOf(appCoroutineScope, dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() + private val syncService = RustSyncService(roomListService, sessionCoroutineScope) private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, @@ -93,8 +96,6 @@ class RustMatrixClient constructor( } } - private val roomListService = client.roomList() - private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = RustRoomSummaryDataSource( roomListService = roomListService, @@ -113,12 +114,13 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) + syncService.syncState() + .onEach { syncState -> + if (syncState == SyncState.Syncing) { + onSlidingSyncUpdate() + } + }.launchIn(sessionCoroutineScope) rustRoomSummaryDataSource.init() - roomListService.stateFlow() - .onEach { - Timber.v("onRoomList state change: $it") - } - .launchIn(sessionCoroutineScope) } override fun getRoom(roomId: RoomId): MatrixRoom? { @@ -208,26 +210,15 @@ class RustMatrixClient constructor( } } + override fun syncService(): SyncService = syncService + override fun sessionVerificationService(): SessionVerificationService = verificationService override fun pushersService(): PushersService = pushersService override fun notificationService(): NotificationService = notificationService - override fun startSync() { - if (!roomListService.isSyncing()) { - roomListService.sync() - } - } - - override fun stopSync() { - if (roomListService.isSyncing()) { - roomListService.stopSync() - } - } - override fun close() { - stopSync() sessionCoroutineScope.cancel() client.setDelegate(null) verificationService.destroy() @@ -265,7 +256,7 @@ class RustMatrixClient constructor( } } - override fun onSlidingSyncUpdate() { + private fun onSlidingSyncUpdate() { if (!verificationService.isReady.value) { try { verificationService.verificationController = client.getSessionVerificationController() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt index c45d8967b4..2da52b9e29 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt @@ -3,7 +3,6 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow -import org.matrix.rustcomponents.sdk.RoomList import org.matrix.rustcomponents.sdk.RoomListEntriesListener import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate import org.matrix.rustcomponents.sdk.RoomListEntry @@ -15,7 +14,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver import timber.log.Timber -fun RoomListInterface.stateFlow(): Flow = +fun RoomListInterface.roomListStateFlow(): Flow = mxCallbackFlow { val listener = object : RoomListStateListener { override fun onUpdate(state: RoomListState) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt new file mode 100644 index 0000000000..b59dfce529 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt @@ -0,0 +1,30 @@ +/* + * 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.impl.sync + +import io.element.android.libraries.matrix.api.sync.SyncState +import org.matrix.rustcomponents.sdk.RoomListState + +internal fun RoomListState.toSyncState(): SyncState { + return when (this) { + RoomListState.INIT, + RoomListState.SETTING_UP -> SyncState.Idle + RoomListState.RUNNING -> SyncState.Syncing + RoomListState.ERROR -> SyncState.InError + RoomListState.TERMINATED -> SyncState.Terminated + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt new file mode 100644 index 0000000000..97e3f4c865 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -0,0 +1,58 @@ +/* + * 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.impl.sync + +import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.sync.SyncState +import io.element.android.libraries.matrix.impl.room.roomListStateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import org.matrix.rustcomponents.sdk.RoomList +import org.matrix.rustcomponents.sdk.RoomListState +import timber.log.Timber + +class RustSyncService( + private val roomListService: RoomList, + private val sessionCoroutineScope: CoroutineScope +) : SyncService { + + override fun startSync() { + if (!roomListService.isSyncing()) { + roomListService.sync() + } + } + + override fun stopSync() { + if (roomListService.isSyncing()) { + roomListService.stopSync() + } + } + + override fun syncState(): StateFlow { + return roomListService + .roomListStateFlow() + .map(RoomListState::toSyncState) + .onEach { syncState -> + Timber.d("OnSyncState updated = $syncState") + } + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 8b38a74457..58bc6c1ff1 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.test.notification.FakeNotificationSer import io.element.android.libraries.matrix.test.pushers.FakePushersService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource +import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import kotlinx.coroutines.delay @@ -44,11 +45,11 @@ class FakeMatrixClient( private val userDisplayName: Result = Result.success(A_USER_NAME), private val userAvatarURLString: Result = Result.success(AN_AVATAR_URL), override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(), - override val invitesDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(), override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(), private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), private val pushersService: FakePushersService = FakePushersService(), private val notificationService: FakeNotificationService = FakeNotificationService(), + private val syncService: FakeSyncService = FakeSyncService(), ) : MatrixClient { private var ignoreUserResult: Result = Result.success(Unit) @@ -98,9 +99,7 @@ class FakeMatrixClient( return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm")) } - override fun startSync() = Unit - - override fun stopSync() = Unit + override fun syncService() = syncService override suspend fun logout() { delay(100) @@ -131,8 +130,6 @@ class FakeMatrixClient( override fun notificationService(): NotificationService = notificationService - override fun onSlidingSyncUpdate() {} - override fun roomMembershipObserver(): RoomMembershipObserver { return RoomMembershipObserver() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt new file mode 100644 index 0000000000..04837efc90 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -0,0 +1,43 @@ +/* + * 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.test.sync + +import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.sync.SyncState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeSyncService : SyncService { + + private val syncState = MutableStateFlow(SyncState.Idle) + + fun simulateError() { + syncState.value = SyncState.InError + } + + override fun startSync() { + syncState.value = SyncState.Syncing + } + + override fun stopSync() { + syncState.value = SyncState.Terminated + } + + override fun syncState(): StateFlow { + return syncState + } +}