Fix issue when opening encrypted files (#3186)
Also always open the file after a successful download
This commit is contained in:
parent
9cb19c0581
commit
195bc8e914
|
@ -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 🗣:
|
||||||
-
|
-
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue