Merge pull request #4352 from vector-im/feature/adm/room-filtering

Fixing case sensitive non latin room name filtering
This commit is contained in:
Benoit Marty 2021-10-28 12:27:13 +02:00 committed by GitHub
commit 868548d0ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 246 additions and 68 deletions

1
changelog.d/3968.bugfix Normal file
View File

@ -0,0 +1 @@
Fixing room search needing exact casing for non latin-1 character named rooms

View File

@ -19,17 +19,41 @@ package org.matrix.android.sdk.api.query
/** /**
* Basic query language. All these cases are mutually exclusive. * Basic query language. All these cases are mutually exclusive.
*/ */
sealed class QueryStringValue { sealed interface QueryStringValue {
object NoCondition : QueryStringValue() sealed interface ContentQueryStringValue : QueryStringValue {
object IsNull : QueryStringValue() val string: String
object IsNotNull : QueryStringValue() val case: Case
object IsEmpty : QueryStringValue() }
object IsNotEmpty : QueryStringValue()
data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue() object NoCondition : QueryStringValue
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : 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 { enum class Case {
/**
* Match query sensitive to case
*/
SENSITIVE, 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

View File

@ -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.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
}
/**
* 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) { 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 +85,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 +378,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

@ -42,7 +42,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,
displayName = roomSummaryEntity.displayName ?: "", displayName = roomSummaryEntity.displayName() ?: "",
name = roomSummaryEntity.name ?: "", name = roomSummaryEntity.name ?: "",
topic = roomSummaryEntity.topic ?: "", topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "",

View File

@ -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.VersioningState
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag 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.database.model.presence.UserPresenceEntity
import org.matrix.android.sdk.internal.session.room.membership.RoomName
internal open class RoomSummaryEntity( internal open class RoomSummaryEntity(
@PrimaryKey var roomId: String = "", @PrimaryKey var roomId: String = "",
@ -36,10 +37,24 @@ internal open class RoomSummaryEntity(
var children: RealmList<SpaceChildSummaryEntity> = RealmList() var children: RealmList<SpaceChildSummaryEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {
var displayName: String? = "" private var displayName: String? = ""
set(value) {
if (value != field) field = value 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? = "" var avatarUrl: String? = ""
set(value) { set(value) {
if (value != field) field = value if (value != field) field = value
@ -284,5 +299,6 @@ internal open class RoomSummaryEntity(
roomEncryptionTrustLevelStr = value?.name roomEncryptionTrustLevelStr = value?.name
} }
} }
companion object companion object
} }

View File

@ -20,24 +20,41 @@ import io.realm.Case
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmQuery import io.realm.RealmQuery
import org.matrix.android.sdk.api.query.QueryStringValue 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 <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> { class QueryStringValueProcessor @Inject constructor(
when (queryStringValue) { private val normalizer: Normalizer
is QueryStringValue.NoCondition -> Timber.v("No condition to process") ) {
is QueryStringValue.IsNotNull -> isNotNull(field)
is QueryStringValue.IsNull -> isNull(field) fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
is QueryStringValue.IsEmpty -> isEmpty(field) return when (queryStringValue) {
is QueryStringValue.IsNotEmpty -> isNotEmpty(field) is QueryStringValue.NoCondition -> this
is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) is QueryStringValue.IsNotNull -> isNotNull(field)
is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) 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 { private fun QueryStringValue.Case.toRealmCase(): Case {
return when (this) { return when (this) {
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE QueryStringValue.Case.SENSITIVE,
QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE
} }
} }

View File

@ -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.model.GroupSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase 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.query.process
import org.matrix.android.sdk.internal.util.fetchCopyMap import org.matrix.android.sdk.internal.util.fetchCopyMap
import javax.inject.Inject import javax.inject.Inject
internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy, internal class DefaultGroupService @Inject constructor(
private val groupFactory: GroupFactory) : GroupService { @SessionDatabase private val monarchy: Monarchy,
private val groupFactory: GroupFactory,
private val queryStringValueProcessor: QueryStringValueProcessor,
) : GroupService {
override fun getGroup(groupId: String): Group? { override fun getGroup(groupId: String): Group? {
return Realm.getInstance(monarchy.realmConfiguration).use { realm -> 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<GroupSummaryEntity> { private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> {
return GroupSummaryEntity.where(realm) return with(queryStringValueProcessor) {
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) GroupSummaryEntity.where(realm)
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
}
} }
} }

View File

@ -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.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId 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.query.process
import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask
import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask 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 leaveRoomTask: LeaveRoomTask,
private val membershipAdminTask: MembershipAdminTask, private val membershipAdminTask: MembershipAdminTask,
@UserId @UserId
private val userId: String private val userId: String,
private val queryStringValueProcessor: QueryStringValueProcessor
) : MembershipService { ) : MembershipService {
@AssistedFactory @AssistedFactory
@ -94,15 +96,17 @@ internal class DefaultMembershipService @AssistedInject constructor(
} }
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> { private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent() return with(queryStringValueProcessor) {
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId) RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) .process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.apply { .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
if (queryParams.excludeSelf) { .apply {
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) if (queryParams.excludeSelf) {
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
}
} }
} }
} }
override fun getNumberOfJoinedMembers(): Int { override fun getNumberOfJoinedMembers(): Int {

View File

@ -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.database.query.where
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
import org.matrix.android.sdk.internal.util.Normalizer
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -42,6 +43,7 @@ import javax.inject.Inject
internal class RoomDisplayNameResolver @Inject constructor( internal class RoomDisplayNameResolver @Inject constructor(
matrixConfiguration: MatrixConfiguration, matrixConfiguration: MatrixConfiguration,
private val displayNameResolver: DisplayNameResolver, private val displayNameResolver: DisplayNameResolver,
private val normalizer: Normalizer,
@UserId private val userId: String @UserId private val userId: String
) { ) {
@ -54,7 +56,7 @@ internal class RoomDisplayNameResolver @Inject constructor(
* @param roomId: the roomId to resolve the name of. * @param roomId: the roomId to resolve the name of.
* @return the room display name * @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 // this algorithm is the one defined in
// https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617
// calculateRoomName(room, userId) // 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 val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name
if (!name.isNullOrEmpty()) { if (!name.isNullOrEmpty()) {
return name return name.toRoomName()
} }
val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
if (!name.isNullOrEmpty()) { if (!name.isNullOrEmpty()) {
return name return name.toRoomName()
} }
val roomMembers = RoomMemberHelper(realm, roomId) 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] */ /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */
@ -165,4 +167,8 @@ internal class RoomDisplayNameResolver @Inject constructor(
"${roomMemberSummary.displayName} (${roomMemberSummary.userId})" "${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
} }
} }
private fun String.toRoomName() = RoomName(this, normalizedName = normalizer.normalize(this))
} }
internal data class RoomName(val name: String, val normalizedName: String)

View File

@ -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.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase 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.query.process
import javax.inject.Inject import javax.inject.Inject
internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, internal class StateEventDataSource @Inject constructor(
private val realmSessionProvider: RealmSessionProvider) { @SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
private val queryStringValueProcessor: QueryStringValueProcessor
) {
fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? { fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? {
return realmSessionProvider.withRealm { realm -> return realmSessionProvider.withRealm { realm ->
@ -78,13 +82,15 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private
eventTypes: Set<String>, eventTypes: Set<String>,
stateKey: QueryStringValue stateKey: QueryStringValue
): RealmQuery<CurrentStateEventEntity> { ): RealmQuery<CurrentStateEventEntity> {
return realm.where<CurrentStateEventEntity>() return with(queryStringValueProcessor) {
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) realm.where<CurrentStateEventEntity>()
.apply { .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
if (eventTypes.isNotEmpty()) { .apply {
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) if (eventTypes.isNotEmpty()) {
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
}
} }
} .process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey) }
} }
} }

View File

@ -28,6 +28,7 @@ import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter 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.ResultBoundaries
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams 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.findByAlias
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase 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.query.process
import org.matrix.android.sdk.internal.util.fetchCopyMap import org.matrix.android.sdk.internal.util.fetchCopyMap
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, internal class RoomSummaryDataSource @Inject constructor(
private val roomSummaryMapper: RoomSummaryMapper) { @SessionDatabase private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val queryStringValueProcessor: QueryStringValueProcessor
) {
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
return monarchy return monarchy
@ -240,12 +245,20 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
} }
private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> { private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
val query = RoomSummaryEntity.where(realm) val query = with(queryStringValueProcessor) {
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) RoomSummaryEntity.where(realm)
query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) .process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) .let {
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) if (queryParams.displayName.isNormalized()) {
query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) 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 { queryParams.roomCategoryFilter?.let {
when (it) { when (it) {

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,7 @@ 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) roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId))
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

@ -166,7 +166,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
roomSummaryEntity.directUserId = userId roomSummaryEntity.directUserId = userId
// Also update the avatar and displayname, there is a specific treatment for DMs // Also update the avatar and displayname, there is a specific treatment for DMs
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) 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 it.directUserId = null
// Also update the avatar and displayname, there was a specific treatment for DMs // Also update the avatar and displayname, there was a specific treatment for DMs
it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId) it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId)
it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId) it.setDisplayName(roomDisplayNameResolver.resolve(realm, it.roomId))
} }
} }

View File

@ -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)
}
}

View File

@ -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())
}
}

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)
) )
} }
} }