Avoid incomplete downloads in cache

Previously, when a download was aborted (e.g. due to a bad internet
connection), a partly downloaded file was remaining in cache, which
would then be delivered upon later requests.
This can lead e.g. to chats where images aren't loading.

To avoid this, first download files to a temporary file that is not the
final cache file, and only rename/move it on finish.

Note that if you already have broken downloads, you still need to clear
cache once to get rid of them after this commit, but it should not
occur anymore afterwards.
This commit is contained in:
SpiritCroc 2021-07-10 10:51:27 +02:00
parent 910c0ff326
commit 4ef1f5c90f
1 changed files with 14 additions and 2 deletions

View File

@ -131,8 +131,14 @@ internal class DefaultFileService @Inject constructor(
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)
// Write to a tmp file first, so if we abort before done, we don't have a broken cached file
val tmpFile = File(cachedFiles.file.parentFile, "${cachedFiles.file.name}.tmp")
if (tmpFile.exists()) {
Timber.v("## FileService: discard aborted tmp file ${tmpFile.path}")
}
writeToFile(source.inputStream(), tmpFile)
response.close()
tmpFile.renameTo(cachedFiles.file)
} else {
Timber.v("## FileService: cache hit for $url")
}
@ -145,8 +151,13 @@ internal class DefaultFileService @Inject constructor(
Timber.v("## FileService: decrypt file")
// Ensure the parent folder exists
cachedFiles.decryptedFile.parentFile?.mkdirs()
// Write to a tmp file first, so if we abort before done, we don't have a broken cached file
val tmpFile = File(cachedFiles.decryptedFile.parentFile, "${cachedFiles.decryptedFile.name}.tmp")
if (tmpFile.exists()) {
Timber.v("## FileService: discard aborted tmp file ${tmpFile.path}")
}
val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
tmpFile.outputStream().buffered().use { outputStream ->
MXEncryptedAttachments.decryptAttachment(
inputStream,
elementToDecrypt,
@ -154,6 +165,7 @@ internal class DefaultFileService @Inject constructor(
)
}
}
tmpFile.renameTo(cachedFiles.decryptedFile)
if (!decryptSuccess) {
throw IllegalStateException("Decryption error")
}