Fixes myroomnick changing Display Name (#5618)

This commit is contained in:
Eric Decanini 2022-04-13 18:35:33 +02:00 committed by GitHub
parent e109319b1e
commit 9b7e94ebab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 171 additions and 66 deletions

1
changelog.d/5618.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes display name being changed when using /myroomnick

View file

@ -16,6 +16,9 @@
package org.matrix.android.sdk.api.session.user.model
import org.matrix.android.sdk.api.session.profile.ProfileService
import org.matrix.android.sdk.api.util.JsonDict
/**
* Data class which holds information about a user.
* It can be retrieved with [org.matrix.android.sdk.api.session.user.UserService]
@ -27,4 +30,14 @@ data class User(
*/
val displayName: String? = null,
val avatarUrl: String? = null
)
) {
companion object {
fun fromJson(userId: String, json: JsonDict) = User(
userId = userId,
displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String,
avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String
)
}
}

View file

@ -37,6 +37,7 @@ sealed class MatrixItem(
override val displayName: String? = null,
override val avatarUrl: String? = null) :
MatrixItem(id, displayName?.removeSuffix(IRC_PATTERN), avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
@ -200,7 +201,7 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) }
fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) }
fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)

View file

@ -123,7 +123,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
eventId = roomMemberEvent.eventId
root = eventEntity
}
roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
roomMemberEventHandler.handle(realm, roomId, roomMemberEvent, false)
}
roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
roomSummaryUpdater.update(realm, roomId, updateMembers = true)

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.room.membership
import io.realm.Realm
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
@ -33,23 +34,49 @@ internal class RoomMemberEventHandler @Inject constructor(
@UserId private val myUserId: String
) {
fun handle(realm: Realm, roomId: String, event: Event, aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
fun handle(realm: Realm,
roomId: String,
event: Event,
isInitialSync: Boolean,
aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
if (event.type != EventType.STATE_ROOM_MEMBER) {
return false
}
val userId = event.stateKey ?: return false
val roomMember = event.getFixedRoomMemberContent()
return handle(realm, roomId, userId, roomMember, aggregator)
val eventUserId = event.stateKey ?: return false
val roomMember = event.getFixedRoomMemberContent() ?: return false
return if (isInitialSync) {
handleInitialSync(realm, roomId, myUserId, eventUserId, roomMember, aggregator)
} else {
handleIncrementalSync(
realm,
roomId,
eventUserId,
roomMember,
event.resolvedPrevContent(),
aggregator
)
}
}
fun handle(realm: Realm,
roomId: String,
userId: String,
roomMember: RoomMemberContent?,
aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
if (roomMember == null) {
return false
private fun handleInitialSync(realm: Realm,
roomId: String,
currentUserId: String,
eventUserId: String,
roomMember: RoomMemberContent,
aggregator: SyncResponsePostTreatmentAggregator?): Boolean {
if (currentUserId != eventUserId) {
saveUserEntityLocallyIfNecessary(realm, eventUserId, roomMember)
}
saveRoomMemberEntityLocally(realm, roomId, eventUserId, roomMember)
updateDirectChatsIfNecessary(roomId, roomMember, aggregator)
return true
}
private fun saveRoomMemberEntityLocally(realm: Realm,
roomId: String,
userId: String,
roomMember: RoomMemberContent) {
val roomMemberEntity = RoomMemberEntityFactory.create(
roomId,
userId,
@ -58,26 +85,58 @@ internal class RoomMemberEventHandler @Inject constructor(
// but we want to preserve presence record value and not replace it with null
getExistingPresenceState(realm, roomId, userId))
realm.insertOrUpdate(roomMemberEntity)
if (roomMember.membership.isActive()) {
val userEntity = UserEntityFactory.create(userId, roomMember)
realm.insertOrUpdate(userEntity)
}
// check whether this new room member event may be used to update the directs dictionary in account data
// this is required to handle correctly invite by email in DM
val mxId = roomMember.thirdPartyInvite?.signed?.mxid
if (mxId != null && mxId != myUserId) {
aggregator?.directChatsToCheck?.put(roomId, mxId)
}
return true
}
/**
* Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity
* by NULL value.
*/
private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? {
return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity
}
private fun saveUserEntityLocallyIfNecessary(realm: Realm,
userId: String,
roomMember: RoomMemberContent) {
if (roomMember.membership.isActive()) {
saveUserLocally(realm, userId, roomMember)
}
}
private fun saveUserLocally(realm: Realm, userId: String, roomMember: RoomMemberContent) {
val userEntity = UserEntityFactory.create(userId, roomMember)
realm.insertOrUpdate(userEntity)
}
private fun updateDirectChatsIfNecessary(roomId: String,
roomMember: RoomMemberContent,
aggregator: SyncResponsePostTreatmentAggregator?) {
// check whether this new room member event may be used to update the directs dictionary in account data
// this is required to handle correctly invite by email in DM
val mxId = roomMember.thirdPartyInvite?.signed?.mxid
if (mxId != null && mxId != myUserId) {
aggregator?.directChatsToCheck?.put(roomId, mxId)
}
}
private fun handleIncrementalSync(realm: Realm,
roomId: String,
eventUserId: String,
roomMember: RoomMemberContent,
prevContent: Content?,
aggregator: SyncResponsePostTreatmentAggregator?): Boolean {
if (aggregator != null) {
val previousDisplayName = prevContent?.get("displayname") as? String
val previousAvatar = prevContent?.get("avatar_url") as? String
if (previousDisplayName != roomMember.displayName || previousAvatar != roomMember.avatarUrl) {
aggregator.userIdsToFetch.add(eventUserId)
}
}
saveRoomMemberEntityLocally(realm, roomId, eventUserId, roomMember)
// At the end of the sync, fetch all the profiles from the aggregator
updateDirectChatsIfNecessary(roomId, roomMember, aggregator)
return true
}
}

View file

@ -22,4 +22,7 @@ internal class SyncResponsePostTreatmentAggregator {
// Map of roomId to directUserId
val directChatsToCheck = mutableMapOf<String, String>()
// List of userIds to fetch and update at the end of incremental syncs
val userIdsToFetch = mutableListOf<String>()
}

View file

@ -16,10 +16,16 @@
package org.matrix.android.sdk.internal.session.sync.handler
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable
import org.matrix.android.sdk.internal.session.user.UserEntityFactory
import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
import javax.inject.Inject
@ -27,11 +33,14 @@ import javax.inject.Inject
internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
private val directChatsHelper: DirectChatsHelper,
private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
private val updateUserAccountDataTask: UpdateUserAccountDataTask
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val getProfileInfoTask: GetProfileInfoTask,
@SessionDatabase private val monarchy: Monarchy,
) {
suspend fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) {
cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete)
updateDirectUserIds(synResHaResponsePostTreatmentAggregator.directChatsToCheck)
suspend fun handle(aggregator: SyncResponsePostTreatmentAggregator) {
cleanupEphemeralFiles(aggregator.ephemeralFilesToDelete)
updateDirectUserIds(aggregator.directChatsToCheck)
fetchAndUpdateUsers(aggregator.userIdsToFetch)
}
private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List<String>) {
@ -59,13 +68,33 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
}
// remove roomId from currentDirectUserId entry
hasUpdate = hasUpdate or(directChats[currentDirectUserId]?.remove(roomId) == true)
hasUpdate = hasUpdate or (directChats[currentDirectUserId]?.remove(roomId) == true)
// remove currentDirectUserId entry if there is no attached room anymore
hasUpdate = hasUpdate or(directChats.takeIf { it[currentDirectUserId].isNullOrEmpty() }?.remove(currentDirectUserId) != null)
hasUpdate = hasUpdate or (directChats.takeIf { it[currentDirectUserId].isNullOrEmpty() }?.remove(currentDirectUserId) != null)
}
}
if (hasUpdate) {
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
}
}
private suspend fun fetchAndUpdateUsers(userIdsToFetch: List<String>) {
fetchUsers(userIdsToFetch)
.takeIf { it.isNotEmpty() }
?.saveLocally()
}
private suspend fun fetchUsers(userIdsToFetch: List<String>) = userIdsToFetch.mapNotNull {
tryOrNull {
val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it))
User.fromJson(it, profileJson)
}
}
private fun List<User>.saveLocally() {
val userEntities = map { user -> UserEntityFactory.create(user) }
monarchy.doWithRealm {
it.insertOrUpdate(userEntities)
}
}
}

View file

@ -206,6 +206,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
syncLocalTimestampMillis: Long,
aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
Timber.v("Handle join sync for room $roomId")
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed)
?._roomSyncEphemeral
@ -240,7 +241,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
// Give info to crypto module
cryptoService.onStateEvent(roomId, event)
roomMemberEventHandler.handle(realm, roomId, event, aggregator)
roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator)
}
}
if (roomSync.timeline?.events?.isNotEmpty() == true) {
@ -282,6 +283,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
insertType: EventInsertType,
syncLocalTimestampMillis: Long): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
@ -295,7 +297,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
eventId = eventEntity.eventId
root = eventEntity
}
roomMemberEventHandler.handle(realm, roomId, event)
roomMemberEventHandler.handle(realm, roomId, event, isInitialSync)
}
}
val inviterEvent = roomSync.inviteState?.events?.lastOrNull {
@ -311,6 +313,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
roomSync: RoomSync,
insertType: EventInsertType,
syncLocalTimestampMillis: Long): RoomEntity {
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
for (event in roomSync.state?.events.orEmpty()) {
if (event.eventId == null || event.stateKey == null || event.type == null) {
@ -322,7 +325,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
eventId = event.eventId
root = eventEntity
}
roomMemberEventHandler.handle(realm, roomId, event)
roomMemberEventHandler.handle(realm, roomId, event, isInitialSync)
}
for (event in roomSync.timeline?.events.orEmpty()) {
if (event.eventId == null || event.senderId == null || event.type == null) {
@ -336,7 +339,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
root = eventEntity
}
if (event.type == EventType.STATE_ROOM_MEMBER) {
roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
roomMemberEventHandler.handle(realm, roomEntity.roomId, event, isInitialSync)
}
}
}
@ -380,11 +383,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
continue
}
eventIds.add(event.eventId)
liveEventService.get().dispatchLiveEventReceived(event, roomId, insertType == EventInsertType.INITIAL_SYNC)
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
eventIds.add(event.eventId)
liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync)
if (event.isEncrypted() && !isInitialSync) {
runBlocking {
decryptIfNeeded(event, roomId)
@ -403,9 +406,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
root = eventEntity
}
if (event.type == EventType.STATE_ROOM_MEMBER) {
val fixedContent = event.getFixedRoomMemberContent()
roomMemberContentsByUser[event.stateKey] = fixedContent
roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent, aggregator)
roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent()
roomMemberEventHandler.handle(realm, roomEntity.roomId, event, isInitialSync, aggregator)
}
}

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.user
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import org.matrix.android.sdk.api.session.profile.ProfileService
import org.matrix.android.sdk.api.session.user.UserService
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.Optional
@ -37,16 +36,10 @@ internal class DefaultUserService @Inject constructor(private val userDataSource
}
override suspend fun resolveUser(userId: String): User {
val known = getUser(userId)
if (known != null) {
return known
} else {
return getUser(userId) ?: run {
val params = GetProfileInfoTask.Params(userId)
val data = getProfileInfoTask.execute(params)
return User(
userId,
data[ProfileService.DISPLAY_NAME_KEY] as? String,
data[ProfileService.AVATAR_URL_KEY] as? String)
val json = getProfileInfoTask.execute(params)
User.fromJson(userId, json)
}
}

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.user
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.internal.database.model.UserEntity
internal object UserEntityFactory {
@ -24,8 +25,16 @@ internal object UserEntityFactory {
fun create(userId: String, roomMember: RoomMemberContent): UserEntity {
return UserEntity(
userId = userId,
displayName = roomMember.displayName ?: "",
avatarUrl = roomMember.avatarUrl ?: ""
displayName = roomMember.displayName.orEmpty(),
avatarUrl = roomMember.avatarUrl.orEmpty()
)
}
fun create(user: User): UserEntity {
return UserEntity(
userId = user.userId,
displayName = user.displayName.orEmpty(),
avatarUrl = user.avatarUrl.orEmpty()
)
}
}

View file

@ -55,7 +55,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.flow.flow
@ -112,8 +112,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor(
session.flow().liveUserCryptoDevices(initialState.userId)
.map {
Pair(
it.fold(true, { prev, dev -> prev && dev.isVerified }),
it.fold(true, { prev, dev -> prev && (dev.trustLevel?.crossSigningVerified == true) })
it.fold(true) { prev, dev -> prev && dev.isVerified },
it.fold(true) { prev, dev -> prev && (dev.trustLevel?.crossSigningVerified == true) }
)
}
.execute { it ->
@ -327,14 +327,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor(
private suspend fun fetchProfileInfo() {
val result = runCatchingToAsync {
session.getProfileAsUser(initialState.userId)
.let {
MatrixItem.UserItem(
id = initialState.userId,
displayName = it.displayName,
avatarUrl = it.avatarUrl
)
}
session.getProfile(initialState.userId)
.let { User.fromJson(initialState.userId, it) }
.toMatrixItem()
}
setState {