Downloaded (large?) files are truncated

This commit is contained in:
Valere 2020-06-23 15:21:07 +02:00 committed by Benoit Marty
parent 19f16c9e56
commit 80e8cd4191
5 changed files with 90 additions and 34 deletions

View File

@ -58,6 +58,7 @@ internal class DefaultFileService @Inject constructor(
/** /**
* Download file in the cache folder, and eventually decrypt it * Download file in the cache folder, and eventually decrypt it
* TODO implement clear file, to delete "MF" * TODO implement clear file, to delete "MF"
* TODO looks like files are copied 3 times
*/ */
override fun downloadFile(downloadMode: FileService.DownloadMode, override fun downloadFile(downloadMode: FileService.DownloadMode,
id: String, id: String,
@ -87,20 +88,30 @@ internal class DefaultFileService @Inject constructor(
return@flatMap Try.Failure(e) return@flatMap Try.Failure(e)
} }
var inputStream = response.body?.byteStream() if (!response.isSuccessful) {
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
if (!response.isSuccessful || inputStream == null) {
return@flatMap Try.Failure(IOException()) 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()}")
if (elementToDecrypt != null) { if (elementToDecrypt != null) {
Timber.v("## decrypt file") Timber.v("## decrypt file")
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) val decryptedStream = MXEncryptedAttachments.decryptAttachment(source.inputStream(), elementToDecrypt)
?: return@flatMap Try.Failure(IllegalStateException("Decryption error")) response.close()
if (decryptedStream == null) {
return@flatMap Try.Failure(IllegalStateException("Decryption error"))
} else {
decryptedStream.use {
writeToFile(decryptedStream, destFile)
}
}
} else {
writeToFile(source.inputStream(), destFile)
response.close()
} }
writeToFile(inputStream, destFile)
} }
Try.just(copyFile(destFile, downloadMode)) Try.just(copyFile(destFile, downloadMode))

View File

@ -17,10 +17,8 @@
package im.vector.matrix.android.internal.util package im.vector.matrix.android.internal.util
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import okio.buffer
import okio.sink
import okio.source
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
/** /**
@ -28,9 +26,7 @@ import java.io.InputStream
*/ */
@WorkerThread @WorkerThread
fun writeToFile(inputStream: InputStream, outputFile: File) { fun writeToFile(inputStream: InputStream, outputFile: File) {
inputStream.source().buffer().use { input -> FileOutputStream(outputFile).use {
outputFile.sink().buffer().use { output -> inputStream.copyTo(it)
output.writeAll(input)
}
} }
} }

View File

@ -19,12 +19,14 @@ package im.vector.riotx.core.files
import android.app.DownloadManager import android.app.DownloadManager
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import arrow.core.Try import arrow.core.Try
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import okio.source
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -56,7 +58,7 @@ fun addEntryToDownloadManager(context: Context,
file: File, file: File,
mimeType: String, mimeType: String,
title: String = file.name, title: String = file.name,
description: String = file.name) { description: String = file.name) : Uri? {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
@ -65,17 +67,31 @@ fun addEntryToDownloadManager(context: Context,
put(MediaStore.Downloads.MIME_TYPE, mimeType) put(MediaStore.Downloads.MIME_TYPE, mimeType)
put(MediaStore.Downloads.SIZE, file.length()) put(MediaStore.Downloads.SIZE, file.length())
} }
context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)?.let { uri -> return context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
context.contentResolver.openOutputStream(uri)?.use { outputStream -> ?.let { uri ->
outputStream.sink().buffer().write(file.inputStream().use { it.readBytes() }) Timber.v("## addEntryToDownloadManager(): $uri")
val source = file.inputStream().source().buffer()
context.contentResolver.openOutputStream(uri)?.sink()?.buffer()?.let { sink ->
source.use { input ->
sink.use { output ->
output.writeAll(input)
} }
} }
}
uri
} ?: run {
Timber.v("## addEntryToDownloadManager(): context.contentResolver.insert failed")
null
}
} else { } else {
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager? val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
downloadManager?.addCompletedDownload(title, description, true, mimeType, file.absolutePath, file.length(), true) downloadManager?.addCompletedDownload(title, description, true, mimeType, file.absolutePath, file.length(), true)
return null
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## addEntryToDownloadManager(): Exception") Timber.e(e, "## addEntryToDownloadManager(): Exception")
} }
return null
} }

View File

@ -167,6 +167,7 @@ import im.vector.riotx.features.invite.VectorInviteView
import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoContentRenderer
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.permalink.NavigationInterceptor import im.vector.riotx.features.permalink.NavigationInterceptor
import im.vector.riotx.features.permalink.PermalinkHandler import im.vector.riotx.features.permalink.PermalinkHandler
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
@ -209,6 +210,7 @@ class RoomDetailFragment @Inject constructor(
private val eventHtmlRenderer: EventHtmlRenderer, private val eventHtmlRenderer: EventHtmlRenderer,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val notificationUtils: NotificationUtils,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) : private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) :
VectorBaseFragment(), VectorBaseFragment(),
TimelineEventController.Callback, TimelineEventController.Callback,
@ -486,8 +488,19 @@ class RoomDetailFragment @Inject constructor(
if (action.throwable != null) { if (action.throwable != null) {
activity.toast(errorFormatter.toHumanReadable(action.throwable)) activity.toast(errorFormatter.toHumanReadable(action.throwable))
} else if (action.file != null) { } else if (action.file != null) {
activity.toast(getString(R.string.downloaded_file, action.file.path)) addEntryToDownloadManager(activity, action.file, action.mimeType)?.let {
addEntryToDownloadManager(activity, action.file, action.mimeType) // This is a temporary solution to help users find downloaded files
// there is a better way to do that
// On android Q+ this method returns the file URI, on older
// it returns null, and the download manager handles the notification
notificationUtils.buildDownloadFileNotification(
it,
action.file.name ?: "file",
action.mimeType
).let { notification ->
notificationUtils.showNotificationMessage("DL", action.file.absolutePath.hashCode(), notification)
}
}
} }
} }

View File

@ -480,6 +480,26 @@ class NotificationUtils @Inject constructor(private val context: Context,
.build() .build()
} }
fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification {
return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setGroup(stringProvider.getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_download)
.setContentText(stringProvider.getString(R.string.downloaded_file, fileName))
.setAutoCancel(true)
.apply {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mimeType)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
PendingIntent.getActivity(
context, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT
).let {
setContentIntent(it)
}
}
.build()
}
/** /**
* Build a notification for a Room * Build a notification for a Room
*/ */