Don't leak MediaFileHandle when downloading voice messages (#1748)

Uses the new `MediaFile.persist()` [API](https://github.com/matrix-org/matrix-rust-sdk/pull/2789) to cache voice messages.
This allows to close the `MediaFile` handle after use and keeping the file.
Also disables rust sdk caching for voice messages as we'll use the app's cache dir for that purpose.

Fixes: https://github.com/vector-im/element-meta/issues/2175
This commit is contained in:
Marco Romano 2023-11-09 15:15:11 +01:00 committed by GitHub
parent 68cedbf8ff
commit 8f1b699271
7 changed files with 44 additions and 13 deletions

View file

@ -24,7 +24,6 @@ import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.toFile
import java.io.File import java.io.File
/** /**
@ -90,14 +89,15 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
source = mediaSource, source = mediaSource,
mimeType = mimeType, mimeType = mimeType,
body = body, body = body,
useCache = false,
).mapCatching { ).mapCatching {
val dest = cachedFile.apply { parentFile?.mkdirs() } it.use { mediaFile ->
// TODO By not closing the MediaFile we're leaking the rust file handle here. val dest = cachedFile.apply { parentFile?.mkdirs() }
// Not that big of a deal but better to avoid it someday. if (mediaFile.persist(dest.path)) {
if (it.toFile().renameTo(dest)) { dest
dest } else {
} else { error("Failed to move file to cache.")
error("Failed to move file to cache.") }
} }
} }
} }

View file

@ -35,7 +35,13 @@ interface MatrixMediaLoader {
* @param source to fetch the data for. * @param source to fetch the data for.
* @param mimeType: optional mime type. * @param mimeType: optional mime type.
* @param body: optional body which will be used to name the file. * @param body: optional body which will be used to name the file.
* @param useCache: if true, the rust sdk will cache the media in its store.
* @return a [Result] of [MediaFile] * @return a [Result] of [MediaFile]
*/ */
suspend fun downloadMediaFile(source: MediaSource, mimeType: String?, body: String?): Result<MediaFile> suspend fun downloadMediaFile(
source: MediaSource,
mimeType: String?,
body: String?,
useCache: Boolean = true,
): Result<MediaFile>
} }

View file

@ -21,10 +21,16 @@ import java.io.File
/** /**
* A wrapper around a media file on the disk. * A wrapper around a media file on the disk.
* When closed the file will be removed from the disk. * When closed the file will be removed from the disk unless [persist] has been used.
*/ */
interface MediaFile : Closeable { interface MediaFile : Closeable {
fun path(): String fun path(): String
/**
* Persists the temp file to the given path. The file will be moved to
* the given path and won't be deleted anymore when closing the handle.
*/
fun persist(path: String): Boolean
} }
fun MediaFile.toFile(): File { fun MediaFile.toFile(): File {

View file

@ -25,6 +25,10 @@ class RustMediaFile(private val inner: MediaFileHandle) : MediaFile {
return inner.path() return inner.path()
} }
override fun persist(path: String): Boolean {
return inner.persist(path)
}
override fun close() { override fun close() {
inner.close() inner.close()
} }

View file

@ -71,7 +71,12 @@ class RustMediaLoader(
} }
} }
override suspend fun downloadMediaFile(source: MediaSource, mimeType: String?, body: String?): Result<MediaFile> = override suspend fun downloadMediaFile(
source: MediaSource,
mimeType: String?,
body: String?,
useCache: Boolean,
): Result<MediaFile> =
withContext(mediaDispatcher) { withContext(mediaDispatcher) {
runCatching { runCatching {
source.toRustMediaSource().use { mediaSource -> source.toRustMediaSource().use { mediaSource ->
@ -79,7 +84,7 @@ class RustMediaLoader(
mediaSource = mediaSource, mediaSource = mediaSource,
body = body, body = body,
mimeType = mimeType ?: MimeTypes.OctetStream, mimeType = mimeType ?: MimeTypes.OctetStream,
useCache = true, useCache = useCache,
tempDir = cacheDirectory.path, tempDir = cacheDirectory.path,
) )
RustMediaFile(mediaFile) RustMediaFile(mediaFile)

View file

@ -17,11 +17,16 @@
package io.element.android.libraries.matrix.test.media package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.media.MediaFile
import java.io.File
class FakeMediaFile(private val path: String) : MediaFile { class FakeMediaFile(private val path: String) : MediaFile {
override fun path(): String { override fun path(): String {
return path return path
} }
override fun persist(path: String): Boolean {
return File(path()).renameTo(File(path))
}
override fun close() = Unit override fun close() = Unit
} }

View file

@ -42,7 +42,12 @@ class FakeMediaLoader : MatrixMediaLoader {
} }
} }
override suspend fun downloadMediaFile(source: MediaSource, mimeType: String?, body: String?): Result<MediaFile> = simulateLongTask { override suspend fun downloadMediaFile(
source: MediaSource,
mimeType: String?,
body: String?,
useCache: Boolean,
): Result<MediaFile> = simulateLongTask {
if (shouldFail) { if (shouldFail) {
Result.failure(RuntimeException()) Result.failure(RuntimeException())
} else { } else {