adding normalised room display name field and making use of it when filtering rooms by name

- fixes non latin-1 character set room names from being ignored when searching with inexact casing
This commit is contained in:
Adam Brown 2021-10-27 13:06:55 +01:00
parent d5ed95988d
commit 4ae04fc297
8 changed files with 51 additions and 9 deletions

View File

@ -39,4 +39,6 @@ sealed interface QueryStringValue {
INSENSITIVE, INSENSITIVE,
NORMALIZED NORMALIZED
} }
fun isNormalized() = this is ContentQueryStringValue && case == Case.NORMALIZED
} }

View File

@ -45,11 +45,17 @@ 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.database.model.presence.UserPresenceEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber 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
}
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Session from $oldVersion to $newVersion") Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@ -72,6 +78,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 15) migrateTo16(realm)
if (oldVersion <= 16) migrateTo17(realm) if (oldVersion <= 16) migrateTo17(realm)
if (oldVersion <= 17) migrateTo18(realm) if (oldVersion <= 17) migrateTo18(realm)
if (oldVersion <= 18) migrateTo19(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -364,4 +371,16 @@ internal object RealmSessionStoreMigration : RealmMigration {
realm.schema.get("RoomMemberSummaryEntity") realm.schema.get("RoomMemberSummaryEntity")
?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) ?.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)
}
}
}
} }

View File

@ -40,6 +40,7 @@ private const val REALM_NAME = "disk_store.realm"
*/ */
internal class SessionRealmConfigurationFactory @Inject constructor( internal class SessionRealmConfigurationFactory @Inject constructor(
private val realmKeysUtils: RealmKeysUtils, private val realmKeysUtils: RealmKeysUtils,
private val realmSessionStoreMigration: RealmSessionStoreMigration,
@SessionFilesDirectory val directory: File, @SessionFilesDirectory val directory: File,
@SessionId val sessionId: String, @SessionId val sessionId: String,
@UserMd5 val userMd5: String, @UserMd5 val userMd5: String,
@ -71,7 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.modules(SessionRealmModule()) .modules(SessionRealmModule())
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
.migration(RealmSessionStoreMigration) .migration(realmSessionStoreMigration)
.build() .build()
// Try creating a realm instance and if it succeeds we can clear the flag // Try creating a realm instance and if it succeeds we can clear the flag

View File

@ -40,6 +40,17 @@ internal open class RoomSummaryEntity(
set(value) { set(value) {
if (value != field) field = value if (value != field) field = value
} }
/**
* Workaround for Realm only supporting Latin-1 character sets when sorting
* or filtering by case
* See https://github.com/realm/realm-core/issues/777
*/
var normalizedDisplayName: String? = ""
set(value) {
if (value != field) field = value
}
var avatarUrl: String? = "" var avatarUrl: String? = ""
set(value) { set(value) {
if (value != field) field = value if (value != field) field = value
@ -284,5 +295,6 @@ internal open class RoomSummaryEntity(
roomEncryptionTrustLevelStr = value?.name roomEncryptionTrustLevelStr = value?.name
} }
} }
companion object companion object
} }

View File

@ -245,7 +245,11 @@ internal class RoomSummaryDataSource @Inject constructor(
val query = with(queryStringValueProcessor) { val query = with(queryStringValueProcessor) {
val query = RoomSummaryEntity.where(realm) val query = RoomSummaryEntity.where(realm)
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
if (queryParams.displayName.isNormalized()) {
query.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName)
} else {
query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
}
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false)

View File

@ -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.RoomDisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper 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.session.room.relationship.RoomChildRelationInfo
import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -75,7 +76,8 @@ internal class RoomSummaryUpdater @Inject constructor(
private val roomAvatarResolver: RoomAvatarResolver, private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor, private val eventDecryptor: EventDecryptor,
private val crossSigningService: DefaultCrossSigningService, private val crossSigningService: DefaultCrossSigningService,
private val roomAccountDataDataSource: RoomAccountDataDataSource) { private val roomAccountDataDataSource: RoomAccountDataDataSource,
private val normalizer: Normalizer) {
fun update(realm: Realm, fun update(realm: Realm,
roomId: String, roomId: String,
@ -136,7 +138,9 @@ internal class RoomSummaryUpdater @Inject constructor(
// avoid this call if we are sure there are unread events // avoid this call if we are sure there are unread events
!isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) val roomDisplayName = roomDisplayNameResolver.resolve(realm, roomId)
roomSummaryEntity.displayName = roomDisplayName
roomSummaryEntity.normalizedDisplayName = normalizer.normalize(roomDisplayName)
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 New Vector Ltd * Copyright 2021 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -191,7 +191,7 @@ class RoomListViewModel @AssistedInject constructor(
} }
updatableQuery?.updateQuery { updatableQuery?.updateQuery {
it.copy( it.copy(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED)
) )
} }
} }