From 114aa9f785a993f03ea7e40e7c06eefca5fda68f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 26 Sep 2022 22:59:35 +0100 Subject: [PATCH] persisting the decrypted images to the disk and enabling in memory image cache --- .../messenger/src/main/AndroidManifest.xml | 6 +- .../dapk/st/messenger/DecryptingFetcher.kt | 59 ++++++++++++++----- .../dapk/st/messenger/MessengerActivity.kt | 2 +- .../app/dapk/st/messenger/MessengerModule.kt | 3 +- .../app/dapk/st/messenger/MessengerScreen.kt | 5 +- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/features/messenger/src/main/AndroidManifest.xml b/features/messenger/src/main/AndroidManifest.xml index e643a26..41b5625 100644 --- a/features/messenger/src/main/AndroidManifest.xml +++ b/features/messenger/src/main/AndroidManifest.xml @@ -1,14 +1,14 @@ + package="app.dapk.st.messenger"> + android:windowSoftInputMode="adjustResize"/> - + diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/DecryptingFetcher.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/DecryptingFetcher.kt index d54dc06..a345ffc 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/DecryptingFetcher.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/DecryptingFetcher.kt @@ -1,7 +1,9 @@ package app.dapk.st.messenger import android.content.Context +import android.os.Environment import app.dapk.st.core.Base64 +import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.crypto.MediaDecrypter import app.dapk.st.matrix.sync.RoomEvent import coil.ImageLoader @@ -14,14 +16,16 @@ import coil.request.Options import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okio.Buffer +import okio.BufferedSource +import okio.Path.Companion.toOkioPath +import java.io.File -class DecryptingFetcherFactory(private val context: Context, base64: Base64) : Fetcher.Factory { +class DecryptingFetcherFactory(private val context: Context, base64: Base64, private val roomId: RoomId) : Fetcher.Factory { private val mediaDecrypter = MediaDecrypter(base64) override fun create(data: RoomEvent.Image, options: Options, imageLoader: ImageLoader): Fetcher { - return DecryptingFetcher(data, context, mediaDecrypter) + return DecryptingFetcher(data, context, mediaDecrypter, roomId) } } @@ -31,23 +35,48 @@ class DecryptingFetcher( private val data: RoomEvent.Image, private val context: Context, private val mediaDecrypter: MediaDecrypter, + roomId: RoomId, ) : Fetcher { - override suspend fun fetch(): FetchResult { - val response = http.newCall(Request.Builder().url(data.imageMeta.url).build()).execute() - val outputStream = when { - data.imageMeta.keys != null -> handleEncrypted(response, data.imageMeta.keys!!) - else -> response.body?.source() ?: throw IllegalArgumentException("No bitmap response found") - } - return SourceResult(ImageSource(outputStream, context), null, DataSource.NETWORK) + private val directory by lazy { + context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.resolve("SmallTalk/${roomId.value}").also { it.mkdirs() } } - private fun handleEncrypted(response: Response, keys: RoomEvent.Image.ImageMeta.Keys): Buffer { - return response.body?.byteStream()?.let { byteStream -> - Buffer().also { buffer -> - mediaDecrypter.decrypt(byteStream, keys.k, keys.iv).collect { buffer.write(it) } + override suspend fun fetch(): FetchResult { + val diskCacheKey = data.imageMeta.url.hashCode().toString() + val diskCachedFile = directory.resolve(diskCacheKey) + val path = diskCachedFile.toOkioPath() + + return when { + diskCachedFile.exists() -> SourceResult(ImageSource(path), null, DataSource.DISK) + + else -> { + diskCachedFile.createNewFile() + val response = http.newCall(Request.Builder().url(data.imageMeta.url).build()).execute() + when { + data.imageMeta.keys != null -> response.writeDecrypted(diskCachedFile, data.imageMeta.keys!!) + else -> response.body?.source()?.writeToFile(diskCachedFile) ?: throw IllegalArgumentException("No bitmap response found") + } + + SourceResult(ImageSource(path), null, DataSource.NETWORK) } - } ?: Buffer() + } + } + + private fun Response.writeDecrypted(file: File, keys: RoomEvent.Image.ImageMeta.Keys) { + this.body?.byteStream()?.let { byteStream -> + file.outputStream().use { output -> + mediaDecrypter.decrypt(byteStream, keys.k, keys.iv).collect { output.write(it) } + } + } + } +} + +private fun BufferedSource.writeToFile(file: File) { + this.inputStream().use { input -> + file.outputStream().use { output -> + input.copyTo(output) + } } } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerActivity.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerActivity.kt index 01cb2e6..a648dee 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerActivity.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerActivity.kt @@ -50,7 +50,7 @@ class MessengerActivity : DapkActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val payload = readPayload() - val factory = module.decryptingFetcherFactory() + val factory = module.decryptingFetcherFactory(RoomId(payload.roomId)) setContent { Surface(Modifier.fillMaxSize()) { CompositionLocalProvider(LocalDecyptingFetcherFactory provides factory) { diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt index f34013f..90446f9 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt @@ -4,6 +4,7 @@ import android.content.Context import app.dapk.st.core.Base64 import app.dapk.st.core.ProvidableModule import app.dapk.st.matrix.common.CredentialsStore +import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.message.MessageService import app.dapk.st.matrix.room.RoomService import app.dapk.st.matrix.sync.RoomStore @@ -30,5 +31,5 @@ class MessengerModule( return TimelineUseCaseImpl(syncService, messageService, roomService, mergeWithLocalEchosUseCase) } - internal fun decryptingFetcherFactory() = DecryptingFetcherFactory(context, base64) + internal fun decryptingFetcherFactory(roomId: RoomId) = DecryptingFetcherFactory(context, base64, roomId) } \ No newline at end of file diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt index 1730b34..a1e98f8 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt @@ -258,6 +258,7 @@ private fun MessageImage(content: BubbleContent) { painter = rememberAsyncImagePainter( model = ImageRequest.Builder(context) .fetcherFactory(LocalDecyptingFetcherFactory.current) + .memoryCacheKey(content.message.imageMeta.url) .data(content.message) .build() ), @@ -437,6 +438,7 @@ private fun ReplyBubbleContent(content: BubbleContent) { painter = rememberAsyncImagePainter( model = ImageRequest.Builder(context) .fetcherFactory(LocalDecyptingFetcherFactory.current) + .memoryCacheKey(replyingTo.imageMeta.url) .data(replyingTo) .build() ), @@ -478,7 +480,8 @@ private fun ReplyBubbleContent(content: BubbleContent) { modifier = Modifier.size(message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)), painter = rememberAsyncImagePainter( model = ImageRequest.Builder(context) - .data(content.message) + .data(message) + .memoryCacheKey(message.imageMeta.url) .fetcherFactory(LocalDecyptingFetcherFactory.current) .build() ),