Merge pull request #3081 from Dominaezzz/suspend_functions_11

Convert FileService to suspend functions
This commit is contained in:
Benoit Marty 2021-03-29 14:00:12 +02:00 committed by GitHub
commit 0e71dfa8e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 232 additions and 292 deletions

View File

@ -17,11 +17,9 @@
package org.matrix.android.sdk.api.session.file package org.matrix.android.sdk.api.session.file
import android.net.Uri 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.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.model.message.getFileName 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.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.ElementToDecrypt
import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
import java.io.File import java.io.File
@ -41,20 +39,17 @@ interface FileService {
* Download a file. * 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. * 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?, mimeType: String?,
url: String?, url: String?,
elementToDecrypt: ElementToDecrypt?, elementToDecrypt: ElementToDecrypt?): File
callback: MatrixCallback<File>): Cancelable
fun downloadFile(messageContent: MessageWithAttachmentContent, suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File =
callback: MatrixCallback<File>): Cancelable =
downloadFile( downloadFile(
fileName = messageContent.getFileName(), fileName = messageContent.getFileName(),
mimeType = messageContent.mimeType, mimeType = messageContent.mimeType,
url = messageContent.getFileUrl(), url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
callback = callback
) )
fun isFileInCache(mxcUrl: String?, fun isFileInCache(mxcUrl: String?,

View File

@ -20,26 +20,20 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import arrow.core.Try import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.launch import kotlinx.coroutines.completeWith
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request 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.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.file.FileService 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.ElementToDecrypt
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress 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.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.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.md5 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 org.matrix.android.sdk.internal.util.writeToFile
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -53,14 +47,15 @@ internal class DefaultFileService @Inject constructor(
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
@UnauthenticatedWithCertificateWithProgress @UnauthenticatedWithCertificateWithProgress
private val okHttpClient: OkHttpClient, private val okHttpClient: OkHttpClient,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers
private val taskExecutor: TaskExecutor
) : FileService { ) : FileService {
// Legacy folder, will be deleted // Legacy folder, will be deleted
private val legacyFolder = File(sessionCacheDirectory, "MF") private val legacyFolder = File(sessionCacheDirectory, "MF")
// Folder to store downloaded files (not decrypted) // Folder to store downloaded files (not decrypted)
private val downloadFolder = File(sessionCacheDirectory, "F") private val downloadFolder = File(sessionCacheDirectory, "F")
// Folder to store decrypted files // Folder to store decrypted files
private val decryptedFolder = File(downloadFolder, "D") 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 * Retain ongoing downloads to avoid re-downloading and already downloading file
* map of mxCurl to callbacks * map of mxCurl to callbacks
*/ */
private val ongoing = mutableMapOf<String, ArrayList<MatrixCallback<File>>>() private val ongoing = mutableMapOf<String, CompletableDeferred<File>>()
/** /**
* Download file in the cache folder, and eventually decrypt it * Download file in the cache folder, and eventually decrypt it
* TODO looks like files are copied 3 times * TODO looks like files are copied 3 times
*/ */
override fun downloadFile(fileName: String, override suspend fun downloadFile(fileName: String,
mimeType: String?, mimeType: String?,
url: String?, url: String?,
elementToDecrypt: ElementToDecrypt?, elementToDecrypt: ElementToDecrypt?): File {
callback: MatrixCallback<File>): Cancelable { url ?: throw IllegalArgumentException("url is null")
url ?: return NoOpCancellable.also {
callback.onFailure(IllegalArgumentException("url is null"))
}
Timber.v("## FileService downloadFile $url") Timber.v("## FileService downloadFile $url")
synchronized(ongoing) { // TODO: Remove use of `synchronized` in suspend function.
val existingDownload = synchronized(ongoing) {
val existing = ongoing[url] val existing = ongoing[url]
if (existing != null) { if (existing != null) {
Timber.v("## FileService downloadFile is already downloading.. ") Timber.v("## FileService downloadFile is already downloading.. ")
existing.add(callback) existing
return NoOpCancellable
} else { } else {
// mark as tracked // mark as tracked
ongoing[url] = ArrayList() ongoing[url] = CompletableDeferred()
// and proceed to download // and proceed to download
null
} }
} }
return taskExecutor.executorScope.launch(coroutineDispatchers.main) { if (existingDownload != null) {
withContext(coroutineDispatchers.io) { // FIXME If the first downloader cancels then we'll unfortunately be cancelled too.
Try { return existingDownload.await()
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"))
val request = Request.Builder() val result = runCatching {
.url(resolvedUrl) val cachedFiles = withContext(coroutineDispatchers.io) {
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) if (!decryptedFolder.exists()) {
.build() decryptedFolder.mkdirs()
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)
} }
}.flatMap { cachedFiles ->
// Decrypt if necessary // ensure we use unique file name by using URL (mapped to suitable file name)
if (cachedFiles.decryptedFile != null) { // Also we need to add extension for the FileProvider, if not it lot's of app that it's
if (!cachedFiles.decryptedFile.exists()) { // shared with will not function well (even if mime type is passed in the intent)
Timber.v("## FileService: decrypt file") val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
// Ensure the parent folder exists
cachedFiles.decryptedFile.parentFile?.mkdirs() if (!cachedFiles.file.exists()) {
val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
MXEncryptedAttachments.decryptAttachment( val request = Request.Builder()
inputStream, .url(resolvedUrl)
elementToDecrypt, .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
outputStream .build()
)
} val response = okHttpClient.newCall(request).execute()
}
if (!decryptSuccess) { if (!response.isSuccessful) {
return@flatMap Try.Failure(IllegalStateException("Decryption error")) throw IOException()
}
} else {
Timber.v("## FileService: cache hit for decrypted file")
} }
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 { } else {
// Clear file Timber.v("## FileService: cache hit for $url")
Try.just(cachedFiles.file)
} }
}.fold( cachedFiles
{ throwable -> }
callback.onFailure(throwable)
// notify concurrent requests // Decrypt if necessary
val toNotify = synchronized(ongoing) { if (cachedFiles.decryptedFile != null) {
ongoing[url]?.also { if (!cachedFiles.decryptedFile.exists()) {
ongoing.remove(url) Timber.v("## FileService: decrypt file")
} // Ensure the parent folder exists
} cachedFiles.decryptedFile.parentFile?.mkdirs()
toNotify?.forEach { otherCallbacks -> val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
tryOrNull { otherCallbacks.onFailure(throwable) } cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
} MXEncryptedAttachments.decryptAttachment(
}, inputStream,
{ file -> elementToDecrypt,
callback.onSuccess(file) outputStream
// 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) }
} }
} }
) if (!decryptSuccess) {
}.toCancelable() 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, fun storeDataFor(mxcUrl: String,
@ -325,6 +299,7 @@ internal class DefaultFileService @Inject constructor(
companion object { companion object {
private const val ENCRYPTED_FILENAME = "encrypted.bin" private const val ENCRYPTED_FILENAME = "encrypted.bin"
// The extension would be added from the mimetype // The extension would be added from the mimetype
private const val DEFAULT_FILENAME = "file" private const val DEFAULT_FILENAME = "file"
} }

View File

@ -28,10 +28,10 @@ import com.bumptech.glide.signature.ObjectKey
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -113,21 +113,19 @@ class VectorGlideDataFetcher(context: Context,
callback.onLoadFailed(IllegalArgumentException("No File service")) callback.onLoadFailed(IllegalArgumentException("No File service"))
} }
// Use the file vector service, will avoid flickering and redownload after upload // Use the file vector service, will avoid flickering and redownload after upload
fileService.downloadFile( GlobalScope.launch {
fileName = data.filename, val result = runCatching {
mimeType = data.mimeType, fileService.downloadFile(
url = data.url, fileName = data.filename,
elementToDecrypt = data.elementToDecrypt, mimeType = data.mimeType,
callback = object : MatrixCallback<File> { url = data.url,
override fun onSuccess(data: File) { elementToDecrypt = data.elementToDecrypt)
callback.onDataReady(data.inputStream()) }
} result.fold(
{ callback.onDataReady(it.inputStream()) },
override fun onFailure(failure: Throwable) { { callback.onLoadFailed(it as? Exception ?: IOException(it.localizedMessage)) }
callback.onLoadFailed(failure as? Exception ?: IOException(failure.localizedMessage)) )
} }
}
)
// val url = contentUrlResolver.resolveFullSize(data.url) // val url = contentUrlResolver.resolveFullSize(data.url)
// ?: return // ?: return
// //

View File

@ -170,12 +170,12 @@ import im.vector.app.features.widgets.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Shape
import nl.dionsegijn.konfetti.models.Size import nl.dionsegijn.konfetti.models.Size
import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser 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.Session
import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.Event 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.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.net.URL import java.net.URL
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -1676,16 +1675,12 @@ class RoomDetailFragment @Inject constructor(
if (action.messageContent is MessageTextContent) { if (action.messageContent is MessageTextContent) {
shareText(requireContext(), action.messageContent.body) shareText(requireContext(), action.messageContent.body)
} else if (action.messageContent is MessageWithAttachmentContent) { } else if (action.messageContent is MessageWithAttachmentContent) {
session.fileService().downloadFile( lifecycleScope.launch {
messageContent = action.messageContent, val data = session.fileService().downloadFile(messageContent = action.messageContent)
callback = object : MatrixCallback<File> { if (isAdded) {
override fun onSuccess(data: File) { shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri()))
if (isAdded) { }
shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri())) }
}
}
}
)
} }
} }
@ -1706,22 +1701,18 @@ class RoomDetailFragment @Inject constructor(
sharedActionViewModel.pendingAction = action sharedActionViewModel.pendingAction = action
return return
} }
session.fileService().downloadFile( lifecycleScope.launch {
messageContent = action.messageContent, val data = session.fileService().downloadFile(messageContent = action.messageContent)
callback = object : MatrixCallback<File> { if (isAdded) {
override fun onSuccess(data: File) { saveMedia(
if (isAdded) { context = requireContext(),
saveMedia( file = data,
context = requireContext(), title = action.messageContent.body,
file = data, mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), data.toUri()),
title = action.messageContent.body, notificationUtils = notificationUtils
mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), data.toUri()), )
notificationUtils = notificationUtils }
) }
}
}
}
)
} }
private fun handleActions(action: EventSharedAction) { private fun handleActions(action: EventSharedAction) {

View File

@ -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.rx
import org.matrix.android.sdk.rx.unwrap import org.matrix.android.sdk.rx.unwrap
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -1140,25 +1139,17 @@ class RoomDetailViewModel @AssistedInject constructor(
)) ))
} }
} else { } else {
session.fileService().downloadFile( viewModelScope.launch {
messageContent = action.messageFileContent, val result = runCatching {
callback = object : MatrixCallback<File> { session.fileService().downloadFile(messageContent = action.messageFileContent)
override fun onSuccess(data: File) { }
_viewEvents.post(RoomDetailViewEvents.DownloadFileState(
action.messageFileContent.mimeType,
data,
null
))
}
override fun onFailure(failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.DownloadFileState(
_viewEvents.post(RoomDetailViewEvents.DownloadFileState( action.messageFileContent.mimeType,
action.messageFileContent.mimeType, result.getOrNull(),
null, result.exceptionOrNull()
failure ))
)) }
}
})
} }
} }

View File

@ -31,7 +31,8 @@ import im.vector.lib.attachmentviewer.AttachmentInfo
import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.AttachmentSourceProvider
import im.vector.lib.attachmentviewer.ImageLoaderTarget import im.vector.lib.attachmentviewer.ImageLoaderTarget
import im.vector.lib.attachmentviewer.VideoLoaderTarget 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.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -152,21 +153,20 @@ abstract class BaseAttachmentProvider<Type>(
target.onVideoURLReady(info.uid, data.url) target.onVideoURLReady(info.uid, data.url)
} else { } else {
target.onVideoFileLoading(info.uid) target.onVideoFileLoading(info.uid)
fileService.downloadFile( GlobalScope.launch {
fileName = data.filename, val result = runCatching {
mimeType = data.mimeType, fileService.downloadFile(
url = data.url, fileName = data.filename,
elementToDecrypt = data.elementToDecrypt, mimeType = data.mimeType,
callback = object : MatrixCallback<File> { url = data.url,
override fun onSuccess(data: File) { elementToDecrypt = data.elementToDecrypt
target.onVideoFileReady(info.uid, data) )
} }
result.fold(
override fun onFailure(failure: Throwable) { { target.onVideoFileReady(info.uid, it) },
target.onVideoFileLoadFailed(info.uid) { target.onVideoFileLoadFailed(info.uid) }
} )
} }
)
} }
} }

View File

@ -19,7 +19,8 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.lib.attachmentviewer.AttachmentInfo 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.file.FileService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -77,20 +78,16 @@ class DataAttachmentRoomProvider(
override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { override fun getFileForSharing(position: Int, callback: (File?) -> Unit) {
val item = getItem(position) val item = getItem(position)
fileService.downloadFile( GlobalScope.launch {
fileName = item.filename, val result = runCatching {
mimeType = item.mimeType, fileService.downloadFile(
url = item.url, fileName = item.filename,
elementToDecrypt = item.elementToDecrypt, mimeType = item.mimeType,
callback = object : MatrixCallback<File> { url = item.url,
override fun onSuccess(data: File) { elementToDecrypt = item.elementToDecrypt
callback(data) )
} }
callback(result.getOrNull())
override fun onFailure(failure: Throwable) { }
callback(null)
}
}
)
} }
} }

View File

@ -19,7 +19,8 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.lib.attachmentviewer.AttachmentInfo 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.events.model.toModel
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@ -125,21 +126,16 @@ class RoomEventsAttachmentProvider(
val messageContent = timelineEvent.root.getClearContent().toModel<MessageContent>() val messageContent = timelineEvent.root.getClearContent().toModel<MessageContent>()
as? MessageWithAttachmentContent as? MessageWithAttachmentContent
?: return@let ?: return@let
fileService.downloadFile( GlobalScope.launch {
fileName = messageContent.body, val result = runCatching {
mimeType = messageContent.mimeType, fileService.downloadFile(
url = messageContent.getFileUrl(), fileName = messageContent.body,
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), mimeType = messageContent.mimeType,
callback = object : MatrixCallback<File> { url = messageContent.getFileUrl(),
override fun onSuccess(data: File) { elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
callback(data) }
} callback(result.getOrNull())
}
override fun onFailure(failure: Throwable) {
callback(null)
}
}
)
} }
} }
} }

View File

@ -25,11 +25,11 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.files.LocalFilesHelper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.net.URLEncoder import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject
@ -74,28 +74,31 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
thumbnailView.isVisible = true thumbnailView.isVisible = true
loadingView.isVisible = true loadingView.isVisible = true
activeSessionHolder.getActiveSession().fileService() GlobalScope.launch {
.downloadFile( val result = runCatching {
fileName = data.filename, activeSessionHolder.getActiveSession().fileService()
mimeType = data.mimeType, .downloadFile(
url = data.url, fileName = data.filename,
elementToDecrypt = data.elementToDecrypt, mimeType = data.mimeType,
callback = object : MatrixCallback<File> { url = data.url,
override fun onSuccess(data: File) { elementToDecrypt = data.elementToDecrypt)
thumbnailView.isVisible = false }
loadingView.isVisible = false result.fold(
videoView.isVisible = true { data ->
thumbnailView.isVisible = false
loadingView.isVisible = false
videoView.isVisible = true
videoView.setVideoPath(data.path) videoView.setVideoPath(data.path)
videoView.start() videoView.start()
} },
{
override fun onFailure(failure: Throwable) { loadingView.isVisible = false
loadingView.isVisible = false errorView.isVisible = true
errorView.isVisible = true errorView.text = errorFormatter.toHumanReadable(it)
errorView.text = errorFormatter.toHumanReadable(failure) }
} )
}) }
} }
} else { } else {
val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) val resolvedUrl = contentUrlResolver.resolveFullSize(data.url)
@ -112,28 +115,31 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
thumbnailView.isVisible = true thumbnailView.isVisible = true
loadingView.isVisible = true loadingView.isVisible = true
activeSessionHolder.getActiveSession().fileService() GlobalScope.launch {
.downloadFile( val result = runCatching {
fileName = data.filename, activeSessionHolder.getActiveSession().fileService()
mimeType = data.mimeType, .downloadFile(
url = data.url, fileName = data.filename,
elementToDecrypt = null, mimeType = data.mimeType,
callback = object : MatrixCallback<File> { url = data.url,
override fun onSuccess(data: File) { elementToDecrypt = null)
thumbnailView.isVisible = false }
loadingView.isVisible = false result.fold(
videoView.isVisible = true { data ->
thumbnailView.isVisible = false
loadingView.isVisible = false
videoView.isVisible = true
videoView.setVideoPath(data.path) videoView.setVideoPath(data.path)
videoView.start() videoView.start()
} },
{
override fun onFailure(failure: Throwable) { loadingView.isVisible = false
loadingView.isVisible = false errorView.isVisible = true
errorView.isVisible = true errorView.text = errorFormatter.toHumanReadable(it)
errorView.text = errorFormatter.toHumanReadable(failure) }
} )
}) }
} }
} }
} }

View File

@ -32,10 +32,8 @@ import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.message.MessageType 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.rx
import org.matrix.android.sdk.rx.unwrap import org.matrix.android.sdk.rx.unwrap
import java.io.File
class RoomUploadsViewModel @AssistedInject constructor( class RoomUploadsViewModel @AssistedInject constructor(
@Assisted initialState: RoomUploadsViewState, @Assisted initialState: RoomUploadsViewState,
@ -130,12 +128,8 @@ class RoomUploadsViewModel @AssistedInject constructor(
private fun handleShare(action: RoomUploadsAction.Share) { private fun handleShare(action: RoomUploadsAction.Share) {
viewModelScope.launch { viewModelScope.launch {
try { try {
val file = awaitCallback<File> { val file = session.fileService().downloadFile(
session.fileService().downloadFile( messageContent = action.uploadEvent.contentWithAttachmentContent)
messageContent = action.uploadEvent.contentWithAttachmentContent,
callback = it
)
}
_viewEvents.post(RoomUploadsViewEvents.FileReadyForSharing(file)) _viewEvents.post(RoomUploadsViewEvents.FileReadyForSharing(file))
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomUploadsViewEvents.Failure(failure)) _viewEvents.post(RoomUploadsViewEvents.Failure(failure))
@ -146,11 +140,8 @@ class RoomUploadsViewModel @AssistedInject constructor(
private fun handleDownload(action: RoomUploadsAction.Download) { private fun handleDownload(action: RoomUploadsAction.Download) {
viewModelScope.launch { viewModelScope.launch {
try { try {
val file = awaitCallback<File> { val file = session.fileService().downloadFile(
session.fileService().downloadFile( messageContent = action.uploadEvent.contentWithAttachmentContent)
messageContent = action.uploadEvent.contentWithAttachmentContent,
callback = it)
}
_viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body))
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomUploadsViewEvents.Failure(failure)) _viewEvents.post(RoomUploadsViewEvents.Failure(failure))