From 5aeca1f81a22abf96af0a96574e05029a31df545 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:48:28 +0100 Subject: [PATCH] saving sync filter changed (#7627) --- changelog.d/7626.sdk | 2 + .../android/sdk/common/CommonTestHelper.kt | 5 + .../sdk/api/session/sync/FilterService.kt | 13 +- .../session/sync/filter/SyncFilterBuilder.kt | 129 ++++++++++++++++++ .../database/RealmSessionStoreMigration.kt | 4 +- .../database/mapper/FilterParamsMapper.kt | 61 +++++++++ .../database/migration/MigrateSessionTo043.kt | 1 - .../database/migration/MigrateSessionTo045.kt | 38 ++++++ .../database/model/SessionRealmModule.kt | 3 +- .../database/model/SyncFilterParamsEntity.kt | 36 +++++ .../session/filter/DefaultFilterRepository.kt | 87 ++++++------ .../session/filter/DefaultFilterService.kt | 22 ++- .../internal/session/filter/FilterFactory.kt | 39 ------ .../internal/session/filter/FilterModule.kt | 3 + .../session/filter/FilterRepository.kt | 31 ++++- .../session/filter/GetCurrentFilterTask.kt | 55 ++++++++ .../internal/session/filter/SaveFilterTask.kt | 43 ++---- .../timeline/FetchTokenAndPaginateTask.kt | 2 +- .../room/timeline/GetContextOfEventTask.kt | 2 +- .../session/room/timeline/PaginationTask.kt | 2 +- .../sdk/internal/session/sync/SyncTask.kt | 15 +- .../internal/sync/filter/SyncFilterParams.kt | 25 ++++ .../sync/DefaultGetCurrentFilterTaskTest.kt | 100 ++++++++++++++ .../sdk/test/fakes/FakeFilterRepository.kt | 34 +++++ .../FakeHomeServerCapabilitiesDataSource.kt | 30 ++++ .../sdk/test/fakes/FakeSaveFilterTask.kt | 40 ++++++ .../ConfigureAndStartSessionUseCase.kt | 6 +- .../im/vector/app/features/sync/SyncUtils.kt | 48 +++++++ .../ConfigureAndStartSessionUseCaseTest.kt | 8 +- .../app/test/fakes/FakeFilterService.kt | 11 +- 30 files changed, 736 insertions(+), 159 deletions(-) create mode 100644 changelog.d/7626.sdk create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt create mode 100644 vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt diff --git a/changelog.d/7626.sdk b/changelog.d/7626.sdk new file mode 100644 index 0000000000..4d9f28183a --- /dev/null +++ b/changelog.d/7626.sdk @@ -0,0 +1,2 @@ +Sync Filter now taking in account homeserver capabilities to not pass unsupported parameters. +Sync Filter is now configured by providing SyncFilterBuilder class instance, instead of Filter to identify Filter changes related to homeserver capabilities diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index eeb2def582..8edecb273d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder import timber.log.Timber import java.util.UUID import java.util.concurrent.CountDownLatch @@ -346,6 +347,10 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: assertTrue(registrationResult is RegistrationResult.Success) val session = (registrationResult as RegistrationResult.Success).session session.open() + session.filterService().setSyncFilter( + SyncFilterBuilder() + .lazyLoadMembersForStateEvents(true) + ) if (sessionTestParams.withInitialSync) { syncSession(session, 120_000) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt index bc592df474..7347bee165 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt @@ -16,19 +16,12 @@ package org.matrix.android.sdk.api.session.sync +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder + interface FilterService { - enum class FilterPreset { - NoFilter, - - /** - * Filter for Element, will include only known event type. - */ - ElementFilter - } - /** * Configure the filter for the sync. */ - fun setFilter(filterPreset: FilterPreset) + suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt new file mode 100644 index 0000000000..ad55b26dfd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.sync.filter + +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.internal.session.filter.Filter +import org.matrix.android.sdk.internal.session.filter.RoomEventFilter +import org.matrix.android.sdk.internal.session.filter.RoomFilter +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams + +class SyncFilterBuilder { + private var lazyLoadMembersForStateEvents: Boolean? = null + private var lazyLoadMembersForMessageEvents: Boolean? = null + private var useThreadNotifications: Boolean? = null + private var listOfSupportedEventTypes: List? = null + private var listOfSupportedStateEventTypes: List? = null + + fun lazyLoadMembersForStateEvents(lazyLoadMembersForStateEvents: Boolean) = apply { this.lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents } + + fun lazyLoadMembersForMessageEvents(lazyLoadMembersForMessageEvents: Boolean) = + apply { this.lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents } + + fun useThreadNotifications(useThreadNotifications: Boolean) = + apply { this.useThreadNotifications = useThreadNotifications } + + fun listOfSupportedStateEventTypes(listOfSupportedStateEventTypes: List) = + apply { this.listOfSupportedStateEventTypes = listOfSupportedStateEventTypes } + + fun listOfSupportedTimelineEventTypes(listOfSupportedEventTypes: List) = + apply { this.listOfSupportedEventTypes = listOfSupportedEventTypes } + + internal fun with(currentFilterParams: SyncFilterParams?) = + apply { + currentFilterParams?.let { + useThreadNotifications = currentFilterParams.useThreadNotifications + lazyLoadMembersForMessageEvents = currentFilterParams.lazyLoadMembersForMessageEvents + lazyLoadMembersForStateEvents = currentFilterParams.lazyLoadMembersForStateEvents + listOfSupportedEventTypes = currentFilterParams.listOfSupportedEventTypes?.toList() + listOfSupportedStateEventTypes = currentFilterParams.listOfSupportedStateEventTypes?.toList() + } + } + + internal fun extractParams(): SyncFilterParams { + return SyncFilterParams( + useThreadNotifications = useThreadNotifications, + lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents, + lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents, + listOfSupportedEventTypes = listOfSupportedEventTypes, + listOfSupportedStateEventTypes = listOfSupportedStateEventTypes, + ) + } + + internal fun build(homeServerCapabilities: HomeServerCapabilities): Filter { + return Filter( + room = buildRoomFilter(homeServerCapabilities) + ) + } + + private fun buildRoomFilter(homeServerCapabilities: HomeServerCapabilities): RoomFilter { + return RoomFilter( + timeline = buildTimelineFilter(homeServerCapabilities), + state = buildStateFilter() + ) + } + + private fun buildTimelineFilter(homeServerCapabilities: HomeServerCapabilities): RoomEventFilter? { + val resolvedUseThreadNotifications = if (homeServerCapabilities.canUseThreadReadReceiptsAndNotifications) { + useThreadNotifications + } else { + null + } + return RoomEventFilter( + enableUnreadThreadNotifications = resolvedUseThreadNotifications, + lazyLoadMembers = lazyLoadMembersForMessageEvents + ).orNullIfEmpty() + } + + private fun buildStateFilter(): RoomEventFilter? = + RoomEventFilter( + lazyLoadMembers = lazyLoadMembersForStateEvents, + types = listOfSupportedStateEventTypes + ).orNullIfEmpty() + + private fun RoomEventFilter.orNullIfEmpty(): RoomEventFilter? { + return if (hasData()) { + this + } else { + null + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SyncFilterBuilder + + if (lazyLoadMembersForStateEvents != other.lazyLoadMembersForStateEvents) return false + if (lazyLoadMembersForMessageEvents != other.lazyLoadMembersForMessageEvents) return false + if (useThreadNotifications != other.useThreadNotifications) return false + if (listOfSupportedEventTypes != other.listOfSupportedEventTypes) return false + if (listOfSupportedStateEventTypes != other.listOfSupportedStateEventTypes) return false + + return true + } + + override fun hashCode(): Int { + var result = lazyLoadMembersForStateEvents?.hashCode() ?: 0 + result = 31 * result + (lazyLoadMembersForMessageEvents?.hashCode() ?: 0) + result = 31 * result + (useThreadNotifications?.hashCode() ?: 0) + result = 31 * result + (listOfSupportedEventTypes?.hashCode() ?: 0) + result = 31 * result + (listOfSupportedStateEventTypes?.hashCode() ?: 0) + return result + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 1529064b96..2fb87ca874 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -61,6 +61,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -69,7 +70,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 44L, + schemaVersion = 45L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -123,5 +124,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 42) MigrateSessionTo042(realm).perform() if (oldVersion < 43) MigrateSessionTo043(realm).perform() if (oldVersion < 44) MigrateSessionTo044(realm).perform() + if (oldVersion < 45) MigrateSessionTo045(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt new file mode 100644 index 0000000000..645cb41af5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.mapper + +import io.realm.RealmList +import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams +import javax.inject.Inject + +internal class FilterParamsMapper @Inject constructor() { + + fun map(entity: SyncFilterParamsEntity): SyncFilterParams { + val eventTypes = if (entity.listOfSupportedEventTypesHasBeenSet) { + entity.listOfSupportedEventTypes?.toList() + } else { + null + } + val stateEventTypes = if (entity.listOfSupportedStateEventTypesHasBeenSet) { + entity.listOfSupportedStateEventTypes?.toList() + } else { + null + } + return SyncFilterParams( + useThreadNotifications = entity.useThreadNotifications, + lazyLoadMembersForMessageEvents = entity.lazyLoadMembersForMessageEvents, + lazyLoadMembersForStateEvents = entity.lazyLoadMembersForStateEvents, + listOfSupportedEventTypes = eventTypes, + listOfSupportedStateEventTypes = stateEventTypes, + ) + } + + fun map(params: SyncFilterParams): SyncFilterParamsEntity { + return SyncFilterParamsEntity( + useThreadNotifications = params.useThreadNotifications, + lazyLoadMembersForMessageEvents = params.lazyLoadMembersForMessageEvents, + lazyLoadMembersForStateEvents = params.lazyLoadMembersForStateEvents, + listOfSupportedEventTypes = params.listOfSupportedEventTypes.toRealmList(), + listOfSupportedEventTypesHasBeenSet = params.listOfSupportedEventTypes != null, + listOfSupportedStateEventTypes = params.listOfSupportedStateEventTypes.toRealmList(), + listOfSupportedStateEventTypesHasBeenSet = params.listOfSupportedStateEventTypes != null, + ) + } + + private fun List?.toRealmList(): RealmList? { + return this?.toTypedArray()?.let { RealmList(*it) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt index c7fda61671..49e9bac18c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.EditionOfEventFields import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.util.database.RealmMigrator internal class MigrateSessionTo043(realm: DynamicRealm) : RealmMigrator(realm, 43) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt new file mode 100644 index 0000000000..d2b43ded28 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo045(realm: DynamicRealm) : RealmMigrator(realm, 45) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.create("SyncFilterParamsEntity") + .addField(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_STATE_EVENTS, Boolean::class.java) + .setNullable(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_STATE_EVENTS, true) + .addField(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_MESSAGE_EVENTS, Boolean::class.java) + .setNullable(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_MESSAGE_EVENTS, true) + .addField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_EVENT_TYPES_HAS_BEEN_SET, Boolean::class.java) + .addField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_STATE_EVENT_TYPES_HAS_BEEN_SET, Boolean::class.java) + .addField(SyncFilterParamsEntityFields.USE_THREAD_NOTIFICATIONS, Boolean::class.java) + .setNullable(SyncFilterParamsEntityFields.USE_THREAD_NOTIFICATIONS, true) + .addRealmListField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_EVENT_TYPES.`$`, String::class.java) + .addRealmListField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_STATE_EVENT_TYPES.`$`, String::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index b222bcb710..93ff67a911 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -70,7 +70,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit SpaceChildSummaryEntity::class, SpaceParentSummaryEntity::class, UserPresenceEntity::class, - ThreadSummaryEntity::class + ThreadSummaryEntity::class, + SyncFilterParamsEntity::class, ] ) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt new file mode 100644 index 0000000000..e4b62f28e8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.model + +import io.realm.RealmList +import io.realm.RealmObject + +/** + * This entity stores Sync Filter configuration data, provided by the client. + */ +internal open class SyncFilterParamsEntity( + var lazyLoadMembersForStateEvents: Boolean? = null, + var lazyLoadMembersForMessageEvents: Boolean? = null, + var useThreadNotifications: Boolean? = null, + var listOfSupportedEventTypes: RealmList? = null, + var listOfSupportedEventTypesHasBeenSet: Boolean = false, + var listOfSupportedStateEventTypes: RealmList? = null, + var listOfSupportedStateEventTypesHasBeenSet: Boolean = false, +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt index 1d1bb0e715..4e5b005584 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt @@ -17,74 +17,71 @@ package org.matrix.android.sdk.internal.session.filter import com.zhuinden.monarchy.Monarchy -import io.realm.Realm import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.mapper.FilterParamsMapper import org.matrix.android.sdk.internal.database.model.FilterEntity -import org.matrix.android.sdk.internal.database.model.FilterEntityFields +import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject -internal class DefaultFilterRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : FilterRepository { +internal class DefaultFilterRepository @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val filterParamsMapper: FilterParamsMapper +) : FilterRepository { - override suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - val filterEntity = FilterEntity.get(realm) - // Filter has changed, or no filter Id yet - filterEntity == null || - filterEntity.filterBodyJson != filter.toJSONString() || - filterEntity.filterId.isBlank() - }.also { hasChanged -> - if (hasChanged) { - // Filter is new or has changed, store it and reset the filter Id. - // This has to be done outside of the Realm.use(), because awaitTransaction change the current thread - monarchy.awaitTransaction { realm -> - // We manage only one filter for now - val filterJson = filter.toJSONString() - val roomEventFilterJson = roomEventFilter.toJSONString() - - val filterEntity = FilterEntity.getOrCreate(realm) - - filterEntity.filterBodyJson = filterJson - filterEntity.roomEventFilterJson = roomEventFilterJson - // Reset filterId - filterEntity.filterId = "" - } - } - } - } - - override suspend fun storeFilterId(filter: Filter, filterId: String) { - monarchy.awaitTransaction { + override suspend fun storeSyncFilter(filter: Filter, filterId: String, roomEventFilter: RoomEventFilter) { + monarchy.awaitTransaction { realm -> // We manage only one filter for now val filterJson = filter.toJSONString() + val roomEventFilterJson = roomEventFilter.toJSONString() - // Update the filter id, only if the filter body matches - it.where() - .equalTo(FilterEntityFields.FILTER_BODY_JSON, filterJson) - ?.findFirst() - ?.filterId = filterId + val filterEntity = FilterEntity.getOrCreate(realm) + + filterEntity.filterBodyJson = filterJson + filterEntity.roomEventFilterJson = roomEventFilterJson + filterEntity.filterId = filterId } } - override suspend fun getFilter(): String { + override suspend fun getStoredSyncFilterBody(): String { return monarchy.awaitTransaction { - val filter = FilterEntity.getOrCreate(it) - if (filter.filterId.isBlank()) { - // Use the Json format - filter.filterBodyJson + FilterEntity.getOrCreate(it).filterBodyJson + } + } + + override suspend fun getStoredSyncFilterId(): String? { + return monarchy.awaitTransaction { + val id = FilterEntity.get(it)?.filterId + if (id.isNullOrBlank()) { + null } else { - // Use FilterId - filter.filterId + id } } } - override suspend fun getRoomFilter(): String { + override suspend fun getRoomFilterBody(): String { return monarchy.awaitTransaction { FilterEntity.getOrCreate(it).roomEventFilterJson } } + + override suspend fun getStoredFilterParams(): SyncFilterParams? { + return monarchy.awaitTransaction { realm -> + realm.where().findFirst()?.let { + filterParamsMapper.map(it) + } + } + } + + override suspend fun storeFilterParams(params: SyncFilterParams) { + return monarchy.awaitTransaction { realm -> + val entity = filterParamsMapper.map(params) + realm.insertOrUpdate(entity) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt index 2e68d02d8c..c54e7de07a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt @@ -17,19 +17,27 @@ package org.matrix.android.sdk.internal.session.filter import org.matrix.android.sdk.api.session.sync.FilterService -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource import javax.inject.Inject internal class DefaultFilterService @Inject constructor( private val saveFilterTask: SaveFilterTask, - private val taskExecutor: TaskExecutor + private val filterRepository: FilterRepository, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, ) : FilterService { // TODO Pass a list of support events instead - override fun setFilter(filterPreset: FilterService.FilterPreset) { - saveFilterTask - .configureWith(SaveFilterTask.Params(filterPreset)) - .executeBy(taskExecutor) + override suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder) { + filterRepository.storeFilterParams(filterBuilder.extractParams()) + + // don't upload/store filter until homeserver capabilities are fetched + homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.let { homeServerCapabilities -> + saveFilterTask.execute( + SaveFilterTask.Params( + filter = filterBuilder.build(homeServerCapabilities) + ) + ) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt index e0919c52e3..1bd2e59e59 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt @@ -45,46 +45,7 @@ internal object FilterFactory { return FilterUtil.enableLazyLoading(Filter(), true) } - fun createElementFilter(): Filter { - return Filter( - room = RoomFilter( - timeline = createElementTimelineFilter(), - state = createElementStateFilter() - ) - ) - } - fun createDefaultRoomFilter(): RoomEventFilter { return RoomEventFilter(lazyLoadMembers = true) } - - fun createElementRoomFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true, - // TODO Enable this for optimization - // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() - ) - } - - private fun createElementTimelineFilter(): RoomEventFilter? { -// we need to check if homeserver supports thread notifications before setting this param -// return RoomEventFilter(enableUnreadThreadNotifications = true) - return null - } - - private fun createElementStateFilter(): RoomEventFilter { - return RoomEventFilter(lazyLoadMembers = true) - } - - // Get only managed types by Element - private val listOfSupportedEventTypes = listOf( - // TODO Complete the list - EventType.MESSAGE - ) - - // Get only managed types by Element - private val listOfSupportedStateEventTypes = listOf( - // TODO Complete the list - EventType.STATE_ROOM_MEMBER - ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt index 8531bed1ff..ca9f798fd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt @@ -44,4 +44,7 @@ internal abstract class FilterModule { @Binds abstract fun bindSaveFilterTask(task: DefaultSaveFilterTask): SaveFilterTask + + @Binds + abstract fun bindGetCurrentFilterTask(task: DefaultGetCurrentFilterTask): GetCurrentFilterTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt index f40231c8cf..71d7391e87 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt @@ -16,25 +16,42 @@ package org.matrix.android.sdk.internal.session.filter +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams + +/** + * Repository for request filters. + */ internal interface FilterRepository { /** - * Return true if the filterBody has changed, or need to be sent to the server. + * Stores sync filter and room filter. + * Note: It looks like we could use [Filter.room.timeline] instead of a separate [RoomEventFilter], but it's not clear if it's safe, so research is needed + * @return true if the filterBody has changed, or need to be sent to the server. */ - suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean + suspend fun storeSyncFilter(filter: Filter, filterId: String, roomEventFilter: RoomEventFilter) /** - * Set the filterId of this filter. + * Returns stored sync filter's JSON body if it exists. */ - suspend fun storeFilterId(filter: Filter, filterId: String) + suspend fun getStoredSyncFilterBody(): String? /** - * Return filter json or filter id. + * Returns stored sync filter's ID if it exists. */ - suspend fun getFilter(): String + suspend fun getStoredSyncFilterId(): String? /** * Return the room filter. */ - suspend fun getRoomFilter(): String + suspend fun getRoomFilterBody(): String + + /** + * Returns filter params stored in local storage if it exists. + */ + suspend fun getStoredFilterParams(): SyncFilterParams? + + /** + * Stores filter params to local storage. + */ + suspend fun storeFilterParams(params: SyncFilterParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt new file mode 100644 index 0000000000..e88f286e27 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.filter + +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetCurrentFilterTask : Task + +internal class DefaultGetCurrentFilterTask @Inject constructor( + private val filterRepository: FilterRepository, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, + private val saveFilterTask: SaveFilterTask +) : GetCurrentFilterTask { + + override suspend fun execute(params: Unit): String { + val storedFilterId = filterRepository.getStoredSyncFilterId() + val storedFilterBody = filterRepository.getStoredSyncFilterBody() + val homeServerCapabilities = homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities() + val currentFilter = SyncFilterBuilder() + .with(filterRepository.getStoredFilterParams()) + .build(homeServerCapabilities) + + val currentFilterBody = currentFilter.toJSONString() + + return when (storedFilterBody) { + currentFilterBody -> storedFilterId ?: storedFilterBody + else -> saveFilter(currentFilter) + } + } + + private suspend fun saveFilter(filter: Filter) = saveFilterTask + .execute( + SaveFilterTask.Params( + filter = filter + ) + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index 63afa1bbbc..82d5ff4d2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.filter -import org.matrix.android.sdk.api.session.sync.FilterService import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest @@ -26,10 +25,10 @@ import javax.inject.Inject /** * Save a filter, in db and if any changes, upload to the server. */ -internal interface SaveFilterTask : Task { +internal interface SaveFilterTask : Task { data class Params( - val filterPreset: FilterService.FilterPreset + val filter: Filter ) } @@ -37,33 +36,21 @@ internal class DefaultSaveFilterTask @Inject constructor( @UserId private val userId: String, private val filterAPI: FilterApi, private val filterRepository: FilterRepository, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, ) : SaveFilterTask { - override suspend fun execute(params: SaveFilterTask.Params) { - val filterBody = when (params.filterPreset) { - FilterService.FilterPreset.ElementFilter -> { - FilterFactory.createElementFilter() - } - FilterService.FilterPreset.NoFilter -> { - FilterFactory.createDefaultFilter() - } - } - val roomFilter = when (params.filterPreset) { - FilterService.FilterPreset.ElementFilter -> { - FilterFactory.createElementRoomFilter() - } - FilterService.FilterPreset.NoFilter -> { - FilterFactory.createDefaultRoomFilter() - } - } - val updated = filterRepository.storeFilter(filterBody, roomFilter) - if (updated) { - val filterResponse = executeRequest(globalErrorReceiver) { - // TODO auto retry - filterAPI.uploadFilter(userId, filterBody) - } - filterRepository.storeFilterId(filterBody, filterResponse.filterId) + override suspend fun execute(params: SaveFilterTask.Params): String { + val filter = params.filter + val filterResponse = executeRequest(globalErrorReceiver) { + // TODO auto retry + filterAPI.uploadFilter(userId, filter) } + + filterRepository.storeSyncFilter( + filter = filter, + filterId = filterResponse.filterId, + roomEventFilter = FilterFactory.createDefaultRoomFilter() + ) + return filterResponse.filterId } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt index 96646b42ed..9d8d8ecbf1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt @@ -47,7 +47,7 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor( ) : FetchTokenAndPaginateTask { override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { - val filter = filterRepository.getRoomFilter() + val filter = filterRepository.getRoomFilterBody() val response = executeRequest(globalErrorReceiver) { roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt index 015e55f070..c3911dfa2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt @@ -39,7 +39,7 @@ internal class DefaultGetContextOfEventTask @Inject constructor( ) : GetContextOfEventTask { override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result { - val filter = filterRepository.getRoomFilter() + val filter = filterRepository.getRoomFilterBody() val response = executeRequest(globalErrorReceiver) { // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process. roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt index 8aeccb66c8..1a7b1cdac4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt @@ -41,7 +41,7 @@ internal class DefaultPaginationTask @Inject constructor( ) : PaginationTask { override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { - val filter = filterRepository.getRoomFilter() + val filter = filterRepository.getRoomFilterBody() val chunk = executeRequest( globalErrorReceiver, canRetry = true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index bc1a69769d..8a287fb0b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -36,7 +36,7 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.toFailure import org.matrix.android.sdk.internal.session.SessionListeners import org.matrix.android.sdk.internal.session.dispatchTo -import org.matrix.android.sdk.internal.session.filter.FilterRepository +import org.matrix.android.sdk.internal.session.filter.GetCurrentFilterTask import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.user.UserStore @@ -64,11 +64,9 @@ internal interface SyncTask : Task { internal class DefaultSyncTask @Inject constructor( private val syncAPI: SyncAPI, @UserId private val userId: String, - private val filterRepository: FilterRepository, private val syncResponseHandler: SyncResponseHandler, private val syncRequestStateTracker: SyncRequestStateTracker, private val syncTokenStore: SyncTokenStore, - private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val userStore: UserStore, private val session: Session, private val sessionListeners: SessionListeners, @@ -79,6 +77,8 @@ internal class DefaultSyncTask @Inject constructor( private val syncResponseParser: InitialSyncResponseParser, private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val clock: Clock, + private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, + private val getCurrentFilterTask: GetCurrentFilterTask ) : SyncTask { private val workingDir = File(fileDirectory, "is") @@ -100,8 +100,13 @@ internal class DefaultSyncTask @Inject constructor( requestParams["since"] = token timeout = params.timeout } + + // Maybe refresh the homeserver capabilities data we know + getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) + val filter = getCurrentFilterTask.execute(Unit) + requestParams["timeout"] = timeout.toString() - requestParams["filter"] = filterRepository.getFilter() + requestParams["filter"] = filter params.presence?.let { requestParams["set_presence"] = it.value } val isInitialSync = token == null @@ -115,8 +120,6 @@ internal class DefaultSyncTask @Inject constructor( ) syncRequestStateTracker.startRoot(InitialSyncStep.ImportingAccount, 100) } - // Maybe refresh the homeserver capabilities data we know - getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt new file mode 100644 index 0000000000..a7de7f5579 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.sync.filter + +internal data class SyncFilterParams( + val lazyLoadMembersForStateEvents: Boolean? = null, + val lazyLoadMembersForMessageEvents: Boolean? = null, + val useThreadNotifications: Boolean? = null, + val listOfSupportedEventTypes: List? = null, + val listOfSupportedStateEventTypes: List? = null, +) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt new file mode 100644 index 0000000000..201423685c --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.sync + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder +import org.matrix.android.sdk.internal.session.filter.DefaultGetCurrentFilterTask +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams +import org.matrix.android.sdk.test.fakes.FakeFilterRepository +import org.matrix.android.sdk.test.fakes.FakeHomeServerCapabilitiesDataSource +import org.matrix.android.sdk.test.fakes.FakeSaveFilterTask + +private const val A_FILTER_ID = "filter-id" +private val A_HOMESERVER_CAPABILITIES = HomeServerCapabilities() +private val A_SYNC_FILTER_PARAMS = SyncFilterParams( + lazyLoadMembersForMessageEvents = true, + lazyLoadMembersForStateEvents = true, + useThreadNotifications = true +) + +@ExperimentalCoroutinesApi +class DefaultGetCurrentFilterTaskTest { + + private val filterRepository = FakeFilterRepository() + private val homeServerCapabilitiesDataSource = FakeHomeServerCapabilitiesDataSource() + private val saveFilterTask = FakeSaveFilterTask() + + private val getCurrentFilterTask = DefaultGetCurrentFilterTask( + filterRepository = filterRepository, + homeServerCapabilitiesDataSource = homeServerCapabilitiesDataSource.instance, + saveFilterTask = saveFilterTask + ) + + @Test + fun `given no filter is stored, when execute, then executes task to save new filter`() = runTest { + filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS) + + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES) + + filterRepository.givenFilterStored(null, null) + + getCurrentFilterTask.execute(Unit) + + val filter = SyncFilterBuilder() + .with(A_SYNC_FILTER_PARAMS) + .build(A_HOMESERVER_CAPABILITIES) + + saveFilterTask.verifyExecution(filter) + } + + @Test + fun `given filter is stored and didn't change, when execute, then returns stored filter id`() = runTest { + filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS) + + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES) + + val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES) + filterRepository.givenFilterStored(A_FILTER_ID, filter.toJSONString()) + + val result = getCurrentFilterTask.execute(Unit) + + result shouldBeEqualTo A_FILTER_ID + } + + @Test + fun `given filter is set and home server capabilities has changed, when execute, then executes task to save new filter`() = runTest { + filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS) + + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES) + + val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES) + filterRepository.givenFilterStored(A_FILTER_ID, filter.toJSONString()) + + val newHomeServerCapabilities = HomeServerCapabilities(canUseThreadReadReceiptsAndNotifications = true) + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(newHomeServerCapabilities) + val newFilter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(newHomeServerCapabilities) + + getCurrentFilterTask.execute(Unit) + + saveFilterTask.verifyExecution(newFilter) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt new file mode 100644 index 0000000000..b8225f21d6 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.test.fakes + +import io.mockk.coEvery +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.filter.FilterRepository +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams + +internal class FakeFilterRepository : FilterRepository by mockk() { + + fun givenFilterStored(filterId: String?, filterBody: String?) { + coEvery { getStoredSyncFilterId() } returns filterId + coEvery { getStoredSyncFilterBody() } returns filterBody + } + + fun givenFilterParamsAreStored(syncFilterParams: SyncFilterParams?) { + coEvery { getStoredFilterParams() } returns syncFilterParams + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt new file mode 100644 index 0000000000..9a56a599d1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource + +internal class FakeHomeServerCapabilitiesDataSource { + val instance = mockk() + + fun givenHomeServerCapabilities(homeServerCapabilities: HomeServerCapabilities) { + every { instance.getHomeServerCapabilities() } returns homeServerCapabilities + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt new file mode 100644 index 0000000000..40bee227e0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import io.mockk.slot +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.internal.session.filter.Filter +import org.matrix.android.sdk.internal.session.filter.SaveFilterTask +import java.util.UUID + +internal class FakeSaveFilterTask : SaveFilterTask by mockk() { + + init { + coEvery { execute(any()) } returns UUID.randomUUID().toString() + } + + fun verifyExecution(filter: Filter) { + val slot = slot() + coVerify { execute(capture(slot)) } + val params = slot.captured + params.filter shouldBeEqualTo filter + } +} diff --git a/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt b/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt index c47769052c..96c3f8a6ce 100644 --- a/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt @@ -24,9 +24,9 @@ import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.sync.SyncUtils import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.sync.FilterService import timber.log.Timber import javax.inject.Inject @@ -41,7 +41,9 @@ class ConfigureAndStartSessionUseCase @Inject constructor( fun execute(session: Session, startSyncing: Boolean = true) { Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing") session.open() - session.filterService().setFilter(FilterService.FilterPreset.ElementFilter) + session.coroutineScope.launch { + session.filterService().setSyncFilter(SyncUtils.getSyncFilterBuilder()) + } if (startSyncing) { session.startSyncing(context) } diff --git a/vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt b/vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt new file mode 100644 index 0000000000..e3408d8814 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 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.app.features.sync + +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder + +object SyncUtils { + // Get only managed types by Element + private val listOfSupportedTimelineEventTypes = listOf( + // TODO Complete the list + EventType.MESSAGE + ) + + // Get only managed types by Element + private val listOfSupportedStateEventTypes = listOf( + // TODO Complete the list + EventType.STATE_ROOM_MEMBER + ) + + fun getSyncFilterBuilder(): SyncFilterBuilder { + return SyncFilterBuilder() + .useThreadNotifications(true) + .lazyLoadMembersForStateEvents(true) + /** + * Currently we don't set [lazy_load_members = true] for Filter.room.timeline even though we set it for RoomFilter which is used later to + * fetch messages in a room. It's not clear if it's done so by mistake or intentionally, so changing it could case side effects and need + * careful testing + * */ +// .lazyLoadMembersForMessageEvents(true) +// .listOfSupportedStateEventTypes(listOfSupportedStateEventTypes) +// .listOfSupportedTimelineEventTypes(listOfSupportedTimelineEventTypes) + } +} diff --git a/vector/src/test/java/im/vector/app/core/session/ConfigureAndStartSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/session/ConfigureAndStartSessionUseCaseTest.kt index 760879b69d..01596e796d 100644 --- a/vector/src/test/java/im/vector/app/core/session/ConfigureAndStartSessionUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/core/session/ConfigureAndStartSessionUseCaseTest.kt @@ -19,6 +19,7 @@ package im.vector.app.core.session import im.vector.app.core.extensions.startSyncing import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase import im.vector.app.features.session.coroutineScope +import im.vector.app.features.sync.SyncUtils import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeEnableNotificationsSettingUpdater import im.vector.app.test.fakes.FakeSession @@ -38,7 +39,6 @@ import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test -import org.matrix.android.sdk.api.session.sync.FilterService class ConfigureAndStartSessionUseCaseTest { @@ -83,7 +83,7 @@ class ConfigureAndStartSessionUseCaseTest { // Then verify { fakeSession.startSyncing(fakeContext.instance) } - fakeSession.fakeFilterService.verifySetFilter(FilterService.FilterPreset.ElementFilter) + fakeSession.fakeFilterService.verifySetSyncFilter(SyncUtils.getSyncFilterBuilder()) fakeSession.fakePushersService.verifyRefreshPushers() fakeWebRtcCallManager.verifyCheckForProtocolsSupportIfNeeded() coVerify { fakeUpdateMatrixClientInfoUseCase.execute(fakeSession) } @@ -105,7 +105,7 @@ class ConfigureAndStartSessionUseCaseTest { // Then verify { fakeSession.startSyncing(fakeContext.instance) } - fakeSession.fakeFilterService.verifySetFilter(FilterService.FilterPreset.ElementFilter) + fakeSession.fakeFilterService.verifySetSyncFilter(SyncUtils.getSyncFilterBuilder()) fakeSession.fakePushersService.verifyRefreshPushers() fakeWebRtcCallManager.verifyCheckForProtocolsSupportIfNeeded() coVerify(inverse = true) { fakeUpdateMatrixClientInfoUseCase.execute(fakeSession) } @@ -127,7 +127,7 @@ class ConfigureAndStartSessionUseCaseTest { // Then verify(inverse = true) { fakeSession.startSyncing(fakeContext.instance) } - fakeSession.fakeFilterService.verifySetFilter(FilterService.FilterPreset.ElementFilter) + fakeSession.fakeFilterService.verifySetSyncFilter(SyncUtils.getSyncFilterBuilder()) fakeSession.fakePushersService.verifyRefreshPushers() fakeWebRtcCallManager.verifyCheckForProtocolsSupportIfNeeded() coVerify { fakeUpdateMatrixClientInfoUseCase.execute(fakeSession) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt index 4332368127..9be59d31fd 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt @@ -16,20 +16,21 @@ package im.vector.app.test.fakes -import io.mockk.every +import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.just import io.mockk.mockk import io.mockk.runs -import io.mockk.verify import org.matrix.android.sdk.api.session.sync.FilterService +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder class FakeFilterService : FilterService by mockk() { fun givenSetFilterSucceeds() { - every { setFilter(any()) } just runs + coEvery { setSyncFilter(any()) } just runs } - fun verifySetFilter(filterPreset: FilterService.FilterPreset) { - verify { setFilter(filterPreset) } + fun verifySetSyncFilter(filterBuilder: SyncFilterBuilder) { + coVerify { setSyncFilter(filterBuilder) } } }