Fix issue when opening encrypted files (#3186)

Also always open the file after a successful download
This commit is contained in:
Benoit Marty 2021-04-28 15:11:48 +02:00
parent 9cb19c0581
commit 195bc8e914
4 changed files with 48 additions and 29 deletions

View File

@ -13,6 +13,7 @@ Bugfix 🐛:
- Do not invite the current user when creating a room (#3123) - Do not invite the current user when creating a room (#3123)
- Fix color issues when the system theme is changed (#2738) - Fix color issues when the system theme is changed (#2738)
- Fix issues on Android 11 (#3067) - Fix issues on Android 11 (#3067)
- Fix issue when opening encrypted files (#3186)
Translations 🗣: Translations 🗣:
- -

View File

@ -29,14 +29,19 @@ import java.io.File
*/ */
interface FileService { interface FileService {
enum class FileState { sealed class FileState {
IN_CACHE, /**
DOWNLOADING, * The original file is in cache, but the decrypted files can be deleted for security reason.
UNKNOWN * To decrypt the file again, call [downloadFile], the encrypted file will not be downloaded again
* @param decryptedFileInCache true if the decrypted file is available. Always true for clear files.
*/
data class InCache(val decryptedFileInCache: Boolean) : FileState()
object Downloading : FileState()
object Unknown : FileState()
} }
/** /**
* Download a file. * Download a file if necessary and ensure that if the file is encrypted, the file is decrypted.
* 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.
*/ */
suspend fun downloadFile(fileName: String, suspend fun downloadFile(fileName: String,

View File

@ -219,7 +219,7 @@ internal class DefaultFileService @Inject constructor(
fileName: String, fileName: String,
mimeType: String?, mimeType: String?,
elementToDecrypt: ElementToDecrypt?): Boolean { elementToDecrypt: ElementToDecrypt?): Boolean {
return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) is FileService.FileState.InCache
} }
internal data class CachedFiles( internal data class CachedFiles(
@ -256,12 +256,17 @@ internal class DefaultFileService @Inject constructor(
fileName: String, fileName: String,
mimeType: String?, mimeType: String?,
elementToDecrypt: ElementToDecrypt?): FileService.FileState { elementToDecrypt: ElementToDecrypt?): FileService.FileState {
mxcUrl ?: return FileService.FileState.UNKNOWN mxcUrl ?: return FileService.FileState.Unknown
if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE val files = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null)
if (files.file.exists()) {
return FileService.FileState.InCache(
decryptedFileInCache = files.getClearFile().exists()
)
}
val isDownloading = synchronized(ongoing) { val isDownloading = synchronized(ongoing) {
ongoing[mxcUrl] != null ongoing[mxcUrl] != null
} }
return if (isDownloading) FileService.FileState.DOWNLOADING else FileService.FileState.UNKNOWN return if (isDownloading) FileService.FileState.Downloading else FileService.FileState.Unknown
} }
/** /**

View File

@ -78,6 +78,7 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
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.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
@ -823,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
}.exhaustive }.exhaustive
} }
is SendMode.EDIT -> { is SendMode.EDIT -> {
// is original event a reply? // is original event a reply?
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
if (inReplyTo != null) { if (inReplyTo != null) {
@ -846,7 +847,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.MessageSent) _viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft() popDraft()
} }
is SendMode.QUOTE -> { is SendMode.QUOTE -> {
val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
val textMsg = messageContent?.body val textMsg = messageContent?.body
@ -867,7 +868,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.MessageSent) _viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft() popDraft()
} }
is SendMode.REPLY -> { is SendMode.REPLY -> {
state.sendMode.timelineEvent.let { state.sendMode.timelineEvent.let {
room.replyToMessage(it, action.text.toString(), action.autoMarkdown) room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
_viewEvents.post(RoomDetailViewEvents.MessageSent) _viewEvents.post(RoomDetailViewEvents.MessageSent)
@ -1138,7 +1139,6 @@ class RoomDetailViewModel @AssistedInject constructor(
val mxcUrl = action.messageFileContent.getFileUrl() ?: return val mxcUrl = action.messageFileContent.getFileUrl() ?: return
val isLocalSendingFile = action.senderId == session.myUserId val isLocalSendingFile = action.senderId == session.myUserId
&& mxcUrl.startsWith("content://") && mxcUrl.startsWith("content://")
val isDownloaded = session.fileService().isFileInCache(action.messageFileContent)
if (isLocalSendingFile) { if (isLocalSendingFile) {
tryOrNull { Uri.parse(mxcUrl) }?.let { tryOrNull { Uri.parse(mxcUrl) }?.let {
_viewEvents.post(RoomDetailViewEvents.OpenFile( _viewEvents.post(RoomDetailViewEvents.OpenFile(
@ -1147,26 +1147,34 @@ class RoomDetailViewModel @AssistedInject constructor(
null null
)) ))
} }
} else if (isDownloaded) {
// we can open it
session.fileService().getTemporarySharableURI(action.messageFileContent)?.let { uri ->
_viewEvents.post(RoomDetailViewEvents.OpenFile(
action.messageFileContent.mimeType,
uri,
null
))
}
} else { } else {
viewModelScope.launch { viewModelScope.launch {
val result = runCatching { val fileState = session.fileService().fileState(action.messageFileContent)
session.fileService().downloadFile(messageContent = action.messageFileContent) var canOpen = fileState is FileService.FileState.InCache && fileState.decryptedFileInCache
if (!canOpen) {
// First download, or download and decrypt, or decrypt from cache
val result = runCatching {
session.fileService().downloadFile(messageContent = action.messageFileContent)
}
_viewEvents.post(RoomDetailViewEvents.DownloadFileState(
action.messageFileContent.mimeType,
result.getOrNull(),
result.exceptionOrNull()
))
canOpen = result.isSuccess
} }
_viewEvents.post(RoomDetailViewEvents.DownloadFileState( if (canOpen) {
action.messageFileContent.mimeType, // We can now open the file
result.getOrNull(), session.fileService().getTemporarySharableURI(action.messageFileContent)?.let { uri ->
result.exceptionOrNull() _viewEvents.post(RoomDetailViewEvents.OpenFile(
)) action.messageFileContent.mimeType,
uri,
null
))
}
}
} }
} }
} }