Send files to several rooms at a time

This commit is contained in:
Benoit Marty 2020-02-13 17:00:16 +01:00
parent 81de914360
commit 06ba478232
11 changed files with 284 additions and 92 deletions

View File

@ -52,20 +52,27 @@ interface SendService {
* Method to send a media asynchronously.
* @param attachment the media to send
* @param compressBeforeSending set to true to compress media before sending them
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @return a [Cancelable]
*/
fun sendMedia(attachment: ContentAttachmentData,
// TODO Change to a Compression Level Enum
compressBeforeSending: Boolean): Cancelable
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable
/**
* Method to send a list of media asynchronously.
* @param attachments the list of media to send
* @param compressBeforeSending set to true to compress media before sending them
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @return a [Cancelable]
*/
fun sendMedias(attachments: List<ContentAttachmentData>,
// TODO Change to a Compression Level Enum
compressBeforeSending: Boolean): Cancelable
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable
/**
* Send a poll to the room.

View File

@ -17,7 +17,11 @@
package im.vector.matrix.android.internal.di
import android.content.Context
import androidx.work.*
import androidx.work.Constraints
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import javax.inject.Inject
internal class WorkManagerProvider @Inject constructor(
@ -54,5 +58,7 @@ internal class WorkManagerProvider @Inject constructor(
val workConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
const val BACKOFF_DELAY = 10_000L
}
}

View File

@ -38,6 +38,7 @@ import im.vector.matrix.android.internal.session.pushers.PushersModule
import im.vector.matrix.android.internal.session.room.RoomModule
import im.vector.matrix.android.internal.session.room.relation.SendRelationWorker
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
import im.vector.matrix.android.internal.session.room.send.MultipleEventSendingDispatcherWorker
import im.vector.matrix.android.internal.session.room.send.RedactEventWorker
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
import im.vector.matrix.android.internal.session.signout.SignOutModule
@ -85,23 +86,25 @@ internal interface SessionComponent {
fun taskExecutor(): TaskExecutor
fun inject(sendEventWorker: SendEventWorker)
fun inject(worker: SendEventWorker)
fun inject(sendEventWorker: SendRelationWorker)
fun inject(worker: SendRelationWorker)
fun inject(encryptEventWorker: EncryptEventWorker)
fun inject(worker: EncryptEventWorker)
fun inject(redactEventWorker: RedactEventWorker)
fun inject(worker: MultipleEventSendingDispatcherWorker)
fun inject(getGroupDataWorker: GetGroupDataWorker)
fun inject(worker: RedactEventWorker)
fun inject(uploadContentWorker: UploadContentWorker)
fun inject(worker: GetGroupDataWorker)
fun inject(syncWorker: SyncWorker)
fun inject(worker: UploadContentWorker)
fun inject(addHttpPusherWorker: AddHttpPusherWorker)
fun inject(worker: SyncWorker)
fun inject(sendVerificationMessageWorker: SendVerificationMessageWorker)
fun inject(worker: AddHttpPusherWorker)
fun inject(worker: SendVerificationMessageWorker)
@Component.Factory
interface Factory {

View File

@ -24,11 +24,15 @@ 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.*
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
import im.vector.matrix.android.internal.network.ProgressRequestBody
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
import im.vector.matrix.android.internal.session.room.send.MultipleEventSendingDispatcherWorker
import im.vector.matrix.android.internal.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
@ -43,8 +47,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
val roomId: String,
val event: Event,
val events: List<Event>,
val attachment: ContentAttachmentData,
val isRoomEncrypted: Boolean,
val compressBeforeSending: Boolean,
@ -68,14 +71,17 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
val eventId = params.event.eventId ?: return Result.success()
val attachment = params.attachment
val attachmentFile = try {
File(attachment.path)
} catch (e: Exception) {
Timber.e(e)
contentUploadStateTracker.setFailure(params.event.eventId, e)
params.events
.mapNotNull { it.eventId }
.forEach {
contentUploadStateTracker.setFailure(it, e)
}
return Result.success(
WorkerParamsFactory.toData(params.copy(
lastFailureMessage = e.localizedMessage
@ -91,14 +97,22 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData ->
val thumbnailProgressListener = object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
contentUploadStateTracker.setProgressThumbnail(eventId, current, total)
params.events
.mapNotNull { it.eventId }
.forEach {
contentUploadStateTracker.setProgressThumbnail(it, current, total)
}
}
}
try {
val contentUploadResponse = if (params.isRoomEncrypted) {
Timber.v("Encrypt thumbnail")
contentUploadStateTracker.setEncryptingThumbnail(eventId)
params.events
.mapNotNull { it.eventId }
.forEach {
contentUploadStateTracker.setEncryptingThumbnail(it)
}
val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
@ -121,11 +135,15 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
val progressListener = object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
if (isStopped) {
contentUploadStateTracker.setFailure(eventId, Throwable("Cancelled"))
} else {
contentUploadStateTracker.setProgress(eventId, current, total)
}
params.events
.mapNotNull { it.eventId }
.forEach {
if (isStopped) {
contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
} else {
contentUploadStateTracker.setProgress(it, current, total)
}
}
}
}
@ -134,7 +152,11 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
return try {
val contentUploadResponse = if (params.isRoomEncrypted) {
Timber.v("Encrypt file")
contentUploadStateTracker.setEncrypting(eventId)
params.events
.mapNotNull { it.eventId }
.forEach {
contentUploadStateTracker.setEncrypting(it)
}
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
@ -154,7 +176,12 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
}
private fun handleFailure(params: Params, failure: Throwable): Result {
contentUploadStateTracker.setFailure(params.event.eventId!!, failure)
params.events
.mapNotNull { it.eventId }
.forEach {
contentUploadStateTracker.setFailure(it, failure)
}
return Result.success(
WorkerParamsFactory.toData(
params.copy(
@ -170,9 +197,18 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
thumbnailUrl: String?,
thumbnailEncryptedFileInfo: EncryptedFileInfo?): Result {
Timber.v("handleSuccess $attachmentUrl, work is stopped $isStopped")
contentUploadStateTracker.setSuccess(params.event.eventId!!)
val event = updateEvent(params.event, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
val sendParams = SendEventWorker.Params(params.sessionId, params.roomId, event)
params.events
.mapNotNull { it.eventId }
.forEach {
contentUploadStateTracker.setSuccess(it)
}
val updatedEvents = params.events
.map {
updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
}
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isRoomEncrypted)
return Result.success(WorkerParamsFactory.toData(sendParams))
}

View File

@ -196,13 +196,13 @@ internal class DefaultRelationService @AssistedInject constructor(
private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest {
// Same parameter
val params = EncryptEventWorker.Params(sessionId, roomId, event, keepKeys)
val params = EncryptEventWorker.Params(sessionId, event, keepKeys)
val sendWorkData = WorkerParamsFactory.toData(params)
return timeLineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
}
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
}

View File

@ -22,7 +22,6 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.Operation
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
@ -49,7 +48,6 @@ import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
private const val UPLOAD_WORK = "UPLOAD_WORK"
private const val BACKOFF_DELAY = 10_000L
internal class DefaultSendService @AssistedInject constructor(
@Assisted private val roomId: String,
@ -58,7 +56,6 @@ internal class DefaultSendService @AssistedInject constructor(
@SessionId private val sessionId: String,
private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val localEchoRepository: LocalEchoRepository
) : SendService {
@ -103,6 +100,7 @@ internal class DefaultSendService @AssistedInject constructor(
return if (cryptoService.isRoomEncrypted(roomId)) {
Timber.v("Send event in encrypted room")
val encryptWork = createEncryptEventWork(event, true)
// Note that event will be replaced by the result of the previous work
val sendWork = createSendEventWork(event, false)
timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork)
} else {
@ -111,9 +109,11 @@ internal class DefaultSendService @AssistedInject constructor(
}
}
override fun sendMedias(attachments: List<ContentAttachmentData>, compressBeforeSending: Boolean): Cancelable {
override fun sendMedias(attachments: List<ContentAttachmentData>,
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable {
return attachments.mapTo(CancelableBag()) {
sendMedia(it, compressBeforeSending)
sendMedia(it, compressBeforeSending, roomIds)
}
}
@ -201,43 +201,66 @@ internal class DefaultSendService @AssistedInject constructor(
}
}
override fun sendMedia(attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable {
override fun sendMedia(attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable {
// Create an event with the media file path
val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
createLocalEcho(it)
// Ensure current roomId is included in the set
val allRoomIds = (roomIds + roomId).toList()
// Create local echo for each room
val allLocalEchoes = allRoomIds.map {
localEchoEventFactory.createMediaEvent(it, attachment).also { event ->
createLocalEcho(event)
}
}
return internalSendMedia(event, attachment, compressBeforeSending)
return internalSendMedia(allLocalEchoes, attachment, compressBeforeSending)
}
private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable {
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
/**
* We use the roomId of the local echo event
*/
private fun internalSendMedia(allLocalEchoes: List<Event>, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable {
val splitLocalEchoes = allLocalEchoes.groupBy { cryptoService.isRoomEncrypted(it.roomId!!) }
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, compressBeforeSending, startChain = true)
val sendWork = createSendEventWork(localEcho, false)
val encryptedLocalEchoes = splitLocalEchoes[true].orEmpty()
val clearLocalEchoes = splitLocalEchoes[false].orEmpty()
if (isRoomEncrypted) {
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
val cancelableBag = CancelableBag()
val op: Operation = workManagerProvider.workManager
if (encryptedLocalEchoes.isNotEmpty()) {
val uploadWork = createUploadMediaWork(encryptedLocalEchoes, attachment, true, compressBeforeSending, startChain = true)
val dispatcherWork = createMultipleEventDispatcherWork(true)
val operation = workManagerProvider.workManager
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
.then(encryptWork)
.then(sendWork)
.then(dispatcherWork)
.enqueue()
op.result.addListener(Runnable {
if (op.result.isCancelled) {
operation.result.addListener(Runnable {
if (operation.result.isCancelled) {
Timber.e("CHAIN WAS CANCELLED")
} else if (op.state.value is Operation.State.FAILURE) {
} else if (operation.state.value is Operation.State.FAILURE) {
Timber.e("CHAIN DID FAIL")
}
}, workerFutureListenerExecutor)
} else {
workManagerProvider.workManager
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
.then(sendWork)
.enqueue()
cancelableBag.add(CancelableWork(workManagerProvider.workManager, dispatcherWork.id))
}
return CancelableWork(workManagerProvider.workManager, sendWork.id)
if (clearLocalEchoes.isNotEmpty()) {
val uploadWork = createUploadMediaWork(clearLocalEchoes, attachment, false, compressBeforeSending, startChain = true)
val dispatcherWork = createMultipleEventDispatcherWork(false)
workManagerProvider.workManager
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
.then(dispatcherWork)
.enqueue()
cancelableBag.add(CancelableWork(workManagerProvider.workManager, dispatcherWork.id))
}
return cancelableBag
}
private fun createLocalEcho(event: Event) {
@ -250,19 +273,19 @@ internal class DefaultSendService @AssistedInject constructor(
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
// Same parameter
val params = EncryptEventWorker.Params(sessionId, roomId, event)
val params = EncryptEventWorker.Params(sessionId, event)
val sendWorkData = WorkerParamsFactory.toData(params)
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.setInputData(sendWorkData)
.startChain(startChain)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
@ -277,19 +300,33 @@ internal class DefaultSendService @AssistedInject constructor(
return timelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
}
private fun createUploadMediaWork(event: Event,
private fun createUploadMediaWork(allLocalEchos: List<Event>,
attachment: ContentAttachmentData,
isRoomEncrypted: Boolean,
compressBeforeSending: Boolean,
startChain: Boolean): OneTimeWorkRequest {
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted, compressBeforeSending)
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, allLocalEchos, attachment, isRoomEncrypted, compressBeforeSending)
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.startChain(startChain)
.setInputData(uploadWorkData)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}
private fun createMultipleEventDispatcherWork(isRoomEncrypted: Boolean): OneTimeWorkRequest {
// the list of events will be replaced by the result of the media upload work
val params = MultipleEventSendingDispatcherWorker.Params(sessionId, emptyList(), isRoomEncrypted)
val workData = WorkerParamsFactory.toData(params)
return workManagerProvider.matrixOneTimeWorkRequestBuilder<MultipleEventSendingDispatcherWorker>()
// No constraint
// .setConstraints(WorkManagerProvider.workConstraints)
.startChain(false)
.setInputData(workData)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}
}

View File

@ -20,7 +20,6 @@ import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
@ -39,9 +38,8 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
val roomId: String,
val event: Event,
/**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
/** Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to) */
val keepKeys: List<String>? = null,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@ -53,7 +51,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
Timber.v("Start Encrypt work")
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success().also {
Timber.v("Work cancelled due to input error from parent")
Timber.e("Work cancelled due to input error from parent")
}
Timber.v("Start Encrypt work for event ${params.event.eventId}")
@ -80,7 +78,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
var result: MXEncryptEventContentResult? = null
try {
result = awaitCallback {
crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
crypto.encryptEventContent(localMutableContent, localEvent.type, localEvent.roomId!!, it)
}
} catch (throwable: Throwable) {
error = throwable
@ -98,7 +96,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
type = safeResult.eventType,
content = safeResult.eventContent
)
val nextWorkerParams = SendEventWorker.Params(params.sessionId, params.roomId, encryptedEvent)
val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent)
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
} else {
val sendState = when (error) {
@ -107,7 +105,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
}
localEchoUpdater.updateSendState(localEvent.eventId, sendState)
// always return success, or the chain will be stuck for ever!
val nextWorkerParams = SendEventWorker.Params(params.sessionId, params.roomId, localEvent, error?.localizedMessage
val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage
?: "Error")
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2020 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.send
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.CoroutineWorker
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
import im.vector.matrix.android.internal.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import im.vector.matrix.android.internal.worker.startChain
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* This worker creates a new work for each events passed in parameter
*/
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
val events: List<Event>,
val isEncrypted: Boolean,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject lateinit var workManagerProvider: WorkManagerProvider
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
override suspend fun doWork(): Result {
Timber.v("Start dispatch sending multiple event work")
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success().also {
Timber.e("Work cancelled due to input error from parent")
}
if (params.lastFailureMessage != null) {
// Transmit the error
return Result.success(inputData)
}
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
// Create a work for every event
params.events.forEach { event ->
if (params.isEncrypted) {
Timber.v("Send event in encrypted room")
val encryptWork = createEncryptEventWork(params.sessionId, event, true)
// Note that event will be replaced by the result of the previous work
val sendWork = createSendEventWork(params.sessionId, event, false)
timelineSendEventWorkCommon.postSequentialWorks(event.roomId!!, encryptWork, sendWork)
} else {
val sendWork = createSendEventWork(params.sessionId, event, true)
timelineSendEventWorkCommon.postWork(event.roomId!!, sendWork)
}
}
return Result.success()
}
private fun createEncryptEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
val params = EncryptEventWorker.Params(sessionId, event)
val sendWorkData = WorkerParamsFactory.toData(params)
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.setInputData(sendWorkData)
.startChain(startChain)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}
private fun createSendEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
}
}

View File

@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
internal class SendEventWorker(context: Context,
@ -39,7 +40,6 @@ internal class SendEventWorker(context: Context,
@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
val roomId: String,
val event: Event,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@ -50,7 +50,9 @@ internal class SendEventWorker(context: Context,
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
?: return Result.success().also {
Timber.e("Work cancelled due to input error from parent")
}
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
@ -66,7 +68,7 @@ internal class SendEventWorker(context: Context,
return Result.success(inputData)
}
return try {
sendEvent(event.eventId, event.type, event.content, params.roomId)
sendEvent(event)
Result.success()
} catch (exception: Throwable) {
if (exception.shouldBeRetried()) {
@ -79,16 +81,16 @@ internal class SendEventWorker(context: Context,
}
}
private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) {
localEchoUpdater.updateSendState(eventId, SendState.SENDING)
private suspend fun sendEvent(event: Event) {
localEchoUpdater.updateSendState(event.eventId!!, SendState.SENDING)
executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.send(
eventId,
roomId,
eventType,
content
event.eventId,
event.roomId!!,
event.type,
event.content
)
}
localEchoUpdater.updateSendState(eventId, SendState.SENT)
localEchoUpdater.updateSendState(event.eventId, SendState.SENT)
}
}

View File

@ -579,10 +579,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
if (maxUploadFileSize == HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN) {
// Unknown limitation
room.sendMedias(attachments, action.compressBeforeSending)
room.sendMedias(attachments, action.compressBeforeSending, emptySet())
} else {
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
null -> room.sendMedias(attachments, action.compressBeforeSending)
null -> room.sendMedias(attachments, action.compressBeforeSending, emptySet())
else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError(
tooBigFile.name ?: tooBigFile.path,
tooBigFile.size,

View File

@ -135,19 +135,19 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState:
proposeMediaEdition: Boolean,
compressMediaBeforeSending: Boolean) {
if (!proposeMediaEdition) {
selectedRoomIds.forEach { roomId ->
val room = session.getRoom(roomId)
room?.sendMedias(attachmentData, compressMediaBeforeSending)
}
// Pick the first room to send the media
selectedRoomIds.firstOrNull()
?.let { roomId -> session.getRoom(roomId) }
?.sendMedias(attachmentData, compressMediaBeforeSending, selectedRoomIds)
} else {
val previewable = attachmentData.filterPreviewables()
val nonPreviewable = attachmentData.filterNonPreviewables()
if (nonPreviewable.isNotEmpty()) {
// Send the non previewable attachment right now (?)
selectedRoomIds.forEach { roomId ->
val room = session.getRoom(roomId)
room?.sendMedias(nonPreviewable, compressMediaBeforeSending)
}
// Pick the first room to send the media
selectedRoomIds.firstOrNull()
?.let { roomId -> session.getRoom(roomId) }
?.sendMedias(nonPreviewable, compressMediaBeforeSending, selectedRoomIds)
}
if (previewable.isNotEmpty()) {
// In case of multiple share of media, edit them first