BayernMessenger/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt

239 lines
10 KiB
Kotlin

/*
* 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.CoroutineWorker
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.*
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.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import javax.inject.Inject
internal class UploadContentWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true)
internal data class Params(
override val userId: String,
val roomId: String,
val event: Event,
val attachment: ContentAttachmentData,
val isRoomEncrypted: Boolean,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject lateinit var fileUploader: FileUploader
@Inject lateinit var contentUploadStateTracker: DefaultContentUploadStateTracker
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
Timber.v("Starting upload media work with params $params")
if (params.lastFailureMessage != null) {
// Transmit the error
Timber.v("Stop upload media work due to input failure")
return Result.success(inputData)
}
val sessionComponent = getSessionComponent(params.userId) ?: 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)
return Result.success(
WorkerParamsFactory.toData(params.copy(
lastFailureMessage = e.localizedMessage
))
)
}
var uploadedThumbnailUrl: String? = null
var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData ->
val thumbnailProgressListener = object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
contentUploadStateTracker.setProgressThumbnail(eventId, current, total)
}
}
val contentUploadResponse = if (params.isRoomEncrypted) {
Timber.v("Encrypt thumbnail")
contentUploadStateTracker.setEncryptingThumbnail(eventId)
MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
.flatMap { encryptionResult ->
uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader
.uploadByteArray(encryptionResult.encryptedByteArray,
"thumb_${attachment.name}",
"application/octet-stream",
thumbnailProgressListener)
}
} else {
fileUploader
.uploadByteArray(thumbnailData.bytes,
"thumb_${attachment.name}",
thumbnailData.mimeType,
thumbnailProgressListener)
}
contentUploadResponse
.fold(
{ Timber.e(it) },
{ uploadedThumbnailUrl = it.contentUri }
)
}
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)
}
}
}
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
val contentUploadResponse = if (params.isRoomEncrypted) {
Timber.v("Encrypt file")
contentUploadStateTracker.setEncrypting(eventId)
MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
.flatMap { encryptionResult ->
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
}
} else {
fileUploader
.uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
}
return contentUploadResponse
.fold(
{ handleFailure(params, it) },
{ handleSuccess(params, it.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) }
)
}
private fun handleFailure(params: Params, failure: Throwable): Result {
contentUploadStateTracker.setFailure(params.event.eventId!!, failure)
return Result.success(
WorkerParamsFactory.toData(
params.copy(
lastFailureMessage = failure.localizedMessage
)
)
)
}
private fun handleSuccess(params: Params,
attachmentUrl: String,
encryptedFileInfo: EncryptedFileInfo?,
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.userId, params.roomId, event)
return Result.success(WorkerParamsFactory.toData(sendParams))
}
private fun updateEvent(event: Event,
url: String,
encryptedFileInfo: EncryptedFileInfo?,
thumbnailUrl: String? = null,
thumbnailEncryptedFileInfo: EncryptedFileInfo?): Event {
val messageContent: MessageContent = event.content.toModel() ?: return event
val updatedContent = when (messageContent) {
is MessageImageContent -> messageContent.update(url, encryptedFileInfo)
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
is MessageFileContent -> messageContent.update(url, encryptedFileInfo)
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo)
else -> messageContent
}
return event.copy(content = updatedContent.toContent())
}
private fun MessageImageContent.update(url: String,
encryptedFileInfo: EncryptedFileInfo?): MessageImageContent {
return copy(
url = if (encryptedFileInfo == null) url else null,
encryptedFileInfo = encryptedFileInfo?.copy(url = url)
)
}
private fun MessageVideoContent.update(url: String,
encryptedFileInfo: EncryptedFileInfo?,
thumbnailUrl: String?,
thumbnailEncryptedFileInfo: EncryptedFileInfo?): MessageVideoContent {
return copy(
url = if (encryptedFileInfo == null) url else null,
encryptedFileInfo = encryptedFileInfo?.copy(url = url),
videoInfo = videoInfo?.copy(
thumbnailUrl = if (thumbnailEncryptedFileInfo == null) thumbnailUrl else null,
thumbnailFile = thumbnailEncryptedFileInfo?.copy(url = thumbnailUrl)
)
)
}
private fun MessageFileContent.update(url: String,
encryptedFileInfo: EncryptedFileInfo?): MessageFileContent {
return copy(
url = if (encryptedFileInfo == null) url else null,
encryptedFileInfo = encryptedFileInfo?.copy(url = url)
)
}
private fun MessageAudioContent.update(url: String,
encryptedFileInfo: EncryptedFileInfo?): MessageAudioContent {
return copy(
url = if (encryptedFileInfo == null) url else null,
encryptedFileInfo = encryptedFileInfo?.copy(url = url)
)
}
}