Send media: first working implementation. Then, need to fix local echo and handle other types than image.

This commit is contained in:
ganfra 2019-04-04 19:55:58 +02:00
parent 18591d0287
commit c47eeb9cec
17 changed files with 293 additions and 65 deletions

View File

@ -0,0 +1,32 @@
/*
* 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.api.session.content

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class ContentAttachmentData(
val size: Long = 0,
val duration: Long = 0,
val date: Long = 0,
val height: Long = 0,
val width: Long = 0,
val name: String? = null,
val path: String? = null,
val mimeType: String? = null
) : Parcelable

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room.send
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
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.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.session.room.media.MediaAttachment import im.vector.matrix.android.api.session.content.ContentAttachmentData


/** /**
* This interface defines methods to send events in a room. It's implemented at the room level. * This interface defines methods to send events in a room. It's implemented at the room level.
@ -34,6 +34,6 @@ interface SendService {
*/ */
fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable


fun sendMedia(attachment: MediaAttachment, callback: MatrixCallback<Event>): Cancelable fun sendMedia(attachment: ContentAttachmentData, callback: MatrixCallback<Event>): Cancelable


} }

View File

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

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ContentUploadResponse(
@Json(name = "content_uri") val contentUri: String
)

View File

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

import arrow.core.Try
import arrow.core.Try.Companion.raise
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.internal.di.MoshiProvider
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import java.io.File
import java.io.IOException


internal class ContentUploader(private val okHttpClient: OkHttpClient,
private val sessionParams: SessionParams) {

private val moshi = MoshiProvider.providesMoshi()
private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java)

fun uploadFile(attachment: ContentAttachmentData): Try<ContentUploadResponse> {
if (attachment.path == null || attachment.mimeType == null) {
return raise(RuntimeException())
}
val file = File(attachment.path)
val urlString = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload"

val urlBuilder = HttpUrl.parse(urlString)?.newBuilder()
?: return raise(RuntimeException())

val httpUrl = urlBuilder
.addQueryParameter(
"filename", attachment.name
).build()

val requestBody = RequestBody.create(
MediaType.parse(attachment.mimeType),
file
)
val request = Request.Builder()
.url(httpUrl)
.post(requestBody)
.build()

return Try {
okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException()
} else {
response.body()?.source()?.let {
responseAdapter.fromJson(it)
}
?: throw IOException()
}
}
}
}
}

View File

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

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject

internal class UploadContentWorker(context: Context, params: WorkerParameters)
: Worker(context, params), MatrixKoinComponent {

private val mediaUploader by inject<ContentUploader>()

@JsonClass(generateAdapter = true)
internal data class Params(
val roomId: String,
val event: Event,
val attachment: ContentAttachmentData
)

override fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()

return mediaUploader
.uploadFile(params.attachment)
.fold({ handleFailure() }, { handleSuccess(params, it) })
}

private fun handleFailure(): Result {
return Result.retry()
}

private fun handleSuccess(params: Params, contentUploadResponse: ContentUploadResponse): Result {
val event = updateEvent(params.event, contentUploadResponse.contentUri)
val sendParams = SendEventWorker.Params(params.roomId, event)
return Result.success(WorkerParamsFactory.toData(sendParams))
}

private fun updateEvent(event: Event, url: String): Event {
val messageContent: MessageContent = event.content.toModel() ?: return event
val updatedContent = when (messageContent) {
is MessageImageContent -> messageContent.update(url)
else -> messageContent
}
return event.copy(content = updatedContent.toContent())
}

private fun MessageImageContent.update(url: String): MessageImageContent {
return copy(url = url)
}


}

View File

@ -23,7 +23,6 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
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

View File

@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.session.room.members.RoomMemberExtracto
import im.vector.matrix.android.internal.session.room.read.DefaultReadService 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.read.SetReadMarkersTask
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.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService 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.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
@ -35,7 +35,7 @@ internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask, private val setReadMarkersTask: SetReadMarkersTask,
private val eventFactory: EventFactory, private val eventFactory: LocalEchoEventFactory,
private val taskExecutor: TaskExecutor) { private val taskExecutor: TaskExecutor) {


fun instantiate(roomId: String): Room { fun instantiate(roomId: String): Room {

View File

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


import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.media.MediaUploader import im.vector.matrix.android.internal.session.content.ContentUploader
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask 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.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask 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.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.EventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.timeline.* import im.vector.matrix.android.internal.session.room.timeline.*
import org.koin.dsl.module.module import org.koin.dsl.module.module
import retrofit2.Retrofit import retrofit2.Retrofit
@ -58,11 +58,11 @@ class RoomModule {
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
EventFactory(get()) LocalEchoEventFactory(get())
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
MediaUploader(get(), get()) ContentUploader(get(), get())
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {

View File

@ -18,7 +18,6 @@


package im.vector.matrix.android.internal.session.room.media package im.vector.matrix.android.internal.session.room.media


import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize


@ -30,7 +29,6 @@ data class MediaAttachment(
val height: Long = 0, val height: Long = 0,
val width: Long = 0, val width: Long = 0,
val name: String? = null, val name: String? = null,
val thumbnail: Uri? = null,
val path: String? = null, val path: String? = null,
val mimeType: String? = null val mimeType: String? = null
) : Parcelable ) : Parcelable

View File

@ -20,11 +20,13 @@ package im.vector.matrix.android.internal.session.room.media


import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.internal.session.content.URI_PREFIX_CONTENT_API import im.vector.matrix.android.internal.session.content.URI_PREFIX_CONTENT_API
import okhttp3.HttpUrl
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException


@ -32,21 +34,28 @@ import java.io.IOException
internal class MediaUploader(private val okHttpClient: OkHttpClient, internal class MediaUploader(private val okHttpClient: OkHttpClient,
private val sessionParams: SessionParams) { private val sessionParams: SessionParams) {


fun uploadFile(attachment: MediaAttachment): Try<String> { fun uploadFile(attachment: ContentAttachmentData): Try<String> {
if (attachment.path == null || attachment.mimeType == null) { if (attachment.path == null || attachment.mimeType == null) {
return Try.raise(RuntimeException()) return Try.raise(RuntimeException())
} }
val urlString = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload"
val file = File(attachment.path) val file = File(attachment.path)
val urlString = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload"


// create RequestBody instance from file val urlBuilder = HttpUrl.parse(urlString)?.newBuilder()
val requestFile = RequestBody.create( ?: return Try.raise(RuntimeException())

val httpUrl = urlBuilder
.addQueryParameter(
"filename", attachment.name
).build()

val requestBody = MultipartBody.create(
MediaType.parse(attachment.mimeType), MediaType.parse(attachment.mimeType),
file file
) )
val request = Request.Builder() val request = Request.Builder()
.url(urlString) .url(httpUrl)
.post(requestFile) .post(requestBody)
.build() .build()


return okHttpClient.newCall(request).execute().use { response -> return okHttpClient.newCall(request).execute().use { response ->

View File

@ -23,17 +23,19 @@ 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.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.internal.session.content.ContentUploader
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject import org.koin.standalone.inject


internal class UploadMediaWorker(context: Context, params: WorkerParameters) internal class UploadMediaWorker(context: Context, params: WorkerParameters)
: Worker(context, params), MatrixKoinComponent { : Worker(context, params), MatrixKoinComponent {


private val mediaUploader by inject<MediaUploader>() private val mediaUploader by inject<ContentUploader>()


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
val attachment: MediaAttachment val attachment: ContentAttachmentData
) )


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

View File

@ -35,7 +35,9 @@ internal class EventsPruner(monarchy: Monarchy) :
override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) } override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) }


override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) { override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
val redactionEvents = inserted.map { it.asDomain() } val redactionEvents = inserted
.mapNotNull { it.asDomain().redacts }

val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents) val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents)
val workData = WorkerParamsFactory.toData(pruneEventWorkerParams) val workData = WorkerParamsFactory.toData(pruneEventWorkerParams)



View File

@ -21,7 +21,6 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.zhuinden.monarchy.Monarchy 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.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
@ -37,8 +36,8 @@ internal class PruneEventWorker(context: Context,
) : Worker(context, workerParameters), MatrixKoinComponent { ) : Worker(context, workerParameters), MatrixKoinComponent {


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal class Params(
val redactionEvents: List<Event> val eventIdsToRedact: List<String>
) )


private val monarchy by inject<Monarchy>() private val monarchy by inject<Monarchy>()
@ -48,18 +47,19 @@ internal class PruneEventWorker(context: Context,
?: return Result.failure() ?: return Result.failure()


val result = monarchy.tryTransactionSync { realm -> val result = monarchy.tryTransactionSync { realm ->
params.redactionEvents.forEach { event -> params.eventIdsToRedact.forEach { eventId ->
pruneEvent(realm, event) pruneEvent(realm, eventId)
} }
} }
return result.fold({ Result.retry() }, { Result.success() }) return result.fold({ Result.retry() }, { Result.success() })
} }


private fun pruneEvent(realm: Realm, redactionEvent: Event?) { private fun pruneEvent(realm: Realm, eventIdToRedact: String) {
if (redactionEvent == null || redactionEvent.redacts.isNullOrEmpty()) { if (eventIdToRedact.isEmpty()) {
return return
} }
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
val eventToPrune = EventEntity.where(realm, eventId = eventIdToRedact).findFirst()
?: return ?: return


val allowedKeys = computeAllowedKeys(eventToPrune.type) val allowedKeys = computeAllowedKeys(eventToPrune.type)
@ -87,7 +87,7 @@ internal class PruneEventWorker(context: Context,
EventType.STATE_ROOM_ALIASES -> listOf("aliases") EventType.STATE_ROOM_ALIASES -> listOf("aliases")
EventType.STATE_CANONICAL_ALIAS -> listOf("alias") EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
EventType.FEEDBACK -> listOf("type", "target_event_id") EventType.FEEDBACK -> listOf("type", "target_event_id")
else -> emptyList() else -> emptyList()
} }
} }



View File

@ -16,17 +16,23 @@


package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send


import androidx.work.* import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.content.ContentAttachmentData
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.room.send.SendService import im.vector.matrix.android.api.session.room.send.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.add import im.vector.matrix.android.internal.database.helper.add
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.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.session.room.media.MediaAttachment import im.vector.matrix.android.internal.session.content.UploadContentWorker
import im.vector.matrix.android.internal.session.room.media.UploadMediaWorker
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
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
@ -35,18 +41,19 @@ import java.util.concurrent.TimeUnit


private const val SEND_WORK = "SEND_WORK" private const val SEND_WORK = "SEND_WORK"
private const val BACKOFF_DELAY = 10_000L private const val BACKOFF_DELAY = 10_000L

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


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




override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable { override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
val event = eventFactory.createTextEvent(roomId, text) val event = eventFactory.createTextEvent(roomId, text)
saveLiveEvent(event) saveLocalEcho(event)
val sendWork = createSendEventWork(event) val sendWork = createSendEventWork(event)
WorkManager.getInstance() WorkManager.getInstance()
.beginUniqueWork(SEND_WORK, ExistingWorkPolicy.APPEND, sendWork) .beginUniqueWork(SEND_WORK, ExistingWorkPolicy.APPEND, sendWork)
@ -55,12 +62,12 @@ internal class DefaultSendService(private val roomId: String,
return CancelableWork(sendWork.id) return CancelableWork(sendWork.id)
} }


override fun sendMedia(attachment: MediaAttachment, callback: MatrixCallback<Event>): Cancelable { override fun sendMedia(attachment: ContentAttachmentData, callback: MatrixCallback<Event>): Cancelable {
// Create an event with the media file path // Create an event with the media file path
val event = eventFactory.createImageEvent(roomId, attachment) val event = eventFactory.createMediaEvent(roomId, attachment).also {
saveLiveEvent(event) saveLocalEcho(it)

}
val uploadWork = createUploadMediaWork(attachment) val uploadWork = createUploadMediaWork(event, attachment)
val sendWork = createSendEventWork(event) val sendWork = createSendEventWork(event)


WorkManager.getInstance() WorkManager.getInstance()
@ -70,10 +77,10 @@ internal class DefaultSendService(private val roomId: String,
return CancelableWork(sendWork.id) return CancelableWork(sendWork.id)
} }


private fun saveLiveEvent(event: Event) { private fun saveLocalEcho(event: Event) {
monarchy.tryTransactionAsync { realm -> monarchy.tryTransactionAsync { realm ->
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
?: return@tryTransactionAsync ?: return@tryTransactionAsync
chunkEntity.add(roomId, event, PaginationDirection.FORWARDS) chunkEntity.add(roomId, event, PaginationDirection.FORWARDS)
} }
} }
@ -89,11 +96,11 @@ internal class DefaultSendService(private val roomId: String,
.build() .build()
} }


private fun createUploadMediaWork(attachment: MediaAttachment): OneTimeWorkRequest { private fun createUploadMediaWork(event: Event, attachment: ContentAttachmentData): OneTimeWorkRequest {
val uploadMediaWorkerParams = UploadMediaWorker.Params(attachment) val uploadMediaWorkerParams = UploadContentWorker.Params(roomId, event, attachment)
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)


return OneTimeWorkRequestBuilder<UploadMediaWorker>() return OneTimeWorkRequestBuilder<UploadContentWorker>()
.setConstraints(WORK_CONSTRAINTS) .setConstraints(WORK_CONSTRAINTS)
.setInputData(uploadWorkData) .setInputData(uploadWorkData)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)

View File

@ -17,38 +17,38 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send


import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.content.ContentAttachmentData
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.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.ImageInfo
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.internal.session.room.media.MediaAttachment


internal class EventFactory(private val credentials: Credentials) { internal class LocalEchoEventFactory(private val credentials: Credentials) {


fun createTextEvent(roomId: String, text: String): Event { fun createTextEvent(roomId: String, text: String): Event {
val content = MessageTextContent(type = MessageType.MSGTYPE_TEXT, body = text) val content = MessageTextContent(type = MessageType.MSGTYPE_TEXT, body = text)
return createEvent(roomId, content) return createEvent(roomId, content)
} }


fun createImageEvent(roomId: String, attachment: MediaAttachment): Event { fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
val content = MessageImageContent( val content = MessageImageContent(
type = MessageType.MSGTYPE_IMAGE, type = MessageType.MSGTYPE_IMAGE,
body = attachment.name ?: "image", body = attachment.name ?: "image",
info = ImageInfo(
mimeType = attachment.mimeType ?: "image/png",
width = attachment.width.toInt(),
height = attachment.height.toInt(),
size = attachment.size.toInt()
),
url = attachment.path url = attachment.path
) )
return createEvent(roomId, content) return createEvent(roomId, content)
} }


fun updateImageEvent(event: Event, url: String): Event { private fun createEvent(roomId: String, content: Any? = null): Event {
val imageContent = event.content.toModel<MessageImageContent>() ?: return event
val updatedContent = imageContent.copy(url = url)
return event.copy(content = updatedContent.toContent())
}

fun createEvent(roomId: String, content: Any? = null): Event {
return Event( return Event(
roomId = roomId, roomId = roomId,
originServerTs = dummyOriginServerTs(), originServerTs = dummyOriginServerTs(),

View File

@ -49,16 +49,17 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
val params = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure() ?: return Result.failure()


if (params.event.eventId == null) { val event = params.event
if (event.eventId == null) {
return Result.failure() return Result.failure()
} }


val result = executeRequest<SendResponse> { val result = executeRequest<SendResponse> {
apiCall = roomAPI.send( apiCall = roomAPI.send(
params.event.eventId, event.eventId,
params.roomId, params.roomId,
params.event.type, event.type,
params.event.content event.content
) )
} }
result.flatMap { sendResponse -> result.flatMap { sendResponse ->
@ -69,6 +70,4 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
} }
return result.fold({ Result.retry() }, { Result.success() }) return result.fold({ Result.retry() }, { Result.success() })
} }


} }

View File

@ -22,7 +22,7 @@ import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
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.session.room.media.MediaAttachment import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.features.home.room.VisibleRoomStore import im.vector.riotredesign.features.home.room.VisibleRoomStore
@ -81,14 +81,13 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
private fun handleSendMedia(action: RoomDetailActions.SendMedia) { private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
val attachment = action.mediaFiles.firstOrNull() val attachment = action.mediaFiles.firstOrNull()
?.let { ?.let {
MediaAttachment( ContentAttachmentData(
it.size, it.size,
it.duration, it.duration,
it.date, it.date,
it.height, it.height,
it.width, it.width,
it.name, it.name,
it.thumbnail,
it.path, it.path,
it.mimeType it.mimeType
) )