persisting the decrypted images to the disk and enabling in memory image cache

This commit is contained in:
Adam Brown 2022-09-26 22:59:35 +01:00
parent 0a3f1f641a
commit 114aa9f785
5 changed files with 54 additions and 21 deletions

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.dapk.st.messenger">
package="app.dapk.st.messenger">
<application>
<activity
android:name=".MessengerActivity"
android:windowSoftInputMode="adjustResize" />
android:windowSoftInputMode="adjustResize"/>
<activity android:name=".roomsettings.RoomSettingsActivity" />
<activity android:name=".roomsettings.RoomSettingsActivity"/>
</application>

View File

@ -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<RoomEvent.Image> {
class DecryptingFetcherFactory(private val context: Context, base64: Base64, private val roomId: RoomId) : Fetcher.Factory<RoomEvent.Image> {
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)
}
}
}

View File

@ -50,7 +50,7 @@ class MessengerActivity : DapkActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val payload = readPayload<MessagerActivityPayload>()
val factory = module.decryptingFetcherFactory()
val factory = module.decryptingFetcherFactory(RoomId(payload.roomId))
setContent {
Surface(Modifier.fillMaxSize()) {
CompositionLocalProvider(LocalDecyptingFetcherFactory provides factory) {

View File

@ -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)
}

View File

@ -258,6 +258,7 @@ private fun MessageImage(content: BubbleContent<RoomEvent.Image>) {
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<RoomEvent.Reply>) {
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<RoomEvent.Reply>) {
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()
),