Downloaded (large?) files are truncated
This commit is contained in:
parent
19f16c9e56
commit
80e8cd4191
@ -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))
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user