diff --git a/changelog.d/3968.bugfix b/changelog.d/3968.bugfix new file mode 100644 index 0000000000..dec0eaf2df --- /dev/null +++ b/changelog.d/3968.bugfix @@ -0,0 +1 @@ +Fixing room search needing exact casing for non latin-1 character named rooms \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 8f83beface..31ec131c5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -19,17 +19,41 @@ package org.matrix.android.sdk.api.query /** * Basic query language. All these cases are mutually exclusive. */ -sealed class QueryStringValue { - object NoCondition : QueryStringValue() - object IsNull : QueryStringValue() - object IsNotNull : QueryStringValue() - object IsEmpty : QueryStringValue() - object IsNotEmpty : QueryStringValue() - data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue() - data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue() +sealed interface QueryStringValue { + sealed interface ContentQueryStringValue : QueryStringValue { + val string: String + val case: Case + } + + object NoCondition : QueryStringValue + object IsNull : QueryStringValue + object IsNotNull : QueryStringValue + object IsEmpty : QueryStringValue + object IsNotEmpty : QueryStringValue + + data class Equals(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue + data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue enum class Case { + /** + * Match query sensitive to case + */ SENSITIVE, - INSENSITIVE + + /** + * Match query insensitive to case, this only works for Latin-1 character sets + */ + INSENSITIVE, + + /** + * Match query with input normalized (case insensitive) + * Works around Realms inability to sort or filter by case for non Latin-1 character sets + * Expects the target field to contain normalized data + * + * @see org.matrix.android.sdk.internal.util.Normalizer.normalize + */ + NORMALIZED } } + +internal fun QueryStringValue.isNormalized() = this is QueryStringValue.ContentQueryStringValue && case == QueryStringValue.Case.NORMALIZED 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 05137f8105..2256d93100 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 @@ -45,11 +45,24 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.query.process +import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber +import javax.inject.Inject -internal object RealmSessionStoreMigration : RealmMigration { +internal class RealmSessionStoreMigration @Inject constructor( + private val normalizer: Normalizer +) : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 18L + companion object { + const val SESSION_STORE_SCHEMA_VERSION = 19L + } + + /** + * Forces all RealmSessionStoreMigration instances to be equal + * Avoids Realm throwing when multiple instances of the migration are set + */ + override fun equals(other: Any?) = other is RealmSessionStoreMigration + override fun hashCode() = 1000 override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -72,6 +85,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) if (oldVersion <= 17) migrateTo18(realm) + if (oldVersion <= 18) migrateTo19(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -364,4 +378,16 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("RoomMemberSummaryEntity") ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) } + + private fun migrateTo19(realm: DynamicRealm) { + Timber.d("Step 18 -> 19") + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java) + ?.transform { + it.getString(RoomSummaryEntityFields.DISPLAY_NAME)?.let { displayName -> + val normalised = normalizer.normalize(displayName) + it.set(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, normalised) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 1771c5b202..04ca26a943 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -40,6 +40,7 @@ private const val REALM_NAME = "disk_store.realm" */ internal class SessionRealmConfigurationFactory @Inject constructor( private val realmKeysUtils: RealmKeysUtils, + private val realmSessionStoreMigration: RealmSessionStoreMigration, @SessionFilesDirectory val directory: File, @SessionId val sessionId: String, @UserMd5 val userMd5: String, @@ -71,7 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) - .migration(RealmSessionStoreMigration) + .migration(realmSessionStoreMigration) .build() // Try creating a realm instance and if it succeeds we can clear the flag diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 6c81550012..3a15e0acf0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -42,7 +42,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa return RoomSummary( roomId = roomSummaryEntity.roomId, - displayName = roomSummaryEntity.displayName ?: "", + displayName = roomSummaryEntity.displayName() ?: "", name = roomSummaryEntity.name ?: "", topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 88b8886936..67672f03ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.session.room.membership.RoomName internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", @@ -36,10 +37,24 @@ internal open class RoomSummaryEntity( var children: RealmList = RealmList() ) : RealmObject() { - var displayName: String? = "" - set(value) { - if (value != field) field = value + private var displayName: String? = "" + + fun displayName() = displayName + + fun setDisplayName(roomName: RoomName) { + if (roomName.name != displayName) { + displayName = roomName.name + normalizedDisplayName = roomName.normalizedName } + } + + /** + * Workaround for Realm only supporting Latin-1 character sets when sorting + * or filtering by case + * See https://github.com/realm/realm-core/issues/777 + */ + private var normalizedDisplayName: String? = "" + var avatarUrl: String? = "" set(value) { if (value != field) field = value @@ -284,5 +299,6 @@ internal open class RoomSummaryEntity( roomEncryptionTrustLevelStr = value?.name } } + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt index fd33682231..b42bf2b8c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt @@ -20,24 +20,41 @@ import io.realm.Case import io.realm.RealmObject import io.realm.RealmQuery import org.matrix.android.sdk.api.query.QueryStringValue -import timber.log.Timber +import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue +import org.matrix.android.sdk.internal.util.Normalizer +import javax.inject.Inject -fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { - when (queryStringValue) { - is QueryStringValue.NoCondition -> Timber.v("No condition to process") - is QueryStringValue.IsNotNull -> isNotNull(field) - is QueryStringValue.IsNull -> isNull(field) - is QueryStringValue.IsEmpty -> isEmpty(field) - is QueryStringValue.IsNotEmpty -> isNotEmpty(field) - is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) - is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) +class QueryStringValueProcessor @Inject constructor( + private val normalizer: Normalizer +) { + + fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { + return when (queryStringValue) { + is QueryStringValue.NoCondition -> this + is QueryStringValue.IsNotNull -> isNotNull(field) + is QueryStringValue.IsNull -> isNull(field) + is QueryStringValue.IsEmpty -> isEmpty(field) + is QueryStringValue.IsNotEmpty -> isNotEmpty(field) + is ContentQueryStringValue -> when (queryStringValue) { + is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) + is QueryStringValue.Contains -> contains(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) + } + } + } + + private fun ContentQueryStringValue.toRealmValue(): String { + return when (case) { + QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) + QueryStringValue.Case.SENSITIVE, + QueryStringValue.Case.INSENSITIVE -> string + } } - return this } private fun QueryStringValue.Case.toRealmCase(): Case { return when (this) { QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE - QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE + QueryStringValue.Case.SENSITIVE, + QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt index 8dc5f3931d..9334d09377 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt @@ -30,12 +30,16 @@ import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.fetchCopyMap import javax.inject.Inject -internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val groupFactory: GroupFactory) : GroupService { +internal class DefaultGroupService @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val groupFactory: GroupFactory, + private val queryStringValueProcessor: QueryStringValueProcessor, +) : GroupService { override fun getGroup(groupId: String): Group? { return Realm.getInstance(monarchy.realmConfiguration).use { realm -> @@ -67,8 +71,10 @@ internal class DefaultGroupService @Inject constructor(@SessionDatabase private } private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery { - return GroupSummaryEntity.where(realm) - .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - .process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + return with(queryStringValueProcessor) { + GroupSummaryEntity.where(realm) + .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + .process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 204deb72b4..6cf82dde44 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask @@ -51,7 +52,8 @@ internal class DefaultMembershipService @AssistedInject constructor( private val leaveRoomTask: LeaveRoomTask, private val membershipAdminTask: MembershipAdminTask, @UserId - private val userId: String + private val userId: String, + private val queryStringValueProcessor: QueryStringValueProcessor ) : MembershipService { @AssistedFactory @@ -94,15 +96,17 @@ internal class DefaultMembershipService @AssistedInject constructor( } private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery { - return RoomMemberHelper(realm, roomId).queryRoomMembersEvent() - .process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId) - .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) - .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - .apply { - if (queryParams.excludeSelf) { - notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) + return with(queryStringValueProcessor) { + RoomMemberHelper(realm, roomId).queryRoomMembersEvent() + .process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId) + .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + .apply { + if (queryParams.excludeSelf) { + notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) + } } - } + } } override fun getNumberOfJoinedMembers(): Int { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 5e77dd157a..bd9f2ecc36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver +import org.matrix.android.sdk.internal.util.Normalizer import javax.inject.Inject /** @@ -42,6 +43,7 @@ import javax.inject.Inject internal class RoomDisplayNameResolver @Inject constructor( matrixConfiguration: MatrixConfiguration, private val displayNameResolver: DisplayNameResolver, + private val normalizer: Normalizer, @UserId private val userId: String ) { @@ -54,7 +56,7 @@ internal class RoomDisplayNameResolver @Inject constructor( * @param roomId: the roomId to resolve the name of. * @return the room display name */ - fun resolve(realm: Realm, roomId: String): String { + fun resolve(realm: Realm, roomId: String): RoomName { // this algorithm is the one defined in // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 // calculateRoomName(room, userId) @@ -66,12 +68,12 @@ internal class RoomDisplayNameResolver @Inject constructor( val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root name = ContentMapper.map(roomName?.content).toModel()?.name if (!name.isNullOrEmpty()) { - return name + return name.toRoomName() } val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root name = ContentMapper.map(canonicalAlias?.content).toModel()?.canonicalAlias if (!name.isNullOrEmpty()) { - return name + return name.toRoomName() } val roomMembers = RoomMemberHelper(realm, roomId) @@ -152,7 +154,7 @@ internal class RoomDisplayNameResolver @Inject constructor( } } } - return name ?: roomId + return (name ?: roomId).toRoomName() } /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */ @@ -165,4 +167,8 @@ internal class RoomDisplayNameResolver @Inject constructor( "${roomMemberSummary.displayName} (${roomMemberSummary.userId})" } } + + private fun String.toRoomName() = RoomName(this, normalizedName = normalizer.normalize(this)) } + +internal data class RoomName(val name: String, val normalizedName: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index a25a362bfa..2114b9c590 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -31,11 +31,15 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import javax.inject.Inject -internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider) { +internal class StateEventDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, + private val queryStringValueProcessor: QueryStringValueProcessor +) { fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? { return realmSessionProvider.withRealm { realm -> @@ -78,13 +82,15 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private eventTypes: Set, stateKey: QueryStringValue ): RealmQuery { - return realm.where() - .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) - .apply { - if (eventTypes.isNotEmpty()) { - `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) + return with(queryStringValueProcessor) { + realm.where() + .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) + .apply { + if (eventTypes.isNotEmpty()) { + `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) + } } - } - .process(CurrentStateEventEntityFields.STATE_KEY, stateKey) + .process(CurrentStateEventEntityFields.STATE_KEY, stateKey) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index dd14b32463..c9fc3c9575 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -28,6 +28,7 @@ import io.realm.RealmQuery import io.realm.kotlin.where import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter +import org.matrix.android.sdk.api.query.isNormalized import org.matrix.android.sdk.api.session.room.ResultBoundaries import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams @@ -47,12 +48,16 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.fetchCopyMap import javax.inject.Inject -internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val roomSummaryMapper: RoomSummaryMapper) { +internal class RoomSummaryDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val roomSummaryMapper: RoomSummaryMapper, + private val queryStringValueProcessor: QueryStringValueProcessor +) { fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { return monarchy @@ -240,12 +245,20 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { - val query = RoomSummaryEntity.where(realm) - query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) - query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) - query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) - query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) + val query = with(queryStringValueProcessor) { + RoomSummaryEntity.where(realm) + .process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) + .let { + if (queryParams.displayName.isNormalized()) { + it.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName) + } else { + it.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + } + } + .process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) + .process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + .equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) + } queryParams.roomCategoryFilter?.let { when (it) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 30014f4539..3556cabb33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo +import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject import kotlin.system.measureTimeMillis @@ -75,7 +76,8 @@ internal class RoomSummaryUpdater @Inject constructor( private val roomAvatarResolver: RoomAvatarResolver, private val eventDecryptor: EventDecryptor, private val crossSigningService: DefaultCrossSigningService, - private val roomAccountDataDataSource: RoomAccountDataDataSource) { + private val roomAccountDataDataSource: RoomAccountDataDataSource, + private val normalizer: Normalizer) { fun update(realm: Realm, roomId: String, @@ -136,7 +138,7 @@ internal class RoomSummaryUpdater @Inject constructor( // avoid this call if we are sure there are unread events !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) - roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) + roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel()?.name roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel()?.topic diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index 3e38cd7839..7f80486c70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -166,7 +166,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( roomSummaryEntity.directUserId = userId // Also update the avatar and displayname, there is a specific treatment for DMs roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) - roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) + roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) } } } @@ -178,7 +178,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( it.directUserId = null // Also update the avatar and displayname, there was a specific treatment for DMs it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId) - it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId) + it.setDisplayName(roomDisplayNameResolver.resolve(realm, it.roomId)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt new file mode 100644 index 0000000000..0e9c885394 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2021 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.util + +import java.text.Normalizer +import javax.inject.Inject + +class Normalizer @Inject constructor() { + + fun normalize(input: String): String { + return Normalizer.normalize(input.lowercase(), Normalizer.Form.NFD) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt new file mode 100644 index 0000000000..3a0574e95a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 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 + +import io.mockk.mockk +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class RealmSessionStoreMigrationTest { + + @Test + fun `when creating multiple migration instances then they are equal`() { + RealmSessionStoreMigration(normalizer = mockk()) shouldBeEqualTo RealmSessionStoreMigration(normalizer = mockk()) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 89f5aec8fb..b38f2565b6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -191,7 +191,7 @@ class RoomListViewModel @AssistedInject constructor( } updatableQuery?.updateQuery { it.copy( - displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED) ) } }