Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes myroomnick changing Display Name #5618

Merged
merged 24 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bbfefbf
Removes user entity change during room nickname change
ericdecanini Mar 23, 2022
4c1e651
Adds changelog file
ericdecanini Mar 23, 2022
927eeb3
Refactors RoomMemberEventHandler with change to only save the user lo…
ericdecanini Mar 30, 2022
0200f48
Fixes lint error
ericdecanini Mar 30, 2022
6c1f6dc
Uses aggregator to handle room name change events
ericdecanini Mar 31, 2022
7f64c62
Adds missing room member update call during incremental syncs
ericdecanini Mar 31, 2022
25ca684
Improves code reuse and readability
ericdecanini Apr 6, 2022
c650843
Merge remote-tracking branch 'origin/develop' into bugfix/eric/wrong-…
ericdecanini Apr 7, 2022
981d157
Fixes errors in RoomSyncHandler
ericdecanini Apr 7, 2022
c1b1386
Replaces MatrixItem fromJson with User.toMatrixItem
ericdecanini Apr 7, 2022
6c3c309
Combines public initial and incremental handle sync methods into one
ericdecanini Apr 7, 2022
304e2f1
Improves code readability
ericdecanini Apr 7, 2022
669af4d
Removes unneeded method
ericdecanini Apr 7, 2022
4465525
Fixes import error
ericdecanini Apr 7, 2022
de8cc07
Removes unused import
ericdecanini Apr 7, 2022
b88eaf1
Removes usage of MatrixItem.UserItem internally
ericdecanini Apr 8, 2022
e80386b
Removes clearing usersToFetch in aggregator
ericdecanini Apr 13, 2022
f103d46
Adds exception safety to fetch users and makes it a single transaction
ericdecanini Apr 13, 2022
5d5a06b
Changes null check timing when fetching users
ericdecanini Apr 13, 2022
2337b18
Reorders val declaration of eventUserId and roomMember
ericdecanini Apr 13, 2022
8964b55
Adds empty check before saving user entities
ericdecanini Apr 13, 2022
5f949e9
Reformats not empty check
ericdecanini Apr 13, 2022
dd988d0
Simplifies handleIncrementalSync logic
ericdecanini Apr 13, 2022
8b80cf0
Restores debug log
ericdecanini Apr 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
eventId = roomMemberEvent.eventId
root = eventEntity
}
roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
roomMemberEventHandler.handleIncrementalSync(roomId, roomMemberEvent)
}
roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
roomSummaryUpdater.update(realm, roomId, updateMembers = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,31 @@ internal class RoomMemberEventHandler @Inject constructor(
@UserId private val myUserId: String
) {

fun handle(realm: Realm,
roomId: String,
event: Event,
aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
fun handleInitialSync(realm: Realm,
roomId: String,
currentUserId: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already injected on constructor (myUserId)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️👄👁️

event: Event,
aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
if (event.type != EventType.STATE_ROOM_MEMBER) {
return false
}
val roomMember = event.getFixedRoomMemberContent() ?: return false
val userId = event.stateKey ?: return false
val eventUserId = event.stateKey ?: return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why those 2 lines of code have been reordered? I think it's better to restore the original order since it's faster to first check for the stateKey.


return handle(realm, roomId, userId, roomMember, event.resolvedPrevContent(), aggregator)
return handleInitialSync(realm, roomId, currentUserId, eventUserId, roomMember, aggregator)
}

private fun handle(realm: Realm,
roomId: String,
userId: String,
roomMember: RoomMemberContent,
prevContent: Map<String, Any>?,
aggregator: SyncResponsePostTreatmentAggregator?): Boolean {
saveRoomMemberEntityLocally(realm, roomId, userId, roomMember)
saveUserEntityLocallyIfNecessary(realm, userId, roomMember, prevContent)
private fun handleInitialSync(realm: Realm,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having two methods with the same name, one public, one private is not ideal to me.
That's also a reason to keep only one public handle method.

roomId: String,
currentUserId: String,
eventUserId: String,
roomMember: RoomMemberContent,
aggregator: SyncResponsePostTreatmentAggregator?): Boolean {
if (currentUserId != eventUserId) {
saveRoomMemberEntityLocally(realm, roomId, eventUserId, roomMember)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always save RoomMember even if its our userId, but don't save User

saveUserEntityLocallyIfNecessary(realm, eventUserId, roomMember)
}

updateDirectChatsIfNecessary(roomId, roomMember, aggregator)
return true
}
Expand All @@ -72,15 +76,18 @@ internal class RoomMemberEventHandler @Inject constructor(
realm.insertOrUpdate(roomMemberEntity)
}

/**
* 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,
prevContent: Map<String, Any>?) {
val previousDisplayName = prevContent?.get("displayname")
val shouldLocallySaveUser = roomMember.membership.isActive() &&
(previousDisplayName == null || previousDisplayName == roomMember.displayName)

if (shouldLocallySaveUser) {
roomMember: RoomMemberContent) {
if (roomMember.membership.isActive()) {
saveUserLocally(realm, userId, roomMember)
}
}
Expand All @@ -101,12 +108,38 @@ internal class RoomMemberEventHandler @Inject constructor(
}
}

/**
* Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity
* by NULL value.
*/
fun handleIncrementalSync(roomId: String,
event: Event,
aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
if (event.type != EventType.STATE_ROOM_MEMBER) {
return false
}
val roomMember = event.getFixedRoomMemberContent() ?: return false
val eventUserId = event.stateKey ?: return false

private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? {
return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity
return handleIncrementalSync(roomId, eventUserId, roomMember, event.resolvedPrevContent(), aggregator)
}

private fun handleIncrementalSync(roomId: String,
eventUserId: String,
roomMember: RoomMemberContent,
prevContent: Map<String, Any>?,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be of type Content?

aggregator: SyncResponsePostTreatmentAggregator?): Boolean {
val previousDisplayName = prevContent?.get("displayname")
val previousAvatar = prevContent?.get("avatar_url")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add as? String on those 2 lines, so isDifferentFrom can just be removed and replaced by !=, WDYT?


if (previousDisplayName.isDifferentFrom(roomMember.displayName) ||
previousAvatar.isDifferentFrom(roomMember.avatarUrl)) {
aggregator?.usersToFetch?.add(eventUserId)
}

// At the end of the sync, fetch all the profiles from the aggregator
updateDirectChatsIfNecessary(roomId, roomMember, aggregator)
return true
}

private fun Any?.isDifferentFrom(value: Any?) = when {
this == null || this == value -> false
else -> true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 usersToFetch = mutableListOf<String>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,30 @@

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.session.profile.ProfileService
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.di.SessionDatabase
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

internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
private val directChatsHelper: DirectChatsHelper,
private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
private val updateUserAccountDataTask: UpdateUserAccountDataTask
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val profileService: ProfileService,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not inject Service in SDK classes. Services are meant to be used by the client app.
Instead you can inject getProfileInfoTask in this case.

@SessionDatabase private val monarchy: Monarchy,
) {
suspend fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) {
cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete)
updateDirectUserIds(synResHaResponsePostTreatmentAggregator.directChatsToCheck)
suspend fun handle(aggregator: SyncResponsePostTreatmentAggregator) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for having renamed this parameter which was quite typoed :)

cleanupEphemeralFiles(aggregator.ephemeralFilesToDelete)
updateDirectUserIds(aggregator.directChatsToCheck)
fetchAndUpdateUsers(aggregator.usersToFetch)
}

private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List<String>) {
Expand Down Expand Up @@ -68,4 +76,27 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
}
}

private suspend fun fetchAndUpdateUsers(usersToFetch: MutableList<String>) {
val userProfiles = fetchUsers(usersToFetch)
userProfiles.forEach { saveUserLocally(monarchy, it) }
usersToFetch.clear()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably not necessary to clear the list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't though, wouldn't we keep fetching the same users even when we don't need to fetch them anymore? I think it could increase bandwith and performance usage the longer the app is kept on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@bmarty bmarty Apr 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usersToFetch parameter could probably be of type List (instead of MutableList), like for the other methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh this wasn't clear to me at first. Changes pushed

}

private fun saveUserLocally(monarchy: Monarchy, userItem: MatrixItem.UserItem) {
val userEntity = UserEntityFactory.create(userItem)
monarchy.doWithRealm {
it.insertOrUpdate(userEntity)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to split code into several smaller methods, but it's maybe harder to read in this particular case.

What about replacing those 3 methods with a single one:

    private suspend fun fetchAndUpdateUsers(usersToFetch: List<String>) {
        usersToFetch
                .map { userId ->
                    val profileJson = profileService.getProfile(userId)
                    User.fromJson(userId, profileJson)
                }
                .map { user -> UserEntityFactory.create(user) }
                .let { userEntities ->
                    monarchy.doWithRealm {
                        it.insertOrUpdate(userEntities)
                    }
                }
    }

Also has the advantage to do on single Realm transaction instead of N transactions with your code.

WDYT?

Copy link
Contributor Author

@ericdecanini ericdecanini Apr 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer it split. The advantage is you can get the whole context of the action from the first method, and only if you're concerned about the details of the implementation do you need to read through the bottom methods.

I agree with making it a single Realm transaction though. I'll push changes to make this happen

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your latest implementation is better, thanks. Maybe just skip the transaction if the list is empty, which will be the case nearly all the time, or in case of error when fetching the profile.


private suspend fun fetchUsers(usersToFetch: MutableList<String>) = usersToFetch.map {
val profileJson = profileService.getProfile(it)

MatrixItem.UserItem(
id = it,
displayName = profileJson[ProfileService.DISPLAY_NAME_KEY] as? String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a method to reuse these json keys as it's used in several places

avatarUrl = profileJson[ProfileService.AVATAR_URL_KEY] as? String
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
insertType: EventInsertType,
syncLocalTimestampMillis: Long,
aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
Timber.v("Handle join sync for room $roomId")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is useful when debugging sync request. Can you restore it please?

val isInitialSync = insertType == EventInsertType.INITIAL_SYNC

val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed)
?._roomSyncEphemeral
Expand Down Expand Up @@ -241,7 +241,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
// Give info to crypto module
cryptoService.onStateEvent(roomId, event)
roomMemberEventHandler.handle(realm, roomId, event, aggregator)
if (isInitialSync) {
roomMemberEventHandler.handleInitialSync(realm, roomId, userId, event, aggregator)
} else {
roomMemberEventHandler.handleIncrementalSync(roomId, event, aggregator)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code, and the similar occurence, will be much simpler with an extra parameter (see my other remark)

}
}
if (roomSync.timeline?.events?.isNotEmpty() == true) {
Expand Down Expand Up @@ -282,7 +286,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
roomSync: InvitedRoomSync,
insertType: EventInsertType,
syncLocalTimestampMillis: Long): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I miss that at the previous review. Same remark:
This comment is useful when debugging sync request. Can you restore it please?

val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
Expand All @@ -296,7 +300,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
eventId = eventEntity.eventId
root = eventEntity
}
roomMemberEventHandler.handle(realm, roomId, event)
if (isInitialSync) {
roomMemberEventHandler.handleInitialSync(realm, roomId, userId, event)
} else {
roomMemberEventHandler.handleIncrementalSync(roomId, event)
}
}
}
val inviterEvent = roomSync.inviteState?.events?.lastOrNull {
Expand All @@ -312,6 +320,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) {
Expand All @@ -323,7 +332,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
eventId = event.eventId
root = eventEntity
}
roomMemberEventHandler.handle(realm, roomId, event)
if (isInitialSync) {
roomMemberEventHandler.handleInitialSync(realm, roomId, userId, event)
} else {
roomMemberEventHandler.handleIncrementalSync(roomId, event)
}
}
for (event in roomSync.timeline?.events.orEmpty()) {
if (event.eventId == null || event.senderId == null || event.type == null) {
Expand All @@ -337,7 +350,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
root = eventEntity
}
if (event.type == EventType.STATE_ROOM_MEMBER) {
roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
if (isInitialSync) {
roomMemberEventHandler.handleInitialSync(realm, roomEntity.roomId, userId, event)
} else {
roomMemberEventHandler.handleIncrementalSync(roomEntity.roomId, event)
}
}
}
}
Expand Down Expand Up @@ -381,11 +398,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)
Expand All @@ -405,7 +422,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
if (event.type == EventType.STATE_ROOM_MEMBER) {
roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent()
roomMemberEventHandler.handle(realm, roomEntity.roomId, event, aggregator)
if (isInitialSync) {
roomMemberEventHandler.handleInitialSync(realm, roomEntity.roomId, userId, event, aggregator)
} else {
roomMemberEventHandler.handleIncrementalSync(roomEntity.roomId, event, aggregator)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@
package org.matrix.android.sdk.internal.session.user

import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.database.model.UserEntity

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(userItem: MatrixItem.UserItem): UserEntity {
return UserEntity(
userId = userItem.id,
displayName = userItem.displayName.orEmpty(),
avatarUrl = userItem.avatarUrl.orEmpty()
)
}
}