diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 62a24bc8..f231009b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -1,12 +1,12 @@ package im.vector.matrix.android.api.session.events.model -import com.google.gson.JsonObject import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import com.squareup.moshi.Types 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 +typealias Content = Map @JsonClass(generateAdapter = true) 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 content(): T? { return toModel(content) } @@ -47,4 +37,8 @@ data class Event( return moshiAdapter.fromJsonValue(data) } + companion object { + val CONTENT_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt index 24fdac06..1c1a3e4b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt @@ -1,16 +1,24 @@ package im.vector.matrix.android.internal.database.helper 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.EventEntity 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.addManagedToChunk(chunkEntity: ChunkEntity) { if (!chunkEntity.isManaged) { throw IllegalStateException("Chunk entity should be managed to use fast contains") } + val realm = chunkEntity.realm 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)) { chunkEntity.events.add(eventEntity) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index 1233a41d..6f7d6da2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -1,6 +1,5 @@ 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.UnsignedData import im.vector.matrix.android.internal.database.model.EventEntity @@ -10,21 +9,11 @@ import im.vector.matrix.android.internal.di.MoshiProvider internal object EventMapper { private val moshi = MoshiProvider.providesMoshi() - private val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) - private val adapter = moshi.adapter>(type) + private val adapter = moshi.adapter>(Event.CONTENT_TYPE) fun map(event: Event): EventEntity { val eventEntity = EventEntity() - eventEntity.eventId = event.eventId ?: "" - 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 + fill(eventEntity, with = event) return eventEntity } @@ -42,6 +31,20 @@ internal object EventMapper { 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 { @@ -50,4 +53,8 @@ internal fun EventEntity.asDomain(): Event { internal fun Event.asEntity(): EventEntity { return EventMapper.map(this) +} + +internal fun EventEntity.fillWith(event: Event) { + EventMapper.fill(this, with = event) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index f54fa93f..05aede38 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -3,17 +3,16 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects -import io.realm.annotations.PrimaryKey -internal open class EventEntity(@PrimaryKey var eventId: String = "", - var type: String = "", - var content: String = "", - var prevContent: String? = null, - var stateKey: String? = null, - var originServerTs: Long? = null, - var sender: String? = null, - var age: Long? = 0, - var redacts: String? = null +internal open class EventEntity(var eventId: String = "", + var type: String = "", + var content: String = "", + var prevContent: String? = null, + var stateKey: String? = null, + var originServerTs: Long? = null, + var sender: String? = null, + var age: Long? = 0, + var redacts: String? = null ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 8f2d1ed9..bb5ca1a6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -23,6 +23,17 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module 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) { RealmConfiguration.Builder() .name(sessionParams.credentials.userId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt index cf0f6b61..077d2727 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt @@ -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.EventType 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.query.where import im.vector.matrix.android.internal.util.WorkerParamsFactory @@ -48,16 +48,18 @@ internal class PruneEventWorker(context: Context, if (redactionEvent == null || redactionEvent.redacts.isNullOrEmpty()) { return } - val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()?.asDomain() + val eventToPruneEntity = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() ?: return + val eventToPrune = eventToPruneEntity.asDomain() + val allowedKeys = computeAllowedKeys(eventToPrune.type) val prunedContent = allowedKeys.fold( { eventToPrune.content }, { eventToPrune.content?.filterKeys { key -> it.contains(key) } } ) - val eventToPruneEntity = eventToPrune.copy(content = prunedContent).asEntity() - realm.insertOrUpdate(eventToPruneEntity) + val prunedEvent = eventToPrune.copy(content = prunedContent) + eventToPruneEntity.fillWith(prunedEvent) } private fun computeAllowedKeys(type: String): Option> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 655cd33d..c3777283 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -1,16 +1,12 @@ 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.session.room.members.RoomMembersResponse import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent import retrofit2.Call -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.PUT -import retrofit2.http.Path -import retrofit2.http.Query +import retrofit2.http.* internal interface RoomAPI { @@ -60,7 +56,7 @@ internal interface RoomAPI { fun send(@Path("txId") txId: String, @Path("roomId") roomId: String, @Path("eventType") eventType: String, - @Body content: MessageContent + @Body content: Content? ): Call diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index bfd9052e..26c06106 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -1,10 +1,12 @@ 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.TimelineHolder 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.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.PaginationRequest import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback @@ -32,6 +34,10 @@ class RoomModule : Module { PaginationRequest(get(), get(), get(), get()) } + scope(DefaultSession.SCOPE) { + val sessionParams = get() + EventFactory(sessionParams.credentials) + } factory { val roomId: String = it[0] @@ -41,7 +47,7 @@ class RoomModule : Module { factory { val roomId: String = it[0] - DefaultSendService(roomId) as SendService + DefaultSendService(roomId, get(), get()) as SendService } }.invoke() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 015d5858..9f6f168f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -1,23 +1,37 @@ package im.vector.matrix.android.internal.session.room.send import androidx.work.* +import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.SendService 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.WorkerParamsFactory +import im.vector.matrix.android.internal.util.tryTransactionAsync import java.util.concurrent.TimeUnit 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() .setRequiredNetworkType(NetworkType.CONNECTED) .build() 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 sendWork = OneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EventFactory.kt new file mode 100644 index 00000000..a2abf102 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EventFactory.kt @@ -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 toContent(data: T?): Content? { + val moshiAdapter = moshi.adapter(T::class.java) + val jsonValue = moshiAdapter.toJsonValue(data) + return jsonValue as? Content? + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index f490beb1..326a746b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -4,12 +4,14 @@ import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -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 com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.Event +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.session.room.RoomAPI 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.inject @@ -20,25 +22,35 @@ internal class SendEventWorker(context: Context, params: WorkerParameters) @JsonClass(generateAdapter = true) internal data class Params( val roomId: String, - val text: String + val event: Event ) private val roomAPI by inject() + private val monarchy by inject() override fun doWork(): Result { - val sendWorkerParameters = WorkerParamsFactory.fromData(inputData) + val params = WorkerParamsFactory.fromData(inputData) ?: return Result.FAILURE - val fakeId = sendWorkerParameters.roomId + "-" + System.currentTimeMillis() + if (params.event.eventId == null) { + return Result.FAILURE + } + val result = executeRequest { apiCall = roomAPI.send( - fakeId, - sendWorkerParameters.roomId, - EventType.MESSAGE, - MessageContent(MessageType.MSGTYPE_TEXT, sendWorkerParameters.text) + params.event.eventId, + params.roomId, + params.event.type, + 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 }) }