Direct chat: handle user account data

This commit is contained in:
ganfra 2019-07-22 18:58:55 +02:00
parent 2c81e41288
commit 151ae7f4dd
18 changed files with 433 additions and 92 deletions

View File

@ -32,6 +32,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0,
var isDirect: Boolean = false,
var directUserId: String? = null,
var otherMemberIds: RealmList<String> = RealmList(),
var notificationCount: Int = 0,
var highlightCount: Int = 0,

View File

@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.kotlin.where

internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
@ -29,3 +30,20 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n
}
return query
}

internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> {
return RoomSummaryEntity.where(realm)
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.findAll()
}

internal fun RoomSummaryEntity.Companion.isDirect(realm: Realm, roomId: String): Boolean {
return RoomSummaryEntity.where(realm)
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.findAll()
.isNotEmpty()
}




View File

@ -65,11 +65,12 @@ internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm:

internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
roomId: String,
includesSending: Boolean,
includedTypes: List<String> = emptyList(),
excludedTypes: List<String> = emptyList()): TimelineEventEntity? {

val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) {
val eventList = if (includesSending && roomEntity.sendingTimelineEvents.isNotEmpty()) {
roomEntity.sendingTimelineEvents
} else {
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents

View File

@ -19,11 +19,11 @@ package im.vector.matrix.android.internal.session
import dagger.BindsInstance
import dagger.Component
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.InitialSyncProgressService
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.CryptoModule
import im.vector.matrix.android.internal.di.MatrixComponent
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
import im.vector.matrix.android.internal.session.cache.CacheModule
import im.vector.matrix.android.internal.session.content.ContentModule
import im.vector.matrix.android.internal.session.content.UploadContentWorker
@ -46,20 +46,21 @@ import im.vector.matrix.android.internal.session.user.UserModule
import im.vector.matrix.android.internal.task.TaskExecutor

@Component(dependencies = [MatrixComponent::class],
modules = [
SessionModule::class,
RoomModule::class,
SyncModule::class,
SignOutModule::class,
GroupModule::class,
UserModule::class,
FilterModule::class,
GroupModule::class,
ContentModule::class,
CacheModule::class,
CryptoModule::class,
PushersModule::class
]
modules = [
SessionModule::class,
RoomModule::class,
SyncModule::class,
SignOutModule::class,
GroupModule::class,
UserModule::class,
FilterModule::class,
GroupModule::class,
ContentModule::class,
CacheModule::class,
CryptoModule::class,
PushersModule::class,
AccountDataModule::class
]
)
@SessionScope
internal interface SessionComponent {

View File

@ -62,7 +62,9 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomId: String,
membership: Membership? = null,
roomSummary: RoomSyncSummary? = null,
unreadNotifications: RoomSyncUnreadNotifications? = null) {
unreadNotifications: RoomSyncUnreadNotifications? = null,
isDirect: Boolean? = null,
directUserId: String? = null) {
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)

@ -85,7 +87,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomSummaryEntity.membership = membership
}

val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, includedTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()

val otherRoomMembers = RoomMembers(realm, roomId)
@ -95,6 +97,10 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
.asSequence()
.map { it.stateKey }

if (isDirect != null) {
roomSummaryEntity.isDirect = isDirect
roomSummaryEntity.directUserId = directUserId
}
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic

View File

@ -17,22 +17,32 @@
package im.vector.matrix.android.internal.session.room.create

import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.internal.database.RealmQueryLatch
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntityFields
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.RealmConfiguration
import javax.inject.Inject

internal interface CreateRoomTask : Task<CreateRoomParams, String>

internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: RoomAPI,
private val monarchy: Monarchy,
private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase private val realmConfiguration: RealmConfiguration) : CreateRoomTask {


@ -41,17 +51,48 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
apiCall = roomAPI.createRoom(params)
}.flatMap { createRoomResponse ->
val roomId = createRoomResponse.roomId!!

// TODO Maybe do the same code for join room request ?
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId)
}

rql.await()

return Try.just(roomId)
Try.just(roomId)
}.flatMap { roomId ->
if (params.isDirect()) {
handleDirectChatCreation(params, roomId)
} else {
Try.just(roomId)
}
}.flatMap { roomId ->
setReadMarkers(roomId)
}
}

private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String): Try<String> {
val otherUserId = params.getFirstInvitedUserId()
?: return Try.raise(IllegalStateException("You can't create a direct room without an invitedUser"))

return monarchy.tryTransactionSync { realm ->
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
this.directUserId = otherUserId
this.isDirect = true
}
}.flatMap {
val directChats = directChatsHelper.getDirectChats()
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
}.flatMap {
Try.just(roomId)
}
}

private suspend fun setReadMarkers(roomId: String): Try<String> {
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
return readMarkersTask
.execute(setReadMarkerParams)
.flatMap {
Try.just(roomId)
}
}

}

View File

@ -34,7 +34,6 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import io.realm.RealmResults
import javax.inject.Inject

/**
@ -81,10 +80,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:

val roomMembers = RoomMembers(realm, roomId)
val loadedMembers = roomMembers.queryRoomMembersEvent().findAll()
val otherMembersSubset = loadedMembers.where()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
.limit(3)
.findAll()


if (roomEntity?.membership == Membership.INVITE) {
val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst()
@ -97,23 +93,29 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
} else {
context.getString(R.string.room_displayname_room_invite)
}
} else {
} else if (roomEntity?.membership == Membership.JOIN) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val memberIds: List<String> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes
val otherMembersSubset: List<EventEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes.mapNotNull {
roomMembers.getStateEvent(it)
}
} else {
otherMembersSubset.mapNotNull { it.stateKey }
loadedMembers.where()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
.limit(3)
.findAll()
}
name = when (memberIds.size) {
val otherMembersCount = roomMembers.getNumberOfMembers() - 1
name = when (otherMembersCount) {
0 -> context.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMember(otherMembersSubset[0], roomMembers)
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
2 -> context.getString(R.string.room_displayname_two_members,
resolveRoomMember(otherMembersSubset[0], roomMembers),
resolveRoomMember(otherMembersSubset[1], roomMembers)
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
)
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1,
resolveRoomMember(otherMembersSubset[0], roomMembers),
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
roomMembers.getNumberOfJoinedMembers() - 1)
}
}
@ -122,8 +124,8 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
return name ?: roomId
}

private fun resolveRoomMember(eventEntity: EventEntity?,
roomMembers: RoomMembers): String? {
private fun resolveRoomMemberName(eventEntity: EventEntity?,
roomMembers: RoomMembers): String? {
if (eventEntity == null) return null
val roomMember = eventEntity.toRoomMember() ?: return null
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)

View File

@ -42,12 +42,16 @@ internal class RoomMembers(private val realm: Realm,
RoomSummaryEntity.where(realm, roomId).findFirst()
}

fun get(userId: String): RoomMember? {
fun getStateEvent(userId: String): EventEntity? {
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.equalTo(EventEntityFields.STATE_KEY, userId)
.findFirst()
}

fun get(userId: String): RoomMember? {
return getStateEvent(userId)
?.let {
it.asDomain().content?.toModel<RoomMember>()
}

View File

@ -17,10 +17,15 @@
package im.vector.matrix.android.internal.session.room.membership.joining

import arrow.core.Try
import im.vector.matrix.android.internal.database.RealmQueryLatch
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntityFields
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.task.Task
import io.realm.RealmConfiguration
import javax.inject.Inject

internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
@ -29,12 +34,30 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
)
}

internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI) : JoinRoomTask {
internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI,
private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase private val realmConfiguration: RealmConfiguration) : JoinRoomTask {

override suspend fun execute(params: JoinRoomTask.Params): Try<Unit> {
return executeRequest {
return executeRequest<Unit> {
apiCall = roomAPI.join(params.roomId, HashMap())
}.flatMap {
val roomId = params.roomId
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId)
}
rql.await()
Try.just(roomId)
}.flatMap { roomId ->
setReadMarkers(roomId)
}
}

private suspend fun setReadMarkers(roomId: String): Try<Unit> {
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
return readMarkersTask.execute(setReadMarkerParams)
}

}

View File

@ -22,14 +22,11 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchCopied
import javax.inject.Inject

internal class DefaultReadService @Inject constructor(private val roomId: String,
@ -39,9 +36,7 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
private val credentials: Credentials) : ReadService {

override fun markAllAsRead(callback: MatrixCallback<Unit>) {
//TODO shouldn't it be latest synced event?
val latestEvent = getLatestEvent()
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId)
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
}

@ -55,9 +50,6 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
}

private fun getLatestEvent(): TimelineEventEntity? {
return monarchy.fetchCopied { TimelineEventEntity.latestEvent(it, roomId) }
}

override fun isEventRead(eventId: String): Boolean {
var isEventRead = false

View File

@ -20,7 +20,6 @@ import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
@ -33,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionAsync
import io.realm.Realm
import timber.log.Timber
import javax.inject.Inject

@ -40,8 +40,9 @@ internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> {

data class Params(
val roomId: String,
val fullyReadEventId: String?,
val readReceiptEventId: String?
val markAllAsRead: Boolean = false,
val fullyReadEventId: String? = null,
val readReceiptEventId: String? = null
)
}

@ -55,21 +56,35 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI

override suspend fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
val markers = HashMap<String, String>()
if (params.fullyReadEventId != null) {
if (LocalEchoEventFactory.isLocalEchoId(params.fullyReadEventId)) {
val fullyReadEventId: String?
val readReceiptEventId: String?

if (params.markAllAsRead) {
val latestSyncedEventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
TimelineEventEntity.latestEvent(realm, roomId = params.roomId, includesSending = false)?.eventId
}
fullyReadEventId = latestSyncedEventId
readReceiptEventId = latestSyncedEventId
} else {
fullyReadEventId = params.fullyReadEventId
readReceiptEventId = params.readReceiptEventId
}

if (fullyReadEventId != null) {
if (LocalEchoEventFactory.isLocalEchoId(fullyReadEventId)) {
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
} else {
markers[READ_MARKER] = params.fullyReadEventId
markers[READ_MARKER] = fullyReadEventId
}
}
if (params.readReceiptEventId != null
&& !isEventRead(params.roomId, params.readReceiptEventId)) {
if (readReceiptEventId != null
&& !isEventRead(params.roomId, readReceiptEventId)) {

if (LocalEchoEventFactory.isLocalEchoId(params.readReceiptEventId)) {
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
Timber.w("Can't set read receipt for local event ${params.fullyReadEventId}")
} else {
updateNotificationCountIfNecessary(params.roomId, params.readReceiptEventId)
markers[READ_RECEIPT] = params.readReceiptEventId
updateNotificationCountIfNecessary(params.roomId, readReceiptEventId)
markers[READ_RECEIPT] = readReceiptEventId
}
}
return if (markers.isEmpty()) {
@ -83,10 +98,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI

private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
monarchy.tryTransactionAsync { realm ->
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId)?.eventId == eventId
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == eventId
if (isLatestReceived) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: return@tryTransactionAsync
?: return@tryTransactionAsync
roomSummary.notificationCount = 0
roomSummary.highlightCount = 0
}
@ -97,13 +112,13 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
var isEventRead = false
monarchy.doWithRealm {
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
?: return@doWithRealm
?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
?: return@doWithRealm
?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
?: Int.MIN_VALUE
?: Int.MIN_VALUE
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
?: Int.MAX_VALUE
?: Int.MAX_VALUE
isEventRead = eventToCheckIndex <= readReceiptIndex
}
return isEventRead

View File

@ -18,27 +18,41 @@ package im.vector.matrix.android.internal.session.sync

import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvent
import im.vector.matrix.android.internal.database.helper.updateSenderDataFor
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.isDirect
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.session.mapWithProgress
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.session.sync.model.*
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.RoomSync
import im.vector.matrix.android.internal.session.sync.model.RoomSyncAccountData
import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -55,6 +69,9 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private val tokenStore: SyncTokenStore,
private val pushRuleService: DefaultPushRuleService,
private val processForPushTask: ProcessEventForPushTask,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val credentials: Credentials,
private val directChatsHelper: DirectChatsHelper,
private val taskExecutor: TaskExecutor) {

sealed class HandlingStrategy {
@ -118,7 +135,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
Timber.v("Handle join sync for room $roomId")

val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)

if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm()
@ -128,7 +145,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
// State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
?: Int.MIN_VALUE
?: Int.MIN_VALUE
val untimelinedStateIndex = minStateIndex + 1
roomSync.state.events.forEach { event ->
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
@ -169,13 +186,27 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
roomEntity.addOrUpdate(chunkEntity)
}
roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(credentials.userId)
val inviterId = myUserStateEvent?.sender
val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
val isDirect = myUserRoomMember?.isDirect
if (isDirect == true && inviterId != null) {
val isAlreadyDirect = RoomSummaryEntity.isDirect(realm, roomId)
if (!isAlreadyDirect) {
val directChatsMap = directChatsHelper.getDirectChats(include = Pair(inviterId, roomId))
val updateUserAccountParams = UpdateUserAccountDataTask.DirectChatParams(
directMessages = directChatsMap
)
updateUserAccountDataTask.configureWith(updateUserAccountParams).executeBy(taskExecutor)
}
}
roomSummaryUpdater.update(realm, roomId, Membership.INVITE, isDirect = isDirect, directUserId = inviterId)
return roomEntity
}

@ -183,7 +214,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomId: String,
roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)

roomEntity.membership = Membership.LEAVE
roomEntity.chunks.deleteAllFromRealm()

View File

@ -19,8 +19,8 @@ package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync
import javax.inject.Inject
@ -37,19 +37,22 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
}

private fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) {
val newDirectRoomIds = directMessages.content.values.flatten()
monarchy.runTransactionSync { realm ->

val oldDirectRooms = RoomSummaryEntity.where(realm)
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.findAll()
oldDirectRooms.forEach { it.isDirect = false }

newDirectRoomIds.forEach { roomId ->
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
if (roomSummaryEntity != null) {
roomSummaryEntity.isDirect = true
realm.insertOrUpdate(roomSummaryEntity)
val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
oldDirectRooms.forEach {
it.isDirect = false
it.directUserId = null
}
directMessages.content.forEach {
val userId = it.key
it.value.forEach { roomId ->
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
if (roomSummaryEntity != null) {
roomSummaryEntity.isDirect = true
roomSummaryEntity.directUserId = userId
realm.insertOrUpdate(roomSummaryEntity)
}
}
}
}

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.user
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.model.UserEntity

@ -29,6 +30,10 @@ internal object UserEntityFactory {
return null
}
val roomMember = event.content.toModel<RoomMember>() ?: return null
// We only use JOIN and INVITED memberships to create User data
if (roomMember.membership != Membership.JOIN || roomMember.membership != Membership.INVITE) {
return null
}
return UserEntity(event.stateKey ?: "",
roomMember.displayName ?: "",
roomMember.avatarUrl ?: ""

View File

@ -0,0 +1,48 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.session.user.accountdata

import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path

interface AccountDataAPI {

/**
* Set some account_data for the client.
*
* @param userId the user id
* @param type the type
* @param params the put params
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}")
fun setAccountData(@Path("userId") userId: String, @Path("type") type: String, @Body params: Any): Call<Unit>

/**
* Gets a bearer token from the homeserver that the user can
* present to a third party in order to prove their ownership
* of the Matrix account they are logged into.
*
* @param userId the user id
* @param body the body content
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
fun openIdToken(@Path("userId") userId: String, @Body body: Map<Any, Any>): Call<Map<Any, Any>>
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.session.user.accountdata

import dagger.Binds
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit

@Module
internal abstract class AccountDataModule {

@Module
companion object {

@JvmStatic
@Provides
fun providesAccountDataAPI(retrofit: Retrofit): AccountDataAPI {
return retrofit.create(AccountDataAPI::class.java)
}

}

@Binds
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAcountDataTask): UpdateUserAccountDataTask

}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.session.user.accountdata

import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.di.SessionDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import timber.log.Timber
import javax.inject.Inject

internal class DirectChatsHelper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {

fun getDirectChats(include: Pair<String, String>? = null, filterRoomId: String? = null): Map<String, List<String>> {
return Realm.getInstance(realmConfiguration).use { realm ->
val currentDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
val directChatsMap = mutableMapOf<String, MutableList<String>>()
for (directRoom in currentDirectRooms) {
if (directRoom.roomId == filterRoomId) continue
val directUserId = directRoom.directUserId ?: continue
directChatsMap.getOrPut(directUserId, { arrayListOf() }).apply {
add(directRoom.roomId)
}
}
if (include != null) {
directChatsMap.getOrPut(include.first, { arrayListOf() }).apply {
if (contains(include.second)) {
Timber.v("Direct chats already include room ${include.second} with user ${include.first}")
} else {
add(include.second)
}
}
}
directChatsMap
}
}


}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.session.user.accountdata

import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.sync.model.UserAccountData
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject

internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Params, Unit> {

interface Params {
val type: String
fun getData(): Any
}

data class DirectChatParams(override val type: String = UserAccountData.TYPE_DIRECT_MESSAGES,
private val directMessages: Map<String, List<String>>
) : Params {

override fun getData(): Any {
return directMessages
}
}


}

internal class DefaultUpdateUserAcountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
private val credentials: Credentials) : UpdateUserAccountDataTask {

override suspend fun execute(params: UpdateUserAccountDataTask.Params): Try<Unit> {

return executeRequest {
apiCall = accountDataApi.setAccountData(credentials.userId, params.type, params.getData())
}
}

}