Merge pull request #3081 from Dominaezzz/suspend_functions_11
Convert FileService to suspend functions
This commit is contained in:
commit
0e71dfa8e1
@ -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?,
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
//
|
//
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
))
|
||||||
))
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
}
|
||||||
}
|
)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user