From f3bc39a0c57c7f28d71313f6fa26a8e24164de49 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:14:55 +0100 Subject: [PATCH 01/20] Cleanup --- .../android/sdk/internal/session/sync/job/SyncThread.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 74cba5e796..424c24663c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker import org.matrix.android.sdk.internal.session.sync.SyncTask -import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.Debouncer import org.matrix.android.sdk.internal.util.createUIHandler @@ -50,14 +49,13 @@ private const val RETRY_WAIT_TIME_MS = 10_000L private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L internal class SyncThread @Inject constructor(private val syncTask: SyncTask, - private val typingUsersTracker: DefaultTypingUsersTracker, private val networkConnectivityChecker: NetworkConnectivityChecker, private val backgroundDetectionObserver: BackgroundDetectionObserver, private val activeCallHandler: ActiveCallHandler ) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.Idle - private var liveState = MutableLiveData(state) + private var liveState = MutableLiveData(state) private val lock = Object() private val syncScope = CoroutineScope(SupervisorJob()) private val debouncer = Debouncer(createUIHandler()) @@ -231,7 +229,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, return } state = newState - debouncer.debounce("post_state", Runnable { + debouncer.debounce("post_state", { liveState.value = newState }, 150) } From 28bfea6af0f611ab40981b0b2b8181da21108556 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:31:16 +0100 Subject: [PATCH 02/20] This code is for debug build (see the path), so no need to check again --- .../interceptors/FormattedJsonHttpLogger.kt | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 630f6f1e29..849a464867 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -38,31 +38,28 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { */ @Synchronized override fun log(@NonNull message: String) { - // In RELEASE there is no log, but for sure, test again BuildConfig.DEBUG - if (BuildConfig.DEBUG) { - Timber.v(message) + Timber.v(message) - if (message.startsWith("{")) { - // JSON Detected - try { - val o = JSONObject(message) - logJson(o.toString(INDENT_SPACE)) - } catch (e: JSONException) { - // Finally this is not a JSON string... - Timber.e(e) - } - } else if (message.startsWith("[")) { - // JSON Array detected - try { - val o = JSONArray(message) - logJson(o.toString(INDENT_SPACE)) - } catch (e: JSONException) { - // Finally not JSON... - Timber.e(e) - } + if (message.startsWith("{")) { + // JSON Detected + try { + val o = JSONObject(message) + logJson(o.toString(INDENT_SPACE)) + } catch (e: JSONException) { + // Finally this is not a JSON string... + Timber.e(e) + } + } else if (message.startsWith("[")) { + // JSON Array detected + try { + val o = JSONArray(message) + logJson(o.toString(INDENT_SPACE)) + } catch (e: JSONException) { + // Finally not JSON... + Timber.e(e) } - // Else not a json string to log } + // Else not a json string to log } private fun logJson(formattedJson: String) { From a0c8a8e97ce5e7739f9881b9e7bfadcd7663dc5b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:36:12 +0100 Subject: [PATCH 03/20] Log HTTP requests and responses in production (level BASIC, i.e. without any private data) --- CHANGES.md | 1 + gradle.properties | 2 +- matrix-sdk-android/build.gradle | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1005040328..12cfd8ef83 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,7 @@ Test: Other changes: - Remove "Status.im" theme #2424 + - Log HTTP requests and responses in production (level BASIC, i.e. without any private data) Changes in Element 1.0.11 (2020-11-27) =================================================== diff --git a/gradle.properties b/gradle.properties index b3f11e08a3..200866be25 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx2048m org.gradle.vfs.watch=true vector.debugPrivateData=false -vector.httpLogLevel=NONE +vector.httpLogLevel=BASIC # Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above #vector.debugPrivateData=true diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index d961560c17..7f0d5c1bbf 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -63,7 +63,7 @@ android { release { buildConfigField "boolean", "LOG_PRIVATE_DATA", "false" - buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE" + buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BASIC" } } From b43f3b3b6aec9413b0f0c165dad79d0ee330023a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:57:18 +0100 Subject: [PATCH 04/20] Log some details about the request which has failed --- .../org/matrix/android/sdk/internal/network/Request.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index e6cec7f7ac..2535a5347a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -16,14 +16,15 @@ package org.matrix.android.sdk.internal.network -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.shouldBeRetried -import org.matrix.android.sdk.internal.network.ssl.CertUtil import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.shouldBeRetried +import org.matrix.android.sdk.internal.network.ssl.CertUtil import retrofit2.Call import retrofit2.awaitResponse +import timber.log.Timber import java.io.IOException internal suspend inline fun executeRequest(eventBus: EventBus?, @@ -49,6 +50,9 @@ internal class Request(private val eventBus: EventBus?) { throw response.toFailure(eventBus) } } catch (exception: Throwable) { + // Log some details about the request which has failed + Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") + // Check if this is a certificateException CertUtil.getCertificateException(exception) // TODO Support certificate error once logged From dda2685bd8693d5317866c69d9c21e7c76c85b35 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 13:33:01 +0100 Subject: [PATCH 05/20] Upgrade Realm dependency to 10.1.2 --- CHANGES.md | 1 + matrix-sdk-android/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 12cfd8ef83..b0be0a1145 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ SDK API changes ⚠️: Build 🧱: - Upgrade some dependencies and Kotlin version - Use fragment-ktx and preference-ktx dependencies (fix lint issue KtxExtensionAvailable) + - Upgrade Realm dependency to 10.1.2 Test: - diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7f0d5c1bbf..519b8439c9 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.0.0" + classpath "io.realm:realm-gradle-plugin:10.1.2" } } From 7152dead1da6272836601e18986b941fb985cf02 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 16:47:29 +0100 Subject: [PATCH 06/20] Rename method --- .../org/matrix/android/sdk/internal/session/SessionModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 32949d60c4..f3a9fc59e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -169,8 +169,8 @@ internal abstract class SessionModule { @JvmStatic @Provides @SessionDownloadsDirectory - fun providesCacheDir(@SessionId sessionId: String, - context: Context): File { + fun providesDownloadsCacheDir(@SessionId sessionId: String, + context: Context): File { return File(context.cacheDir, "downloads/$sessionId") } From 24a9ddaa5e3aad9f42550948c87b1e78d85eee04 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 17:20:40 +0100 Subject: [PATCH 07/20] FileService: remove useless FileService.DownloadMode --- CHANGES.md | 2 +- .../sdk/api/session/file/FileService.kt | 18 --------------- .../sdk/internal/di/MatrixComponent.kt | 3 --- .../android/sdk/internal/di/MatrixModule.kt | 7 ------ .../internal/session/DefaultFileService.kt | 23 ++----------------- .../sdk/internal/session/SessionModule.kt | 5 ++-- .../app/core/glide/VectorGlideModelLoader.kt | 2 -- .../home/room/detail/RoomDetailFragment.kt | 3 --- .../home/room/detail/RoomDetailViewModel.kt | 2 -- .../features/media/BaseAttachmentProvider.kt | 1 - .../media/DataAttachmentRoomProvider.kt | 1 - .../media/RoomEventsAttachmentProvider.kt | 1 - .../features/media/VideoContentRenderer.kt | 3 --- .../uploads/RoomUploadsViewModel.kt | 3 --- 14 files changed, 6 insertions(+), 68 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b0be0a1145..7ba7b7cb76 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,7 +18,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - FileService: remove useless FileService.DownloadMode Build 🧱: - Upgrade some dependencies and Kotlin version diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index 31f016be14..dd592d84a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -27,23 +27,6 @@ import java.io.File */ interface FileService { - enum class DownloadMode { - /** - * Download file in external storage - */ - TO_EXPORT, - - /** - * Download file in cache - */ - FOR_INTERNAL_USE, - - /** - * Download file in file provider path - */ - FOR_EXTERNAL_SHARE - } - enum class FileState { IN_CACHE, DOWNLOADING, @@ -55,7 +38,6 @@ interface FileService { * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ fun downloadFile( - downloadMode: DownloadMode, id: String, fileName: String, mimeType: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index d3f08fde36..f959104e11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -71,9 +71,6 @@ internal interface MatrixComponent { @CacheDirectory fun cacheDir(): File - @ExternalFilesDirectory - fun externalFilesDir(): File? - fun olmManager(): OlmManager fun taskExecutor(): TaskExecutor diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index 71cbd8f1a1..b58fb3e683 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -57,13 +57,6 @@ internal object MatrixModule { return context.cacheDir } - @JvmStatic - @Provides - @ExternalFilesDirectory - fun providesExternalFilesDir(context: Context): File? { - return context.getExternalFilesDir(null) - } - @JvmStatic @Provides @MatrixScope diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 861ae7c7ee..d71c3262c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -29,8 +29,6 @@ import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments -import org.matrix.android.sdk.internal.di.CacheDirectory -import org.matrix.android.sdk.internal.di.ExternalFilesDirectory import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER @@ -54,10 +52,6 @@ import javax.inject.Inject internal class DefaultFileService @Inject constructor( private val context: Context, - @CacheDirectory - private val cacheDirectory: File, - @ExternalFilesDirectory - private val externalFilesDirectory: File?, @SessionDownloadsDirectory private val sessionCacheDirectory: File, private val contentUrlResolver: ContentUrlResolver, @@ -81,8 +75,7 @@ internal class DefaultFileService @Inject constructor( * Download file in the cache folder, and eventually decrypt it * TODO looks like files are copied 3 times */ - override fun downloadFile(downloadMode: FileService.DownloadMode, - id: String, + override fun downloadFile(id: String, fileName: String, mimeType: String?, url: String?, @@ -162,7 +155,7 @@ internal class DefaultFileService @Inject constructor( Timber.v("## FileService: cache hit for $url") } - Try.just(copyFile(destFile, downloadMode)) + Try.just(destFile) } }.fold({ callback.onFailure(it) @@ -232,18 +225,6 @@ internal class DefaultFileService @Inject constructor( return FileProvider.getUriForFile(context, authority, targetFile) } - private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File { - // TODO some of this seems outdated, will need to be re-worked - return when (downloadMode) { - FileService.DownloadMode.TO_EXPORT -> - file.copyTo(File(externalFilesDirectory, file.name), true) - FileService.DownloadMode.FOR_EXTERNAL_SHARE -> - file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true) - FileService.DownloadMode.FOR_INTERNAL_USE -> - file - } - } - override fun getCacheSize(): Int { return downloadFolder.walkTopDown() .onEnter { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f3a9fc59e3..96b44917bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory import org.matrix.android.sdk.internal.di.Authenticated +import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory @@ -170,8 +171,8 @@ internal abstract class SessionModule { @Provides @SessionDownloadsDirectory fun providesDownloadsCacheDir(@SessionId sessionId: String, - context: Context): File { - return File(context.cacheDir, "downloads/$sessionId") + @CacheDirectory cacheFile: File): File { + return File(cacheFile, "downloads/$sessionId") } @JvmStatic diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 71bd3ccc05..cf40926ba4 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -28,7 +28,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.media.ImageContentRenderer import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.file.FileService import timber.log.Timber import java.io.File import java.io.IOException @@ -110,7 +109,6 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde } // Use the file vector service, will avoid flickering and redownload after upload fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, mimeType = data.mimeType, id = data.eventId, url = data.url, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 3f5e476a5e..bbce180e80 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -174,7 +174,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Event 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.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -1657,7 +1656,6 @@ class RoomDetailFragment @Inject constructor( shareText(requireContext(), action.messageContent.body) } else if (action.messageContent is MessageWithAttachmentContent) { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.eventId, fileName = action.messageContent.body, mimeType = action.messageContent.mimeType, @@ -1692,7 +1690,6 @@ class RoomDetailFragment @Inject constructor( return } session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.eventId, fileName = action.messageContent.body, mimeType = action.messageContent.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a83dddc9ac..a13ee3be62 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -69,7 +69,6 @@ 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.toContent 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.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams @@ -1033,7 +1032,6 @@ class RoomDetailViewModel @AssistedInject constructor( } } else { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = action.eventId, fileName = action.messageFileContent.getFileName(), mimeType = action.messageFileContent.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index e23b905919..5f61ca36e4 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -153,7 +153,6 @@ abstract class BaseAttachmentProvider( } else { target.onVideoFileLoading(info.uid) fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, mimeType = data.mimeType, elementToDecrypt = data.elementToDecrypt, diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 18312b4aa0..6f58c1a4f3 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -77,7 +77,6 @@ class DataAttachmentRoomProvider( override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { val item = getItem(position) fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = item.eventId, fileName = item.filename, mimeType = item.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 1e2761dde0..9b895dbc4d 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -125,7 +125,6 @@ class RoomEventsAttachmentProvider( as? MessageWithAttachmentContent ?: return@let fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = timelineEvent.eventId, fileName = messageContent.body, mimeType = messageContent.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index f8cd09ce2f..35375bc8ce 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -27,7 +27,6 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.utils.isLocalFile import kotlinx.android.parcel.Parcelize import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import timber.log.Timber import java.io.File @@ -76,7 +75,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: activeSessionHolder.getActiveSession().fileService() .downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, fileName = data.filename, mimeType = data.mimeType, @@ -116,7 +114,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: activeSessionHolder.getActiveSession().fileService() .downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, fileName = data.filename, mimeType = data.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 763eed5474..95d7ce8e93 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -30,7 +30,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt @@ -134,7 +133,6 @@ class RoomUploadsViewModel @AssistedInject constructor( try { val file = awaitCallback { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.uploadEvent.eventId, fileName = action.uploadEvent.contentWithAttachmentContent.body, url = action.uploadEvent.contentWithAttachmentContent.getFileUrl(), @@ -155,7 +153,6 @@ class RoomUploadsViewModel @AssistedInject constructor( try { val file = awaitCallback { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.uploadEvent.eventId, fileName = action.uploadEvent.contentWithAttachmentContent.body, mimeType = action.uploadEvent.contentWithAttachmentContent.mimeType, From 8e11ba21edfe1283b6055ac36bbbdac352fa27f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 17:37:24 +0100 Subject: [PATCH 08/20] Glide: No Disk cache for encrypted images --- .../im/vector/app/features/media/ImageContentRenderer.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 187c2e85c3..cf214b391a 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -23,6 +23,7 @@ import android.view.View import android.widget.ImageView import androidx.core.view.updateLayoutParams import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestListener @@ -129,6 +130,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: GlideApp .with(contextView) .load(data) + .diskCacheStrategy(DiskCacheStrategy.NONE) } else { // Clear image val resolvedUrl = resolveUrl(data) @@ -183,6 +185,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: GlideApp .with(imageView) .load(data) + .diskCacheStrategy(DiskCacheStrategy.NONE) } else { // Clear image val resolvedUrl = resolveUrl(data) @@ -214,14 +217,16 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: .into(imageView) } - fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest { + private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest { return createGlideRequest(data, mode, GlideApp.with(imageView), size) } fun createGlideRequest(data: Data, mode: Mode, glideRequests: GlideRequests, size: Size = processSize(data, mode)): GlideRequest { return if (data.elementToDecrypt != null) { // Encrypted image - glideRequests.load(data) + glideRequests + .load(data) + .diskCacheStrategy(DiskCacheStrategy.NONE) } else { // Clear image val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() From 42ab7f1b4f8098cb18f24e41cbb9331ce70e6896 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 17:42:42 +0100 Subject: [PATCH 09/20] Add space between image and text And remove useless `apply` block --- .../timeline/factory/EncryptedItemFactory.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index f77e39c245..e88c1f3797 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -82,10 +82,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat when (cryptoError) { MXCryptoError.ErrorType.KEYS_WITHHELD -> { span { - apply { - drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let { - image(it, "baseline") - } + drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let { + image(it, "baseline") + +" " } span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_final)) { textStyle = "italic" @@ -95,10 +94,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat } else -> { span { - apply { - drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let { - image(it, "baseline") - } + drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let { + image(it, "baseline") + +" " } span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_friendly)) { textStyle = "italic" From 237cb63fc2b21674e6e09ff064e451d4007f8ab3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 18:04:42 +0100 Subject: [PATCH 10/20] Small formatting --- .../internal/session/DefaultFileService.kt | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index d71c3262c2..37b27cdbae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -21,6 +21,13 @@ import android.net.Uri import android.webkit.MimeTypeMap import androidx.core.content.FileProvider import arrow.core.Try +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.buffer +import okio.sink +import okio.source import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -36,13 +43,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient -import okhttp3.Request -import okio.buffer -import okio.sink -import okio.source import timber.log.Timber import java.io.File import java.io.IOException @@ -157,30 +157,33 @@ internal class DefaultFileService @Inject constructor( Try.just(destFile) } - }.fold({ - callback.onFailure(it) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) + }.fold( + { throwable -> + callback.onFailure(throwable) + // notify concurrent requests + val toNotify = synchronized(ongoing) { + ongoing[unwrappedUrl]?.also { + ongoing.remove(unwrappedUrl) + } + } + toNotify?.forEach { otherCallbacks -> + tryOrNull { otherCallbacks.onFailure(throwable) } + } + }, + { file -> + callback.onSuccess(file) + // notify concurrent requests + val toNotify = synchronized(ongoing) { + ongoing[unwrappedUrl]?.also { + ongoing.remove(unwrappedUrl) + } + } + Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") + toNotify?.forEach { otherCallbacks -> + tryOrNull { otherCallbacks.onSuccess(file) } + } } - } - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onFailure(it) } - } - }, { file -> - callback.onSuccess(file) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) - } - } - Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onSuccess(file) } - } - }) + ) }.toCancelable() } From 62791e4b36df0020af82fb2098f5bd12d5c3f5f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 18:35:17 +0100 Subject: [PATCH 11/20] Encrypted files: store decrypted file in a dedicated folder --- .../sdk/api/session/file/FileService.kt | 7 +- .../internal/session/DefaultFileService.kt | 65 +++++++++++++------ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index dd592d84a3..e13aed628c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -60,10 +60,15 @@ interface FileService { fun fileState(mxcUrl: String, mimeType: String?): FileState /** - * Clears all the files downloaded by the service + * Clears all the files downloaded by the service, including decrypted files */ fun clearCache() + /** + * Clears all the decrypted files by the service + */ + fun clearDecryptedCache() + /** * Get size of cached files */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 37b27cdbae..062d09e101 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -63,7 +63,15 @@ internal class DefaultFileService @Inject constructor( private fun String.safeFileName() = URLEncoder.encode(this, Charsets.US_ASCII.displayName()) - private val downloadFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded file (not decrypted) + private val legacyFolder = File(sessionCacheDirectory, "MF") + private val downloadFolder = File(sessionCacheDirectory, "F") + private val decryptedFolder = File(downloadFolder, "D") + + init { + // Clear the legacy downloaded files + legacyFolder.deleteRecursively() + } /** * Retain ongoing downloads to avoid re-downloading and already downloading file @@ -103,8 +111,8 @@ internal class DefaultFileService @Inject constructor( return taskExecutor.executorScope.launch(coroutineDispatchers.main) { withContext(coroutineDispatchers.io) { Try { - if (!downloadFolder.exists()) { - downloadFolder.mkdirs() + if (!decryptedFolder.exists()) { + decryptedFolder.mkdirs() } // ensure we use unique file name by using URL (mapped to suitable file name) // Also we need to add extension for the FileProvider, if not it lot's of app that it's @@ -134,29 +142,42 @@ internal class DefaultFileService @Inject constructor( Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") - if (elementToDecrypt != null) { - Timber.v("## FileService: decrypt file") - val decryptSuccess = destFile.outputStream().buffered().use { - MXEncryptedAttachments.decryptAttachment( - source.inputStream(), - elementToDecrypt, - it - ) - } - response.close() - if (!decryptSuccess) { - return@flatMap Try.Failure(IllegalStateException("Decryption error")) - } - } else { - writeToFile(source.inputStream(), destFile) - response.close() - } + // Write the file to cache (encrypted version if the file is encrypted) + writeToFile(source.inputStream(), destFile) + response.close() } else { Timber.v("## FileService: cache hit for $url") } Try.just(destFile) } + }.flatMap { downloadedFile -> + // Decrypt if necessary + if (elementToDecrypt != null) { + val decryptedFile = File(decryptedFolder, fileForUrl(unwrappedUrl, mimeType)) + + if (!decryptedFile.exists()) { + Timber.v("## FileService: decrypt file") + val decryptSuccess = decryptedFile.outputStream().buffered().use { outputStream -> + downloadedFile.inputStream().use { inputStream -> + MXEncryptedAttachments.decryptAttachment( + inputStream, + elementToDecrypt, + outputStream + ) + } + } + if (!decryptSuccess) { + return@flatMap Try.Failure(IllegalStateException("Decryption error")) + } + } else { + Timber.v("## FileService: cache hit for decrypted file") + } + Try.just(decryptedFile) + } else { + // Clear file + Try.just(downloadedFile) + } }.fold( { throwable -> callback.onFailure(throwable) @@ -240,4 +261,8 @@ internal class DefaultFileService @Inject constructor( override fun clearCache() { downloadFolder.deleteRecursively() } + + override fun clearDecryptedCache() { + decryptedFolder.deleteRecursively() + } } From 7057b2970b4a84acbcce1fc31f6b92bcba6f4f50 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 19:31:29 +0100 Subject: [PATCH 12/20] Improve FileService API: add facility methods to deal with MessageWithAttachment object --- .../sdk/api/session/file/FileService.kt | 50 +++++++++++++++++-- .../internal/session/DefaultFileService.kt | 24 ++++++--- .../home/room/detail/RoomDetailFragment.kt | 10 +--- .../home/room/detail/RoomDetailViewModel.kt | 13 ++--- .../timeline/factory/MessageItemFactory.kt | 4 +- .../uploads/RoomUploadsViewModel.kt | 10 +--- 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index e13aed628c..d3327ba920 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -18,8 +18,12 @@ package org.matrix.android.sdk.api.session.file import android.net.Uri import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent +import org.matrix.android.sdk.api.session.room.model.message.getFileName +import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt +import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File /** @@ -45,19 +49,59 @@ interface FileService { elementToDecrypt: ElementToDecrypt?, callback: MatrixCallback): Cancelable - fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean + fun downloadFile( + id: String, + messageContent: MessageWithAttachmentContent, + callback: MatrixCallback): Cancelable = + downloadFile( + id = id, + fileName = messageContent.getFileName(), + mimeType = messageContent.mimeType, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), + callback = callback + ) + + fun isFileInCache(mxcUrl: String?, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): Boolean + + fun isFileInCache(messageContent: MessageWithAttachmentContent) = + isFileInCache( + mxcUrl = messageContent.getFileUrl(), + mimeType = messageContent.mimeType, + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) /** * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it) */ - fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri? + fun getTemporarySharableURI(mxcUrl: String?, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): Uri? + + fun getTemporarySharableURI(messageContent: MessageWithAttachmentContent): Uri? = + getTemporarySharableURI( + mxcUrl = messageContent.getFileUrl(), + mimeType = messageContent.mimeType, + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() + ) /** * Get information on the given file. * Mimetype should be the same one as passed to downloadFile (limitation for now) */ - fun fileState(mxcUrl: String, mimeType: String?): FileState + fun fileState(mxcUrl: String?, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): FileState + + fun fileState(messageContent: MessageWithAttachmentContent): FileState = + fileState( + mxcUrl = messageContent.getFileUrl(), + mimeType = messageContent.mimeType, + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() + ) /** * Clears all the files downloaded by the service, including decrypted files diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 062d09e101..1e5dff107e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -225,12 +225,23 @@ internal class DefaultFileService @Inject constructor( return if (extension != null) "${url.safeFileName()}.$extension" else url.safeFileName() } - override fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean { - return File(downloadFolder, fileForUrl(mxcUrl, mimeType)).exists() + override fun isFileInCache(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Boolean { + return fileState(mxcUrl, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE } - override fun fileState(mxcUrl: String, mimeType: String?): FileService.FileState { - if (isFileInCache(mxcUrl, mimeType)) return FileService.FileState.IN_CACHE + private fun getClearFile(mxcUrl: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): File { + return if (elementToDecrypt == null) { + // Clear file + File(downloadFolder, fileForUrl(mxcUrl, mimeType)) + } else { + // Encrypted file + File(decryptedFolder, fileForUrl(mxcUrl, mimeType)) + } + } + + override fun fileState(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileService.FileState { + mxcUrl ?: return FileService.FileState.UNKNOWN + if (getClearFile(mxcUrl, mimeType, elementToDecrypt).exists()) return FileService.FileState.IN_CACHE val isDownloading = synchronized(ongoing) { ongoing[mxcUrl] != null } @@ -241,10 +252,11 @@ internal class DefaultFileService @Inject constructor( * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it) */ - override fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri? { + override fun getTemporarySharableURI(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Uri? { + mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" - val targetFile = File(downloadFolder, fileForUrl(mxcUrl, mimeType)) + val targetFile = getClearFile(mxcUrl, mimeType, elementToDecrypt) if (!targetFile.exists()) return null return FileProvider.getUriForFile(context, authority, targetFile) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index bbce180e80..a229f72755 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1657,10 +1657,7 @@ class RoomDetailFragment @Inject constructor( } else if (action.messageContent is MessageWithAttachmentContent) { session.fileService().downloadFile( id = action.eventId, - fileName = action.messageContent.body, - mimeType = action.messageContent.mimeType, - url = action.messageContent.getFileUrl(), - elementToDecrypt = action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.messageContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { if (isAdded) { @@ -1691,10 +1688,7 @@ class RoomDetailFragment @Inject constructor( } session.fileService().downloadFile( id = action.eventId, - fileName = action.messageContent.body, - mimeType = action.messageContent.mimeType, - url = action.messageContent.getFileUrl(), - elementToDecrypt = action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.messageContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { if (isAdded) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a13ee3be62..86f22a55ad 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1009,10 +1009,10 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { - val mxcUrl = action.messageFileContent.getFileUrl() + val mxcUrl = action.messageFileContent.getFileUrl() ?: return val isLocalSendingFile = action.senderId == session.myUserId - && mxcUrl?.startsWith("content://") ?: false - val isDownloaded = mxcUrl?.let { session.fileService().isFileInCache(it, action.messageFileContent.mimeType) } ?: false + && mxcUrl.startsWith("content://") + val isDownloaded = session.fileService().isFileInCache(action.messageFileContent) if (isLocalSendingFile) { tryOrNull { Uri.parse(mxcUrl) }?.let { _viewEvents.post(RoomDetailViewEvents.OpenFile( @@ -1023,7 +1023,7 @@ class RoomDetailViewModel @AssistedInject constructor( } } else if (isDownloaded) { // we can open it - session.fileService().getTemporarySharableURI(mxcUrl!!, action.messageFileContent.mimeType)?.let { uri -> + session.fileService().getTemporarySharableURI(action.messageFileContent)?.let { uri -> _viewEvents.post(RoomDetailViewEvents.OpenFile( action.messageFileContent.mimeType, uri, @@ -1033,10 +1033,7 @@ class RoomDetailViewModel @AssistedInject constructor( } else { session.fileService().downloadFile( id = action.eventId, - fileName = action.messageFileContent.getFileName(), - mimeType = action.messageFileContent.mimeType, - url = mxcUrl, - elementToDecrypt = action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.messageFileContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { _viewEvents.post(RoomDetailViewEvents.DownloadFileState( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 2b067ccf3f..213c50b6ac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -204,7 +204,7 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .izLocalFile(fileUrl.isLocalFile()) - .izDownloaded(session.fileService().isFileInCache(fileUrl, messageContent.mimeType)) + .izDownloaded(session.fileService().isFileInCache(fileUrl, messageContent.mimeType, messageContent.encryptedFileInfo?.toElementToDecrypt())) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) @@ -264,7 +264,7 @@ class MessageItemFactory @Inject constructor( .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) .izLocalFile(messageContent.getFileUrl().isLocalFile()) - .izDownloaded(session.fileService().isFileInCache(mxcUrl, messageContent.mimeType)) + .izDownloaded(session.fileService().isFileInCache(messageContent)) .mxcUrl(mxcUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 95d7ce8e93..bd37cecd56 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -134,10 +134,7 @@ class RoomUploadsViewModel @AssistedInject constructor( val file = awaitCallback { session.fileService().downloadFile( id = action.uploadEvent.eventId, - fileName = action.uploadEvent.contentWithAttachmentContent.body, - url = action.uploadEvent.contentWithAttachmentContent.getFileUrl(), - mimeType = action.uploadEvent.contentWithAttachmentContent.mimeType, - elementToDecrypt = action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.uploadEvent.contentWithAttachmentContent, callback = it ) } @@ -154,10 +151,7 @@ class RoomUploadsViewModel @AssistedInject constructor( val file = awaitCallback { session.fileService().downloadFile( id = action.uploadEvent.eventId, - fileName = action.uploadEvent.contentWithAttachmentContent.body, - mimeType = action.uploadEvent.contentWithAttachmentContent.mimeType, - url = action.uploadEvent.contentWithAttachmentContent.getFileUrl(), - elementToDecrypt = action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.uploadEvent.contentWithAttachmentContent, callback = it) } _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) From ca7796114cb56aac7f645c4c20e70082403b531a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 10:50:21 +0100 Subject: [PATCH 13/20] DefaultFileService: better management of the files and the filenames --- .../sdk/api/session/file/FileService.kt | 6 + .../internal/session/DefaultFileService.kt | 141 ++++++++++++------ .../session/content/UploadContentWorker.kt | 3 +- .../android/sdk/internal/util/FileSaver.kt | 3 + .../timeline/factory/MessageItemFactory.kt | 8 +- 5 files changed, 110 insertions(+), 51 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index d3327ba920..d0f53f25de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -63,6 +63,7 @@ interface FileService { ) fun isFileInCache(mxcUrl: String?, + fileName: String, mimeType: String?, elementToDecrypt: ElementToDecrypt? ): Boolean @@ -70,6 +71,7 @@ interface FileService { fun isFileInCache(messageContent: MessageWithAttachmentContent) = isFileInCache( mxcUrl = messageContent.getFileUrl(), + fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) @@ -78,12 +80,14 @@ interface FileService { * (if not other app won't be able to access it) */ fun getTemporarySharableURI(mxcUrl: String?, + fileName: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Uri? fun getTemporarySharableURI(messageContent: MessageWithAttachmentContent): Uri? = getTemporarySharableURI( mxcUrl = messageContent.getFileUrl(), + fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) @@ -93,12 +97,14 @@ interface FileService { * Mimetype should be the same one as passed to downloadFile (limitation for now) */ fun fileState(mxcUrl: String?, + fileName: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileState fun fileState(messageContent: MessageWithAttachmentContent): FileState = fileState( mxcUrl = messageContent.getFileUrl(), + fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 1e5dff107e..006ced8530 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -25,9 +25,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request -import okio.buffer -import okio.sink -import okio.source import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -41,13 +38,12 @@ import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProg import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber import java.io.File import java.io.IOException -import java.io.InputStream -import java.net.URLEncoder import javax.inject.Inject internal class DefaultFileService @Inject constructor( @@ -61,8 +57,6 @@ internal class DefaultFileService @Inject constructor( private val taskExecutor: TaskExecutor ) : FileService { - private fun String.safeFileName() = URLEncoder.encode(this, Charsets.US_ASCII.displayName()) - // Folder to store downloaded file (not decrypted) private val legacyFolder = File(sessionCacheDirectory, "MF") private val downloadFolder = File(sessionCacheDirectory, "F") @@ -89,21 +83,21 @@ internal class DefaultFileService @Inject constructor( url: String?, elementToDecrypt: ElementToDecrypt?, callback: MatrixCallback): Cancelable { - val unwrappedUrl = url ?: return NoOpCancellable.also { + url ?: return NoOpCancellable.also { callback.onFailure(IllegalArgumentException("url is null")) } - Timber.v("## FileService downloadFile $unwrappedUrl") + Timber.v("## FileService downloadFile $url") synchronized(ongoing) { - val existing = ongoing[unwrappedUrl] + val existing = ongoing[url] if (existing != null) { Timber.v("## FileService downloadFile is already downloading.. ") existing.add(callback) return NoOpCancellable } else { // mark as tracked - ongoing[unwrappedUrl] = ArrayList() + ongoing[url] = ArrayList() // and proceed to download } } @@ -117,9 +111,9 @@ internal class DefaultFileService @Inject constructor( // ensure we use unique file name by using URL (mapped to suitable file name) // Also we need to add extension for the FileProvider, if not it lot's of app that it's // shared with will not function well (even if mime type is passed in the intent) - File(downloadFolder, fileForUrl(unwrappedUrl, mimeType)) - }.flatMap { destFile -> - if (!destFile.exists()) { + getFiles(url, fileName, mimeType, elementToDecrypt) + }.flatMap { cachedFiles -> + if (!cachedFiles.file.exists()) { val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) val request = Request.Builder() @@ -143,23 +137,23 @@ 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(), destFile) + writeToFile(source.inputStream(), cachedFiles.file) response.close() } else { Timber.v("## FileService: cache hit for $url") } - Try.just(destFile) + Try.just(cachedFiles) } - }.flatMap { downloadedFile -> + }.flatMap { cachedFiles -> // Decrypt if necessary - if (elementToDecrypt != null) { - val decryptedFile = File(decryptedFolder, fileForUrl(unwrappedUrl, mimeType)) - - if (!decryptedFile.exists()) { + if (cachedFiles.decryptedFile != null) { + if (!cachedFiles.decryptedFile.exists()) { Timber.v("## FileService: decrypt file") - val decryptSuccess = decryptedFile.outputStream().buffered().use { outputStream -> - downloadedFile.inputStream().use { inputStream -> + // Ensure the parent folder exists + cachedFiles.decryptedFile.parentFile?.mkdirs() + val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> + cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> MXEncryptedAttachments.decryptAttachment( inputStream, elementToDecrypt, @@ -173,18 +167,18 @@ internal class DefaultFileService @Inject constructor( } else { Timber.v("## FileService: cache hit for decrypted file") } - Try.just(decryptedFile) + Try.just(cachedFiles.decryptedFile) } else { // Clear file - Try.just(downloadedFile) + Try.just(cachedFiles.file) } }.fold( { throwable -> callback.onFailure(throwable) // notify concurrent requests val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) + ongoing[url]?.also { + ongoing.remove(url) } } toNotify?.forEach { otherCallbacks -> @@ -195,8 +189,8 @@ internal class DefaultFileService @Inject constructor( callback.onSuccess(file) // notify concurrent requests val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) + ongoing[url]?.also { + ongoing.remove(url) } } Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") @@ -208,6 +202,7 @@ internal class DefaultFileService @Inject constructor( }.toCancelable() } + /* fun storeDataFor(url: String, mimeType: String?, inputStream: InputStream) { val file = File(downloadFolder, fileForUrl(url, mimeType)) val source = inputStream.source().buffer() @@ -219,29 +214,70 @@ internal class DefaultFileService @Inject constructor( } } } + */ - private fun fileForUrl(url: String, mimeType: String?): String { - val extension = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) } - return if (extension != null) "${url.safeFileName()}.$extension" else url.safeFileName() - } - - override fun isFileInCache(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Boolean { - return fileState(mxcUrl, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE - } - - private fun getClearFile(mxcUrl: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): File { - return if (elementToDecrypt == null) { - // Clear file - File(downloadFolder, fileForUrl(mxcUrl, mimeType)) - } else { - // Encrypted file - File(decryptedFolder, fileForUrl(mxcUrl, mimeType)) + private fun safeFileName(fileName: String, mimeType: String?): String { + return buildString { + // filename has to be safe for the Android System + val result = fileName.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_") + append(result) + // Check that the extension is correct regarding the mimeType + val extensionFromMime = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) } + if (extensionFromMime != null) { + // Compare + val fileExtension = result.substringAfterLast(delimiter = ".", missingDelimiterValue = "") + if (fileExtension.isEmpty() || fileExtension != extensionFromMime) { + // Missing extension, or diff in extension, add the one provided by the mimetype + append(".") + append(extensionFromMime) + } + } } } - override fun fileState(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileService.FileState { + override fun isFileInCache(mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): Boolean { + return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE + } + + internal data class CachedFiles( + // This is the downloaded file. Can be clear or encrypted + val file: File, + // This is the decrypted file. Null if the original file is not encrypted + val decryptedFile: File? + ) { + fun getClearFile(): File = decryptedFile ?: file + } + + private fun getFiles(mxcUrl: String, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): CachedFiles { + val hashFolder = mxcUrl.md5() + val safeFileName = safeFileName(fileName, mimeType) + return if (elementToDecrypt == null) { + // Clear file + CachedFiles( + File(downloadFolder, "$hashFolder/$safeFileName"), + null + ) + } else { + // Encrypted file + CachedFiles( + File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"), + File(decryptedFolder, "$hashFolder/$safeFileName"), + ) + } + } + + override fun fileState(mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): FileService.FileState { mxcUrl ?: return FileService.FileState.UNKNOWN - if (getClearFile(mxcUrl, mimeType, elementToDecrypt).exists()) return FileService.FileState.IN_CACHE + if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).file.exists()) return FileService.FileState.IN_CACHE val isDownloading = synchronized(ongoing) { ongoing[mxcUrl] != null } @@ -252,11 +288,14 @@ internal class DefaultFileService @Inject constructor( * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it) */ - override fun getTemporarySharableURI(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Uri? { + override fun getTemporarySharableURI(mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): Uri? { mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" - val targetFile = getClearFile(mxcUrl, mimeType, elementToDecrypt) + val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).getClearFile() if (!targetFile.exists()) return null return FileProvider.getUriForFile(context, authority, targetFile) } @@ -277,4 +316,8 @@ internal class DefaultFileService @Inject constructor( override fun clearDecryptedCache() { decryptedFolder.deleteRecursively() } + + companion object { + private const val ENCRYPTED_FILENAME = "encrypted.bin" + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 4a30d6c1e6..8df5082c33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -199,9 +199,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}") try { + /* TODO context.contentResolver.openInputStream(attachment.queryUri)?.let { fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it) - } + } */ Timber.v("## FileService: cache storage updated") } catch (failure: Throwable) { Timber.e(failure, "## FileService: Failed to update file cache") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt index 4dc54d3b19..fb5e3a5774 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt @@ -25,6 +25,9 @@ import java.io.InputStream */ @WorkerThread fun writeToFile(inputStream: InputStream, outputFile: File) { + // Ensure the parent folder exists, else it will crash + outputFile.parentFile?.mkdirs() + outputFile.outputStream().use { inputStream.copyTo(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 213c50b6ac..34086043da 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -84,6 +84,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_POLL +import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent @@ -204,7 +205,12 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .izLocalFile(fileUrl.isLocalFile()) - .izDownloaded(session.fileService().isFileInCache(fileUrl, messageContent.mimeType, messageContent.encryptedFileInfo?.toElementToDecrypt())) + .izDownloaded(session.fileService().isFileInCache( + fileUrl, + messageContent.getFileName(), + messageContent.mimeType, + messageContent.encryptedFileInfo?.toElementToDecrypt()) + ) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) From 1c43f92e49aae4d21cc28b2b193ccb3f2d1d973c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:20:48 +0100 Subject: [PATCH 14/20] DefaultFileService: store just sent file --- .../internal/session/DefaultFileService.kt | 57 +++++++++++-------- .../session/content/UploadContentWorker.kt | 19 ++++--- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 006ced8530..54ff90631b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -111,7 +111,7 @@ internal class DefaultFileService @Inject constructor( // ensure we use unique file name by using URL (mapped to suitable file name) // Also we need to add extension for the FileProvider, if not it lot's of app that it's // shared with will not function well (even if mime type is passed in the intent) - getFiles(url, fileName, mimeType, elementToDecrypt) + getFiles(url, fileName, mimeType, elementToDecrypt != null) }.flatMap { cachedFiles -> if (!cachedFiles.file.exists()) { val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) @@ -202,24 +202,29 @@ internal class DefaultFileService @Inject constructor( }.toCancelable() } - /* - fun storeDataFor(url: String, mimeType: String?, inputStream: InputStream) { - val file = File(downloadFolder, fileForUrl(url, mimeType)) - val source = inputStream.source().buffer() - file.sink().buffer().let { sink -> - source.use { input -> - sink.use { output -> - output.writeAll(input) - } - } + fun storeDataFor(mxcUrl: String, + filename: String?, + mimeType: String?, + originalFile: File, + encryptedFile: File?) { + val files = getFiles(mxcUrl, filename, mimeType, encryptedFile != null) + if (encryptedFile != null) { + // We switch the two files here, original file it the decrypted file + files.decryptedFile?.let { originalFile.copyTo(it) } + encryptedFile.copyTo(files.file) + } else { + // Just copy the original file + originalFile.copyTo(files.file) } } - */ - private fun safeFileName(fileName: String, mimeType: String?): String { + private fun safeFileName(fileName: String?, mimeType: String?): String { return buildString { // filename has to be safe for the Android System - val result = fileName.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_") + val result = fileName + ?.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_") + ?.takeIf { it.isNotEmpty() } + ?: DEFAULT_FILENAME append(result) // Check that the extension is correct regarding the mimeType val extensionFromMime = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) } @@ -252,23 +257,23 @@ internal class DefaultFileService @Inject constructor( } private fun getFiles(mxcUrl: String, - fileName: String, + fileName: String?, mimeType: String?, - elementToDecrypt: ElementToDecrypt?): CachedFiles { + isEncrypted: Boolean): CachedFiles { val hashFolder = mxcUrl.md5() val safeFileName = safeFileName(fileName, mimeType) - return if (elementToDecrypt == null) { - // Clear file - CachedFiles( - File(downloadFolder, "$hashFolder/$safeFileName"), - null - ) - } else { + return if (isEncrypted) { // Encrypted file CachedFiles( File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"), File(decryptedFolder, "$hashFolder/$safeFileName"), ) + } else { + // Clear file + CachedFiles( + File(downloadFolder, "$hashFolder/$safeFileName"), + null + ) } } @@ -277,7 +282,7 @@ internal class DefaultFileService @Inject constructor( mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileService.FileState { mxcUrl ?: return FileService.FileState.UNKNOWN - if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).file.exists()) return FileService.FileState.IN_CACHE + if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE val isDownloading = synchronized(ongoing) { ongoing[mxcUrl] != null } @@ -295,7 +300,7 @@ internal class DefaultFileService @Inject constructor( mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" - val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).getClearFile() + val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).getClearFile() if (!targetFile.exists()) return null return FileProvider.getUriForFile(context, authority, targetFile) } @@ -319,5 +324,7 @@ internal class DefaultFileService @Inject constructor( companion object { private const val ENCRYPTED_FILENAME = "encrypted.bin" + // The extension would be added from the mimetype + private const val DEFAULT_FILENAME = "file" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 8df5082c33..77f39a7768 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -174,14 +174,15 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } } + val encryptedFile: File? val contentUploadResponse = if (params.isEncrypted) { Timber.v("## FileService: Encrypt file") - val tmpEncrypted = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir) + encryptedFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir) .also { filesToDelete.add(it) } uploadedFileEncryptedFileInfo = - MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), tmpEncrypted) { read, total -> + MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total -> notifyTracker(params) { contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong()) } @@ -190,19 +191,23 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter Timber.v("## FileService: Uploading file") fileUploader - .uploadFile(tmpEncrypted, attachment.name, "application/octet-stream", progressListener) + .uploadFile(encryptedFile, attachment.name, "application/octet-stream", progressListener) } else { Timber.v("## FileService: Clear file") + encryptedFile = null fileUploader .uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener) } Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}") try { - /* TODO - context.contentResolver.openInputStream(attachment.queryUri)?.let { - fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it) - } */ + fileService.storeDataFor( + mxcUrl = contentUploadResponse.contentUri, + filename = params.attachment.name, + mimeType = params.attachment.getSafeMimeType(), + originalFile = workingFile, + encryptedFile = encryptedFile + ) Timber.v("## FileService: cache storage updated") } catch (failure: Throwable) { Timber.e(failure, "## FileService: Failed to update file cache") From 283e10dfefd3a3ead990b1a649bc525d1df3127e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:26:49 +0100 Subject: [PATCH 15/20] Use filename if available --- .../sdk/internal/session/room/send/DefaultSendService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 5a71ff7b76..8828f3dfed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -177,7 +177,7 @@ internal class DefaultSendService @AssistedInject constructor( val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, mimeType = messageContent.info.mimeType!!, - name = messageContent.body, + name = messageContent.getFileName(), queryUri = Uri.parse(messageContent.url), type = ContentAttachmentData.Type.FILE ) From e4968c4119014a1bc6ff796b6b8ccf5a720aa7a9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:27:03 +0100 Subject: [PATCH 16/20] Doc and internal --- .../sdk/internal/session/content/ContentUploadResponse.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt index b5de26b39d..1ebe5b2eb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt @@ -20,6 +20,9 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class ContentUploadResponse( +internal data class ContentUploadResponse( + /** + * Required. The MXC URI to the uploaded content. + */ @Json(name = "content_uri") val contentUri: String ) From 0956baecf958bc6db35a26b3af74da8eb4ed8214 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:27:37 +0100 Subject: [PATCH 17/20] Delete unencrypted files each time the app is started --- .../im/vector/app/features/home/HomeActivityViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 680ec17415..90d128320b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -71,12 +71,18 @@ class HomeActivityViewModel @AssistedInject constructor( private var onceTrusted = false init { + cleanupFiles() observeInitialSync() mayBeInitializeCrossSigning() checkSessionPushIsOn() observeCrossSigningReset() } + private fun cleanupFiles() { + // Mitigation: delete all cached decrypted files each time the application is started. + activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache() + } + private fun observeCrossSigningReset() { val safeActiveSession = activeSessionHolder.getSafeActiveSession() ?: return From 4bd538e448a62d2775ef34a6236e988a3c5983ab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:49:25 +0100 Subject: [PATCH 18/20] Changelog and update comment --- CHANGES.md | 1 + .../matrix/android/sdk/internal/session/DefaultFileService.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7ba7b7cb76..7904834e62 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Changes in Element 1.0.12 (2020-XX-XX) Features ✨: - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) - Room setting: update join rules and guest access (#2442) + - Store encrypted file in cache and cleanup decrypted file at each app start Improvements 🙌: - Add Setting Item to Change PIN (#2462) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 54ff90631b..730c0dd82f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -57,9 +57,11 @@ internal class DefaultFileService @Inject constructor( private val taskExecutor: TaskExecutor ) : FileService { - // Folder to store downloaded file (not decrypted) + // Legacy folder, will be deleted private val legacyFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded files (not decrypted) private val downloadFolder = File(sessionCacheDirectory, "F") + // Folder to store decrypted files private val decryptedFolder = File(downloadFolder, "D") init { From 75071cf1d9ab15ee715df1697e1faedc1cc41e81 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 13:50:14 +0100 Subject: [PATCH 19/20] Cleanup --- .../internal/network/interceptors/FormattedJsonHttpLogger.kt | 1 - .../matrix/android/sdk/internal/session/DefaultFileService.kt | 2 +- .../vector/app/features/home/room/detail/RoomDetailFragment.kt | 2 -- .../vector/app/features/home/room/detail/RoomDetailViewModel.kt | 2 -- .../app/features/roomprofile/uploads/RoomUploadsViewModel.kt | 2 -- 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 849a464867..34ed28d467 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.network.interceptors import androidx.annotation.NonNull -import org.matrix.android.sdk.BuildConfig import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONArray import org.json.JSONException diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 730c0dd82f..ee4f5da41e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -268,7 +268,7 @@ internal class DefaultFileService @Inject constructor( // Encrypted file CachedFiles( File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"), - File(decryptedFolder, "$hashFolder/$safeFileName"), + File(decryptedFolder, "$hashFolder/$safeFileName") ) } else { // Clear file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index a229f72755..f8168140a3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -184,7 +184,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -193,7 +192,6 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 86f22a55ad..182ee6016d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -79,7 +79,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.OptionItem -import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -91,7 +90,6 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.util.awaitCallback diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index bd37cecd56..bf2b56fc9b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -31,8 +31,6 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap From 5e2f091ec105e0018e97f086b6d124eaba8ec3b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Dec 2020 13:36:00 +0100 Subject: [PATCH 20/20] Remove useless parameter `id` --- .../sdk/api/session/file/FileService.kt | 19 +++++++------------ .../internal/session/DefaultFileService.kt | 3 +-- .../app/core/glide/VectorGlideModelLoader.kt | 5 ++--- .../home/room/detail/RoomDetailFragment.kt | 2 -- .../home/room/detail/RoomDetailViewModel.kt | 1 - .../features/media/BaseAttachmentProvider.kt | 5 ++--- .../media/DataAttachmentRoomProvider.kt | 3 +-- .../media/RoomEventsAttachmentProvider.kt | 1 - .../features/media/VideoContentRenderer.kt | 2 -- .../uploads/RoomUploadsViewModel.kt | 2 -- 10 files changed, 13 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index d0f53f25de..bcdb5ea257 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -41,20 +41,15 @@ interface FileService { * Download a file. * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ - fun downloadFile( - id: String, - fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback): Cancelable + fun downloadFile(fileName: String, + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt?, + callback: MatrixCallback): Cancelable - fun downloadFile( - id: String, - messageContent: MessageWithAttachmentContent, - callback: MatrixCallback): Cancelable = + fun downloadFile(messageContent: MessageWithAttachmentContent, + callback: MatrixCallback): Cancelable = downloadFile( - id = id, fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, url = messageContent.getFileUrl(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index ee4f5da41e..07cde3da60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -79,8 +79,7 @@ internal class DefaultFileService @Inject constructor( * Download file in the cache folder, and eventually decrypt it * TODO looks like files are copied 3 times */ - override fun downloadFile(id: String, - fileName: String, + override fun downloadFile(fileName: String, mimeType: String?, url: String?, elementToDecrypt: ElementToDecrypt?, diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index cf40926ba4..9a7cf1eb76 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -109,10 +109,9 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde } // Use the file vector service, will avoid flickering and redownload after upload fileService.downloadFile( - mimeType = data.mimeType, - id = data.eventId, - url = data.url, fileName = data.filename, + mimeType = data.mimeType, + url = data.url, elementToDecrypt = data.elementToDecrypt, callback = object : MatrixCallback { override fun onSuccess(data: File) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f8168140a3..f1ae79a0aa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1654,7 +1654,6 @@ class RoomDetailFragment @Inject constructor( shareText(requireContext(), action.messageContent.body) } else if (action.messageContent is MessageWithAttachmentContent) { session.fileService().downloadFile( - id = action.eventId, messageContent = action.messageContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { @@ -1685,7 +1684,6 @@ class RoomDetailFragment @Inject constructor( return } session.fileService().downloadFile( - id = action.eventId, messageContent = action.messageContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 182ee6016d..7bba9728ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1030,7 +1030,6 @@ class RoomDetailViewModel @AssistedInject constructor( } } else { session.fileService().downloadFile( - id = action.eventId, messageContent = action.messageFileContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index 5f61ca36e4..90b17f80d7 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -153,11 +153,10 @@ abstract class BaseAttachmentProvider( } else { target.onVideoFileLoading(info.uid) fileService.downloadFile( - id = data.eventId, - mimeType = data.mimeType, - elementToDecrypt = data.elementToDecrypt, fileName = data.filename, + mimeType = data.mimeType, url = data.url, + elementToDecrypt = data.elementToDecrypt, callback = object : MatrixCallback { override fun onSuccess(data: File) { target.onVideoFileReady(info.uid, data) diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 6f58c1a4f3..584b13f32b 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -77,10 +77,9 @@ class DataAttachmentRoomProvider( override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { val item = getItem(position) fileService.downloadFile( - id = item.eventId, fileName = item.filename, mimeType = item.mimeType, - url = item.url ?: "", + url = item.url, elementToDecrypt = item.elementToDecrypt, callback = object : MatrixCallback { override fun onSuccess(data: File) { diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 9b895dbc4d..569d006fba 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -125,7 +125,6 @@ class RoomEventsAttachmentProvider( as? MessageWithAttachmentContent ?: return@let fileService.downloadFile( - id = timelineEvent.eventId, fileName = messageContent.body, mimeType = messageContent.mimeType, url = messageContent.getFileUrl(), diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index 35375bc8ce..d8eddc7331 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -75,7 +75,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: activeSessionHolder.getActiveSession().fileService() .downloadFile( - id = data.eventId, fileName = data.filename, mimeType = data.mimeType, url = data.url, @@ -114,7 +113,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: activeSessionHolder.getActiveSession().fileService() .downloadFile( - id = data.eventId, fileName = data.filename, mimeType = data.mimeType, url = data.url, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index bf2b56fc9b..b62b633a36 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -131,7 +131,6 @@ class RoomUploadsViewModel @AssistedInject constructor( try { val file = awaitCallback { session.fileService().downloadFile( - id = action.uploadEvent.eventId, messageContent = action.uploadEvent.contentWithAttachmentContent, callback = it ) @@ -148,7 +147,6 @@ class RoomUploadsViewModel @AssistedInject constructor( try { val file = awaitCallback { session.fileService().downloadFile( - id = action.uploadEvent.eventId, messageContent = action.uploadEvent.contentWithAttachmentContent, callback = it) }