From b6f4be289419017cb3b122c5f0e8906119911229 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 28 Mar 2021 13:52:16 +0100 Subject: [PATCH] Convert FileService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/file/FileService.kt | 13 +- .../internal/session/DefaultFileService.kt | 203 ++++++++---------- .../app/core/glide/VectorGlideModelLoader.kt | 32 ++- .../home/room/detail/RoomDetailFragment.kt | 47 ++-- .../home/room/detail/RoomDetailViewModel.kt | 29 +-- .../features/media/BaseAttachmentProvider.kt | 32 +-- .../media/DataAttachmentRoomProvider.kt | 29 ++- .../media/RoomEventsAttachmentProvider.kt | 28 ++- .../features/media/VideoContentRenderer.kt | 94 ++++---- .../uploads/RoomUploadsViewModel.kt | 17 +- 10 files changed, 232 insertions(+), 292 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index bcdb5ea257..adfdc2498e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -17,11 +17,9 @@ package org.matrix.android.sdk.api.session.file import android.net.Uri -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File @@ -41,20 +39,17 @@ interface FileService { * Download a file. * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ - fun downloadFile(fileName: String, + suspend fun downloadFile(fileName: String, mimeType: String?, url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback): Cancelable + elementToDecrypt: ElementToDecrypt?): File - fun downloadFile(messageContent: MessageWithAttachmentContent, - callback: MatrixCallback): Cancelable = + suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File = downloadFile( fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - callback = callback + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) fun isFileInCache(mxcUrl: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 07cde3da60..d05ee48c1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -20,26 +20,20 @@ import android.content.Context import android.net.Uri import android.webkit.MimeTypeMap import androidx.core.content.FileProvider -import arrow.core.Try -import kotlinx.coroutines.launch +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER -import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.md5 -import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber import java.io.File @@ -53,14 +47,15 @@ internal class DefaultFileService @Inject constructor( private val contentUrlResolver: ContentUrlResolver, @UnauthenticatedWithCertificateWithProgress private val okHttpClient: OkHttpClient, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val taskExecutor: TaskExecutor + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : FileService { // Legacy folder, will be deleted private val legacyFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded files (not decrypted) private val downloadFolder = File(sessionCacheDirectory, "F") + // Folder to store decrypted files private val decryptedFolder = File(downloadFolder, "D") @@ -73,134 +68,113 @@ internal class DefaultFileService @Inject constructor( * Retain ongoing downloads to avoid re-downloading and already downloading file * map of mxCurl to callbacks */ - private val ongoing = mutableMapOf>>() + private val ongoing = mutableMapOf>() /** * Download file in the cache folder, and eventually decrypt it * TODO looks like files are copied 3 times */ - override fun downloadFile(fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback): Cancelable { - url ?: return NoOpCancellable.also { - callback.onFailure(IllegalArgumentException("url is null")) - } + override suspend fun downloadFile(fileName: String, + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt?): File { + url ?: throw IllegalArgumentException("url is null") Timber.v("## FileService downloadFile $url") - synchronized(ongoing) { + // TODO: Remove use of `synchronized` in suspend function. + val existingDownload = synchronized(ongoing) { val existing = ongoing[url] if (existing != null) { Timber.v("## FileService downloadFile is already downloading.. ") - existing.add(callback) - return NoOpCancellable + existing } else { // mark as tracked - ongoing[url] = ArrayList() + ongoing[url] = CompletableDeferred() // and proceed to download + null } } - return taskExecutor.executorScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.io) { - Try { - if (!decryptedFolder.exists()) { - decryptedFolder.mkdirs() - } - // ensure we use unique file name by using URL (mapped to suitable file name) - // Also we need to add extension for the FileProvider, if not it lot's of app that it's - // shared with will not function well (even if mime type is passed in the intent) - getFiles(url, fileName, mimeType, elementToDecrypt != null) - }.flatMap { cachedFiles -> - if (!cachedFiles.file.exists()) { - val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) + if (existingDownload != null) { + // FIXME If the first downloader cancels then we'll unfortunately be cancelled too. + return existingDownload.await() + } - val request = Request.Builder() - .url(resolvedUrl) - .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) - .build() - - val response = try { - okHttpClient.newCall(request).execute() - } catch (e: Throwable) { - return@flatMap Try.Failure(e) - } - - if (!response.isSuccessful) { - return@flatMap Try.Failure(IOException()) - } - - val source = response.body?.source() - ?: return@flatMap Try.Failure(IOException()) - - Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") - - // Write the file to cache (encrypted version if the file is encrypted) - writeToFile(source.inputStream(), cachedFiles.file) - response.close() - } else { - Timber.v("## FileService: cache hit for $url") - } - - Try.just(cachedFiles) + val result = runCatching { + val cachedFiles = withContext(coroutineDispatchers.io) { + if (!decryptedFolder.exists()) { + decryptedFolder.mkdirs() } - }.flatMap { cachedFiles -> - // Decrypt if necessary - if (cachedFiles.decryptedFile != null) { - if (!cachedFiles.decryptedFile.exists()) { - Timber.v("## FileService: decrypt file") - // Ensure the parent folder exists - cachedFiles.decryptedFile.parentFile?.mkdirs() - val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> - cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> - MXEncryptedAttachments.decryptAttachment( - inputStream, - elementToDecrypt, - outputStream - ) - } - } - if (!decryptSuccess) { - return@flatMap Try.Failure(IllegalStateException("Decryption error")) - } - } else { - Timber.v("## FileService: cache hit for decrypted file") + + // ensure we use unique file name by using URL (mapped to suitable file name) + // Also we need to add extension for the FileProvider, if not it lot's of app that it's + // shared with will not function well (even if mime type is passed in the intent) + val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null) + + if (!cachedFiles.file.exists()) { + val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null") + + val request = Request.Builder() + .url(resolvedUrl) + .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) + .build() + + val response = okHttpClient.newCall(request).execute() + + if (!response.isSuccessful) { + throw IOException() } - Try.just(cachedFiles.decryptedFile) + + val source = response.body?.source() ?: throw IOException() + + Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") + + // Write the file to cache (encrypted version if the file is encrypted) + writeToFile(source.inputStream(), cachedFiles.file) + response.close() } else { - // Clear file - Try.just(cachedFiles.file) + Timber.v("## FileService: cache hit for $url") } - }.fold( - { throwable -> - callback.onFailure(throwable) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[url]?.also { - ongoing.remove(url) - } - } - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onFailure(throwable) } - } - }, - { file -> - callback.onSuccess(file) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[url]?.also { - ongoing.remove(url) - } - } - Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onSuccess(file) } + cachedFiles + } + + // Decrypt if necessary + if (cachedFiles.decryptedFile != null) { + if (!cachedFiles.decryptedFile.exists()) { + Timber.v("## FileService: decrypt file") + // Ensure the parent folder exists + cachedFiles.decryptedFile.parentFile?.mkdirs() + val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> + cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> + MXEncryptedAttachments.decryptAttachment( + inputStream, + elementToDecrypt, + outputStream + ) } } - ) - }.toCancelable() + if (!decryptSuccess) { + throw IllegalStateException("Decryption error") + } + } else { + Timber.v("## FileService: cache hit for decrypted file") + } + cachedFiles.decryptedFile + } else { + // Clear file + cachedFiles.file + } + } + + // notify concurrent requests + val toNotify = synchronized(ongoing) { ongoing.remove(url) } + result.onSuccess { + Timber.v("## FileService additional to notify is > 0 ") + } + toNotify?.completeWith(result) + + return result.getOrThrow() } fun storeDataFor(mxcUrl: String, @@ -325,6 +299,7 @@ internal class DefaultFileService @Inject constructor( companion object { private const val ENCRYPTED_FILENAME = "encrypted.bin" + // The extension would be added from the mimetype private const val DEFAULT_FILENAME = "file" } diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 81e81bb78a..cbc5effe44 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -28,10 +28,10 @@ import com.bumptech.glide.signature.ObjectKey import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.files.LocalFilesHelper import im.vector.app.features.media.ImageContentRenderer +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import okhttp3.OkHttpClient -import org.matrix.android.sdk.api.MatrixCallback import timber.log.Timber -import java.io.File import java.io.IOException import java.io.InputStream @@ -113,21 +113,19 @@ class VectorGlideDataFetcher(context: Context, callback.onLoadFailed(IllegalArgumentException("No File service")) } // Use the file vector service, will avoid flickering and redownload after upload - fileService.downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = data.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - callback.onDataReady(data.inputStream()) - } - - override fun onFailure(failure: Throwable) { - callback.onLoadFailed(failure as? Exception ?: IOException(failure.localizedMessage)) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = data.elementToDecrypt) + } + result.fold( + { callback.onDataReady(it.inputStream()) }, + { callback.onLoadFailed(it as? Exception ?: IOException(it.localizedMessage)) } + ) + } // val url = contentUrlResolver.resolveFullSize(data.url) // ?: return // diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 64bffcb49c..f116c2b9af 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -170,12 +170,12 @@ import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Size import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.commonmark.parser.Parser -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Event @@ -201,7 +201,6 @@ import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber -import java.io.File import java.net.URL import java.util.UUID import java.util.concurrent.TimeUnit @@ -1676,16 +1675,12 @@ class RoomDetailFragment @Inject constructor( if (action.messageContent is MessageTextContent) { shareText(requireContext(), action.messageContent.body) } else if (action.messageContent is MessageWithAttachmentContent) { - session.fileService().downloadFile( - messageContent = action.messageContent, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - if (isAdded) { - shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri())) - } - } - } - ) + lifecycleScope.launch { + val data = session.fileService().downloadFile(messageContent = action.messageContent) + if (isAdded) { + shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri())) + } + } } } @@ -1706,22 +1701,18 @@ class RoomDetailFragment @Inject constructor( sharedActionViewModel.pendingAction = action return } - session.fileService().downloadFile( - messageContent = action.messageContent, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - if (isAdded) { - saveMedia( - context = requireContext(), - file = data, - title = action.messageContent.body, - mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), data.toUri()), - notificationUtils = notificationUtils - ) - } - } - } - ) + lifecycleScope.launch { + val data = session.fileService().downloadFile(messageContent = action.messageContent) + if (isAdded) { + saveMedia( + context = requireContext(), + file = data, + title = action.messageContent.body, + mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), data.toUri()), + notificationUtils = notificationUtils + ) + } + } } private fun handleActions(action: EventSharedAction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..19912ee02e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -106,7 +106,6 @@ import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap import timber.log.Timber -import java.io.File import java.util.UUID import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -1140,25 +1139,17 @@ class RoomDetailViewModel @AssistedInject constructor( )) } } else { - session.fileService().downloadFile( - messageContent = action.messageFileContent, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - _viewEvents.post(RoomDetailViewEvents.DownloadFileState( - action.messageFileContent.mimeType, - data, - null - )) - } + viewModelScope.launch { + val result = runCatching { + session.fileService().downloadFile(messageContent = action.messageFileContent) + } - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomDetailViewEvents.DownloadFileState( - action.messageFileContent.mimeType, - null, - failure - )) - } - }) + _viewEvents.post(RoomDetailViewEvents.DownloadFileState( + action.messageFileContent.mimeType, + result.getOrNull(), + result.exceptionOrNull() + )) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index 11b8832c94..103f42e903 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -31,7 +31,8 @@ import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.ImageLoaderTarget import im.vector.lib.attachmentviewer.VideoLoaderTarget -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -152,21 +153,20 @@ abstract class BaseAttachmentProvider( target.onVideoURLReady(info.uid, data.url) } else { target.onVideoFileLoading(info.uid) - fileService.downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = data.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - target.onVideoFileReady(info.uid, data) - } - - override fun onFailure(failure: Throwable) { - target.onVideoFileLoadFailed(info.uid) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = data.elementToDecrypt + ) + } + result.fold( + { target.onVideoFileReady(info.uid, it) }, + { target.onVideoFileLoadFailed(info.uid) } + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 328d8f943e..d326b8e50a 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -19,7 +19,8 @@ package im.vector.app.features.media import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -77,20 +78,16 @@ class DataAttachmentRoomProvider( override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { val item = getItem(position) - fileService.downloadFile( - fileName = item.filename, - mimeType = item.mimeType, - url = item.url, - elementToDecrypt = item.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - callback(data) - } - - override fun onFailure(failure: Throwable) { - callback(null) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = item.filename, + mimeType = item.mimeType, + url = item.url, + elementToDecrypt = item.elementToDecrypt + ) + } + callback(result.getOrNull()) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 53c5dac9ad..fd3386826a 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -19,7 +19,8 @@ package im.vector.app.features.media import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -125,21 +126,16 @@ class RoomEventsAttachmentProvider( val messageContent = timelineEvent.root.getClearContent().toModel() as? MessageWithAttachmentContent ?: return@let - fileService.downloadFile( - fileName = messageContent.body, - mimeType = messageContent.mimeType, - url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - callback(data) - } - - override fun onFailure(failure: Throwable) { - callback(null) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = messageContent.body, + mimeType = messageContent.mimeType, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) + } + callback(result.getOrNull()) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index 7e4ea15ff0..59b612afb1 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -25,11 +25,11 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.files.LocalFilesHelper +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import timber.log.Timber -import java.io.File import java.net.URLEncoder import javax.inject.Inject @@ -74,28 +74,31 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc thumbnailView.isVisible = true loadingView.isVisible = true - activeSessionHolder.getActiveSession().fileService() - .downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = data.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - thumbnailView.isVisible = false - loadingView.isVisible = false - videoView.isVisible = true + GlobalScope.launch { + val result = runCatching { + activeSessionHolder.getActiveSession().fileService() + .downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = data.elementToDecrypt) + } + result.fold( + { data -> + thumbnailView.isVisible = false + loadingView.isVisible = false + videoView.isVisible = true - videoView.setVideoPath(data.path) - videoView.start() - } - - override fun onFailure(failure: Throwable) { - loadingView.isVisible = false - errorView.isVisible = true - errorView.text = errorFormatter.toHumanReadable(failure) - } - }) + videoView.setVideoPath(data.path) + videoView.start() + }, + { + loadingView.isVisible = false + errorView.isVisible = true + errorView.text = errorFormatter.toHumanReadable(it) + } + ) + } } } else { val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) @@ -112,28 +115,31 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc thumbnailView.isVisible = true loadingView.isVisible = true - activeSessionHolder.getActiveSession().fileService() - .downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = null, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - thumbnailView.isVisible = false - loadingView.isVisible = false - videoView.isVisible = true + GlobalScope.launch { + val result = runCatching { + activeSessionHolder.getActiveSession().fileService() + .downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = null) + } + result.fold( + { data -> + thumbnailView.isVisible = false + loadingView.isVisible = false + videoView.isVisible = true - videoView.setVideoPath(data.path) - videoView.start() - } - - override fun onFailure(failure: Throwable) { - loadingView.isVisible = false - errorView.isVisible = true - errorView.text = errorFormatter.toHumanReadable(failure) - } - }) + videoView.setVideoPath(data.path) + videoView.start() + }, + { + loadingView.isVisible = false + errorView.isVisible = true + errorView.text = errorFormatter.toHumanReadable(it) + } + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index cdf139c7f6..bae4847f7e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -32,10 +32,8 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap -import java.io.File class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, @@ -130,12 +128,8 @@ class RoomUploadsViewModel @AssistedInject constructor( private fun handleShare(action: RoomUploadsAction.Share) { viewModelScope.launch { try { - val file = awaitCallback { - session.fileService().downloadFile( - messageContent = action.uploadEvent.contentWithAttachmentContent, - callback = it - ) - } + val file = session.fileService().downloadFile( + messageContent = action.uploadEvent.contentWithAttachmentContent) _viewEvents.post(RoomUploadsViewEvents.FileReadyForSharing(file)) } catch (failure: Throwable) { _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) @@ -146,11 +140,8 @@ class RoomUploadsViewModel @AssistedInject constructor( private fun handleDownload(action: RoomUploadsAction.Download) { viewModelScope.launch { try { - val file = awaitCallback { - session.fileService().downloadFile( - messageContent = action.uploadEvent.contentWithAttachmentContent, - callback = it) - } + val file = session.fileService().downloadFile( + messageContent = action.uploadEvent.contentWithAttachmentContent) _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) } catch (failure: Throwable) { _viewEvents.post(RoomUploadsViewEvents.Failure(failure))