Sending : add a basic local echo (still need to define sending states)

This commit is contained in:
ganfra 2018-11-09 18:32:35 +01:00
parent c8a987f932
commit a4abd20e73
11 changed files with 155 additions and 62 deletions

View File

@ -1,12 +1,12 @@
package im.vector.matrix.android.api.session.events.model package im.vector.matrix.android.api.session.events.model


import com.google.gson.JsonObject
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.legacy.util.JsonUtils import java.lang.reflect.ParameterizedType


typealias Content = Map<String, Any> typealias Content = Map<String, @JvmSuppressWildcards Any>


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Event( data class Event(
@ -23,16 +23,6 @@ data class Event(


) { ) {


val contentAsJsonObject: JsonObject? by lazy {
val gson = JsonUtils.getGson(true)
gson.toJsonTree(content).asJsonObject
}

val prevContentAsJsonObject: JsonObject? by lazy {
val gson = JsonUtils.getGson(true)
gson.toJsonTree(prevContent).asJsonObject
}

inline fun <reified T> content(): T? { inline fun <reified T> content(): T? {
return toModel(content) return toModel(content)
} }
@ -47,4 +37,8 @@ data class Event(
return moshiAdapter.fromJsonValue(data) return moshiAdapter.fromJsonValue(data)
} }


companion object {
val CONTENT_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
}

} }

View File

@ -1,16 +1,24 @@
package im.vector.matrix.android.internal.database.helper package im.vector.matrix.android.internal.database.helper


import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.mapper.fillWith
import im.vector.matrix.android.internal.database.model.ChunkEntity 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.query.fastContains import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.database.query.where
import io.realm.kotlin.createObject


internal fun List<Event>.addManagedToChunk(chunkEntity: ChunkEntity) { internal fun List<Event>.addManagedToChunk(chunkEntity: ChunkEntity) {
if (!chunkEntity.isManaged) { if (!chunkEntity.isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains") throw IllegalStateException("Chunk entity should be managed to use fast contains")
} }
val realm = chunkEntity.realm
this.forEach { event -> this.forEach { event ->
val eventEntity = event.asEntity() val eventEntity = EventEntity.where(realm, event.eventId).findFirst()
?: realm.createObject()

eventEntity.fillWith(event)

if (!chunkEntity.events.fastContains(eventEntity)) { if (!chunkEntity.events.fastContains(eventEntity)) {
chunkEntity.events.add(eventEntity) chunkEntity.events.add(eventEntity)
} }

View File

@ -1,6 +1,5 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper


import com.squareup.moshi.Types
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
@ -10,21 +9,11 @@ import im.vector.matrix.android.internal.di.MoshiProvider
internal object EventMapper { internal object EventMapper {


private val moshi = MoshiProvider.providesMoshi() private val moshi = MoshiProvider.providesMoshi()
private val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) private val adapter = moshi.adapter<Map<String, Any>>(Event.CONTENT_TYPE)
private val adapter = moshi.adapter<Map<String, Any>>(type)


fun map(event: Event): EventEntity { fun map(event: Event): EventEntity {
val eventEntity = EventEntity() val eventEntity = EventEntity()
eventEntity.eventId = event.eventId ?: "" fill(eventEntity, with = event)
eventEntity.content = adapter.toJson(event.content)
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
eventEntity.prevContent = adapter.toJson(resolvedPrevContent)
eventEntity.stateKey = event.stateKey
eventEntity.type = event.type
eventEntity.sender = event.sender
eventEntity.originServerTs = event.originServerTs
eventEntity.redacts = event.redacts
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
return eventEntity return eventEntity
} }


@ -42,6 +31,20 @@ internal object EventMapper {
redacts = eventEntity.redacts redacts = eventEntity.redacts
) )
} }

fun fill(eventEntity: EventEntity, with: Event) {
eventEntity.eventId = with.eventId ?: ""
eventEntity.content = adapter.toJson(with.content)
val resolvedPrevContent = with.prevContent ?: with.unsignedData?.prevContent
eventEntity.prevContent = adapter.toJson(resolvedPrevContent)
eventEntity.stateKey = with.stateKey
eventEntity.type = with.type
eventEntity.sender = with.sender
eventEntity.originServerTs = with.originServerTs
eventEntity.redacts = with.redacts
eventEntity.age = with.unsignedData?.age ?: with.originServerTs
}

} }


internal fun EventEntity.asDomain(): Event { internal fun EventEntity.asDomain(): Event {
@ -51,3 +54,7 @@ internal fun EventEntity.asDomain(): Event {
internal fun Event.asEntity(): EventEntity { internal fun Event.asEntity(): EventEntity {
return EventMapper.map(this) return EventMapper.map(this)
} }

internal fun EventEntity.fillWith(event: Event) {
EventMapper.fill(this, with = event)
}

View File

@ -3,17 +3,16 @@ package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey


internal open class EventEntity(@PrimaryKey var eventId: String = "", internal open class EventEntity(var eventId: String = "",
var type: String = "", var type: String = "",
var content: String = "", var content: String = "",
var prevContent: String? = null, var prevContent: String? = null,
var stateKey: String? = null, var stateKey: String? = null,
var originServerTs: Long? = null, var originServerTs: Long? = null,
var sender: String? = null, var sender: String? = null,
var age: Long? = 0, var age: Long? = 0,
var redacts: String? = null var redacts: String? = null
) : RealmObject() { ) : RealmObject() {


companion object companion object

View File

@ -23,6 +23,17 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module


override fun invoke(): ModuleDefinition = module(override = true) { override fun invoke(): ModuleDefinition = module(override = true) {


scope(DefaultSession.SCOPE) {
sessionParams
}

scope(DefaultSession.SCOPE) {
RealmConfiguration.Builder()
.name(sessionParams.credentials.userId)
.deleteRealmIfMigrationNeeded()
.build()
}

scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RealmConfiguration.Builder() RealmConfiguration.Builder()
.name(sessionParams.credentials.userId) .name(sessionParams.credentials.userId)

View File

@ -9,7 +9,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.Event 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.EventType
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.mapper.fillWith
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
@ -48,16 +48,18 @@ internal class PruneEventWorker(context: Context,
if (redactionEvent == null || redactionEvent.redacts.isNullOrEmpty()) { if (redactionEvent == null || redactionEvent.redacts.isNullOrEmpty()) {
return return
} }
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()?.asDomain() val eventToPruneEntity = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
?: return ?: return


val eventToPrune = eventToPruneEntity.asDomain()

val allowedKeys = computeAllowedKeys(eventToPrune.type) val allowedKeys = computeAllowedKeys(eventToPrune.type)
val prunedContent = allowedKeys.fold( val prunedContent = allowedKeys.fold(
{ eventToPrune.content }, { eventToPrune.content },
{ eventToPrune.content?.filterKeys { key -> it.contains(key) } } { eventToPrune.content?.filterKeys { key -> it.contains(key) } }
) )
val eventToPruneEntity = eventToPrune.copy(content = prunedContent).asEntity() val prunedEvent = eventToPrune.copy(content = prunedContent)
realm.insertOrUpdate(eventToPruneEntity) eventToPruneEntity.fillWith(prunedEvent)
} }


private fun computeAllowedKeys(type: String): Option<List<String>> { private fun computeAllowedKeys(type: String): Option<List<String>> {

View File

@ -1,16 +1,12 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room


import im.vector.matrix.android.api.session.room.model.MessageContent import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.*
import retrofit2.http.GET
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query


internal interface RoomAPI { internal interface RoomAPI {


@ -60,7 +56,7 @@ internal interface RoomAPI {
fun send(@Path("txId") txId: String, fun send(@Path("txId") txId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventType") eventType: String, @Path("eventType") eventType: String,
@Body content: MessageContent @Body content: Content?
): Call<SendResponse> ): Call<SendResponse>





View File

@ -1,10 +1,12 @@
package im.vector.matrix.android.internal.session.room 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.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
import im.vector.matrix.android.internal.session.room.send.DefaultSendService 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.DefaultTimelineHolder import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder
import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
@ -32,6 +34,10 @@ class RoomModule : Module {
PaginationRequest(get(), get(), get(), get()) PaginationRequest(get(), get(), get(), get())
} }


scope(DefaultSession.SCOPE) {
val sessionParams = get<SessionParams>()
EventFactory(sessionParams.credentials)
}


factory { factory {
val roomId: String = it[0] val roomId: String = it[0]
@ -41,7 +47,7 @@ class RoomModule : Module {


factory { factory {
val roomId: String = it[0] val roomId: String = it[0]
DefaultSendService(roomId) as SendService DefaultSendService(roomId, get(), get()) as SendService
} }


}.invoke() }.invoke()

View File

@ -1,23 +1,37 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send


import androidx.work.* import androidx.work.*
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addManagedToChunk
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import im.vector.matrix.android.internal.util.tryTransactionAsync
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


private const val SEND_WORK = "SEND_WORK" private const val SEND_WORK = "SEND_WORK"


internal class DefaultSendService(private val roomId: String) : SendService { internal class DefaultSendService(private val roomId: String,
private val eventFactory: EventFactory,
private val monarchy: Monarchy) : SendService {


private val sendConstraints = Constraints.Builder() private val sendConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()


override fun sendTextMessage(text: String): Cancelable { override fun sendTextMessage(text: String): Cancelable {
val event = eventFactory.createTextEvent(roomId, text)


val sendContentWorkerParams = SendEventWorker.Params(roomId, text) monarchy.tryTransactionAsync { realm ->
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
?: return@tryTransactionAsync
listOf(event).addManagedToChunk(chunkEntity)
}

val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
val workData = WorkerParamsFactory.toData(sendContentWorkerParams) val workData = WorkerParamsFactory.toData(sendContentWorkerParams)


val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>() val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>()

View File

@ -0,0 +1,44 @@
package im.vector.matrix.android.internal.session.room.send

import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.Content
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.room.model.MessageContent
import im.vector.matrix.android.api.session.room.model.MessageType
import im.vector.matrix.android.internal.di.MoshiProvider

internal class EventFactory(private val credentials: Credentials) {

private val moshi = MoshiProvider.providesMoshi()

fun createTextEvent(roomId: String, text: String): Event {
val content = MessageContent(type = MessageType.MSGTYPE_TEXT, body = text)

return Event(
roomId = roomId,
originServerTs = dummyOriginServerTs(),
sender = credentials.userId,
eventId = dummyEventId(roomId),
type = EventType.MESSAGE,
content = toContent(content)
)
}

private fun dummyOriginServerTs(): Long {
return System.currentTimeMillis()
}

private fun dummyEventId(roomId: String): String {
return roomId + "-" + dummyOriginServerTs()
}

@Suppress("UNCHECKED_CAST")
private inline fun <reified T> toContent(data: T?): Content? {
val moshiAdapter = moshi.adapter(T::class.java)
val jsonValue = moshiAdapter.toJsonValue(data)
return jsonValue as? Content?
}


}

View File

@ -4,12 +4,14 @@ import android.content.Context
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.EventType import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.MessageContent import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.MessageType import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import im.vector.matrix.android.internal.util.tryTransactionSync
import org.koin.standalone.KoinComponent import org.koin.standalone.KoinComponent
import org.koin.standalone.inject import org.koin.standalone.inject


@ -20,25 +22,35 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
val roomId: String, val roomId: String,
val text: String val event: Event
) )


private val roomAPI by inject<RoomAPI>() private val roomAPI by inject<RoomAPI>()
private val monarchy by inject<Monarchy>()


override fun doWork(): Result { override fun doWork(): Result {


val sendWorkerParameters = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.FAILURE ?: return Result.FAILURE


val fakeId = sendWorkerParameters.roomId + "-" + System.currentTimeMillis() if (params.event.eventId == null) {
return Result.FAILURE
}

val result = executeRequest<SendResponse> { val result = executeRequest<SendResponse> {
apiCall = roomAPI.send( apiCall = roomAPI.send(
fakeId, params.event.eventId,
sendWorkerParameters.roomId, params.roomId,
EventType.MESSAGE, params.event.type,
MessageContent(MessageType.MSGTYPE_TEXT, sendWorkerParameters.text) params.event.content
) )
} }
result.flatMap { sendResponse ->
monarchy.tryTransactionSync { realm ->
val dummyEventEntity = EventEntity.where(realm, params.event.eventId).findFirst()
dummyEventEntity?.eventId = sendResponse.eventId
}
}
return result.fold({ Result.RETRY }, { Result.SUCCESS }) return result.fold({ Result.RETRY }, { Result.SUCCESS })
} }