Room : refactoring how a room is created

This commit is contained in:
ganfra 2019-01-28 18:42:29 +01:00
parent b2e2c14e69
commit 0f66fa0895
11 changed files with 110 additions and 144 deletions

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.api.session.room

import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService
@ -34,11 +33,6 @@ interface Room : TimelineService, SendService, ReadService {
*/
val roomId: String

/**
* The membership of this room for the current user
*/
val myMembership: MyMembership

/**
* A live [RoomSummary] associated with the room
*/

View File

@ -27,7 +27,7 @@ interface RoomService {
/**
* Get a room from a roomId
* @param roomId the roomId to look for.
* @return the room with roomId or null
* @return a room with roomId or null
*/
fun getRoom(roomId: String): Room?


View File

@ -1,37 +0,0 @@
/*
* 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.database.mapper

import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.session.room.DefaultRoom


internal object RoomMapper {


fun map(roomEntity: RoomEntity): Room {
return DefaultRoom(
roomEntity.roomId,
roomEntity.membership
)
}
}

internal fun RoomEntity.asDomain(): Room {
return RoomMapper.map(this)
}

View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.session.group.DefaultGroupService
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.DefaultRoomService
import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
import im.vector.matrix.android.internal.session.room.RoomFactory
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
@ -84,10 +85,9 @@ internal class SessionModule(private val sessionParams: SessionParams) {
}

scope(DefaultSession.SCOPE) {
DefaultRoomService(get()) as RoomService
DefaultRoomService(get(), get()) as RoomService
}


scope(DefaultSession.SCOPE) {
DefaultGroupService(get()) as GroupService
}

View File

@ -19,39 +19,35 @@ package im.vector.matrix.android.internal.session.room
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain
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.where
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import org.koin.core.parameter.parametersOf
import org.koin.standalone.inject

internal data class DefaultRoom(
override val roomId: String,
override val myMembership: MyMembership
) : Room, MatrixKoinComponent {
private val loadRoomMembersTask: LoadRoomMembersTask,
private val monarchy: Monarchy,
private val timelineService: TimelineService,
private val sendService: SendService,
private val readService: ReadService,
private val taskExecutor: TaskExecutor

private val loadRoomMembersTask by inject<LoadRoomMembersTask>()
private val monarchy by inject<Monarchy>()
private val timelineService by inject<TimelineService> { parametersOf(roomId) }
private val sendService by inject<SendService> { parametersOf(roomId) }
private val readService by inject<ReadService> { parametersOf(roomId) }
private val taskExecutor by inject<TaskExecutor>()

) : Room,
TimelineService by timelineService,
SendService by sendService,
ReadService by readService {

override val roomSummary: LiveData<RoomSummary> by lazy {
val liveData = monarchy
@ -64,35 +60,8 @@ internal data class DefaultRoom(
}
}

override fun timeline(eventId: String?): LiveData<TimelineData> {
return timelineService.timeline(eventId)
}

override fun loadRoomMembersIfNeeded(): Cancelable {
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
return loadRoomMembersTask.configureWith(params).executeBy(taskExecutor)
}


override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
return sendService.sendTextMessage(text, callback)
}

override fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>) {
readService.setReadReceipt(eventId, callback)
}

override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>) {
readService.setReadMarkers(fullyReadEventId, readReceiptEventId, callback)
}

override fun markAllAsRead(callback: MatrixCallback<Void>) {
readService.markAllAsRead(callback)
}

override fun markLatestAsRead(callback: MatrixCallback<Void>) {
readService.markLatestAsRead(callback)
}


}

View File

@ -26,15 +26,14 @@ 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.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.fetchManaged

internal class DefaultRoomService(private val monarchy: Monarchy) : RoomService {
internal class DefaultRoomService(private val monarchy: Monarchy,
private val roomFactory: RoomFactory) : RoomService {

override fun getRoom(roomId: String): Room? {
var room: Room? = null
monarchy.doWithRealm { realm ->
room = RoomEntity.where(realm, roomId).findFirst()?.asDomain()
}
return room
monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null
return roomFactory.instantiate(roomId)
}

override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {

View File

@ -20,11 +20,11 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
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.Room
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.last
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMembers
@ -34,20 +34,21 @@ internal class RoomAvatarResolver(private val monarchy: Monarchy,

/**
* Compute the room avatar url
*
* @param roomId the roomId of the room to resolve avatar
* @return the room avatar url, can be a fallback to a room member avatar or null
*/
fun resolve(room: Room): String? {
fun resolve(roomId: String): String? {
var res: String? = null
monarchy.doWithRealm { realm ->
val roomName = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_AVATAR).last()?.asDomain()
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).last()?.asDomain()
res = roomName?.content.toModel<RoomAvatarContent>()?.avatarUrl
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
val roomMembers = RoomMembers(realm, room.roomId)
val roomMembers = RoomMembers(realm, roomId)
val members = roomMembers.getLoaded()
if (room.myMembership == MyMembership.INVITED) {
if (roomEntity?.membership == MyMembership.INVITED) {
if (members.size == 1) {
res = members.entries.first().value.avatarUrl
} else if (members.size > 1) {

View File

@ -0,0 +1,61 @@
/*
* 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.room

import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.send.EventFactory
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.PagingRequestHelper
import java.util.concurrent.Executors

internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
private val monarchy: Monarchy,
private val paginationTask: PaginationTask,
private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask,
private val eventFactory: EventFactory,
private val taskExecutor: TaskExecutor) {

fun instantiate(roomId: String): Room {
val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, helper)
val roomMemberExtractor = RoomMemberExtractor(monarchy, roomId)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineBoundaryCallback, contextOfEventTask, roomMemberExtractor)
val sendService = DefaultSendService(roomId, eventFactory, monarchy)
val readService = DefaultReadService(roomId, monarchy, setReadMarkersTask, taskExecutor)
return DefaultRoom(
roomId,
loadRoomMembersTask,
monarchy,
timelineService,
sendService,
readService,
taskExecutor
)
}

}

View File

@ -17,29 +17,19 @@
package im.vector.matrix.android.internal.session.room

import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.send.EventFactory
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
import im.vector.matrix.android.internal.util.PagingRequestHelper
import org.koin.dsl.module.module
import retrofit2.Retrofit
import java.util.concurrent.Executors


class RoomModule {
@ -76,19 +66,8 @@ class RoomModule {
EventFactory(sessionParams.credentials)
}

factory { (roomId: String) ->
val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper)
val roomMemberExtractor = RoomMemberExtractor(get(), roomId)
DefaultTimelineService(roomId, get(), get(), timelineBoundaryCallback, get(), roomMemberExtractor) as TimelineService
}

factory { (roomId: String) ->
DefaultSendService(roomId, get(), get()) as SendService
}

factory { (roomId: String) ->
DefaultReadService(roomId, get(), get(), get()) as ReadService
scope(DefaultSession.SCOPE) {
RoomFactory(get(), get(), get(), get(), get(), get(), get())
}

}

View File

@ -21,7 +21,6 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
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.Room
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
@ -45,26 +44,26 @@ internal class RoomSummaryUpdater(monarchy: Monarchy,
override val query = Monarchy.Query<RoomEntity> { RoomEntity.where(it) }

override fun process(inserted: List<RoomEntity>, updated: List<RoomEntity>, deleted: List<RoomEntity>) {
val rooms = (inserted + updated).map { it.asDomain() }
val rooms = (inserted + updated).map { it.roomId }
monarchy.writeAsync { realm ->
rooms.forEach { updateRoom(realm, it) }
}
}

private fun updateRoom(realm: Realm, room: Room?) {
if (room == null) {
private fun updateRoom(realm: Realm, roomId: String?) {
if (roomId == null) {
return
}
val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst()
?: realm.createObject(room.roomId)
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)

val lastMessageEvent = EventEntity.where(realm, room.roomId, EventType.MESSAGE).last()
val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain()
val lastMessageEvent = EventEntity.where(realm, roomId, EventType.MESSAGE).last()
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain()

val otherRoomMembers = RoomMembers(realm, room.roomId).getLoaded().filterKeys { it != credentials.userId }
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }

roomSummary.displayName = roomDisplayNameResolver.resolve(context, room).toString()
roomSummary.avatarUrl = roomAvatarResolver.resolve(room)
roomSummary.displayName = roomDisplayNameResolver.resolve(context, roomId).toString()
roomSummary.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummary.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummary.lastMessage = lastMessageEvent
roomSummary.otherMemberIds.clear()

View File

@ -22,13 +22,13 @@ import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials
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.Room
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
import im.vector.matrix.android.api.session.room.model.RoomNameContent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
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.last
import im.vector.matrix.android.internal.database.query.where
@ -45,10 +45,10 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy,
* Compute the room display name
*
* @param context
* @param room: the room to resolve the name of.
* @param roomId: the roomId to resolve the name of.
* @return the room display name
*/
fun resolve(context: Context, room: Room): CharSequence {
fun resolve(context: Context, roomId: String): CharSequence {
// this algorithm is the one defined in
// https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617
// calculateRoomName(room, userId)
@ -57,29 +57,30 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy,
// https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#heading=h.qif6pkqyjgzn
var name: CharSequence? = null
monarchy.doWithRealm { realm ->
val roomName = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_NAME).last()?.asDomain()
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).last()?.asDomain()
name = roomName?.content.toModel<RoomNameContent>()?.name
if (!name.isNullOrEmpty()) {
return@doWithRealm
}

val canonicalAlias = EventEntity.where(realm, room.roomId, EventType.STATE_CANONICAL_ALIAS).last()?.asDomain()
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).last()?.asDomain()
name = canonicalAlias?.content.toModel<RoomCanonicalAliasContent>()?.canonicalAlias
if (!name.isNullOrEmpty()) {
return@doWithRealm
}

val aliases = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_ALIASES).last()?.asDomain()
val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).last()?.asDomain()
name = aliases?.content.toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
if (!name.isNullOrEmpty()) {
return@doWithRealm
}

val roomMembers = RoomMembers(realm, room.roomId)
val roomMembers = RoomMembers(realm, roomId)
val otherRoomMembers = roomMembers.getLoaded()
.filterKeys { it != credentials.userId }

if (room.myMembership == MyMembership.INVITED) {
if (roomEntity?.membership == MyMembership.INVITED) {
//TODO handle invited
/*
if (currentUser != null
@ -94,7 +95,7 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy,
name = context.getString(R.string.room_displayname_room_invite)
} else {

val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst()
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes
} else {
@ -125,6 +126,6 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy,
}
return@doWithRealm
}
return name ?: room.roomId
return name ?: roomId
}
}