persisting the decrypted images to the disk and enabling in memory image cache
This commit is contained in:
parent
0a3f1f641a
commit
114aa9f785
|
@ -1,14 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="app.dapk.st.messenger">
|
package="app.dapk.st.messenger">
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MessengerActivity"
|
android:name=".MessengerActivity"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize"/>
|
||||||
|
|
||||||
<activity android:name=".roomsettings.RoomSettingsActivity" />
|
<activity android:name=".roomsettings.RoomSettingsActivity"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package app.dapk.st.messenger
|
package app.dapk.st.messenger
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
import app.dapk.st.core.Base64
|
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.crypto.MediaDecrypter
|
||||||
import app.dapk.st.matrix.sync.RoomEvent
|
import app.dapk.st.matrix.sync.RoomEvent
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
@ -14,14 +16,16 @@ import coil.request.Options
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
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)
|
private val mediaDecrypter = MediaDecrypter(base64)
|
||||||
|
|
||||||
override fun create(data: RoomEvent.Image, options: Options, imageLoader: ImageLoader): Fetcher {
|
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 data: RoomEvent.Image,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val mediaDecrypter: MediaDecrypter,
|
private val mediaDecrypter: MediaDecrypter,
|
||||||
|
roomId: RoomId,
|
||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
|
|
||||||
override suspend fun fetch(): FetchResult {
|
private val directory by lazy {
|
||||||
val response = http.newCall(Request.Builder().url(data.imageMeta.url).build()).execute()
|
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.resolve("SmallTalk/${roomId.value}").also { it.mkdirs() }
|
||||||
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 fun handleEncrypted(response: Response, keys: RoomEvent.Image.ImageMeta.Keys): Buffer {
|
override suspend fun fetch(): FetchResult {
|
||||||
return response.body?.byteStream()?.let { byteStream ->
|
val diskCacheKey = data.imageMeta.url.hashCode().toString()
|
||||||
Buffer().also { buffer ->
|
val diskCachedFile = directory.resolve(diskCacheKey)
|
||||||
mediaDecrypter.decrypt(byteStream, keys.k, keys.iv).collect { buffer.write(it) }
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class MessengerActivity : DapkActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val payload = readPayload<MessagerActivityPayload>()
|
val payload = readPayload<MessagerActivityPayload>()
|
||||||
val factory = module.decryptingFetcherFactory()
|
val factory = module.decryptingFetcherFactory(RoomId(payload.roomId))
|
||||||
setContent {
|
setContent {
|
||||||
Surface(Modifier.fillMaxSize()) {
|
Surface(Modifier.fillMaxSize()) {
|
||||||
CompositionLocalProvider(LocalDecyptingFetcherFactory provides factory) {
|
CompositionLocalProvider(LocalDecyptingFetcherFactory provides factory) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import app.dapk.st.core.Base64
|
import app.dapk.st.core.Base64
|
||||||
import app.dapk.st.core.ProvidableModule
|
import app.dapk.st.core.ProvidableModule
|
||||||
import app.dapk.st.matrix.common.CredentialsStore
|
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.message.MessageService
|
||||||
import app.dapk.st.matrix.room.RoomService
|
import app.dapk.st.matrix.room.RoomService
|
||||||
import app.dapk.st.matrix.sync.RoomStore
|
import app.dapk.st.matrix.sync.RoomStore
|
||||||
|
@ -30,5 +31,5 @@ class MessengerModule(
|
||||||
return TimelineUseCaseImpl(syncService, messageService, roomService, mergeWithLocalEchosUseCase)
|
return TimelineUseCaseImpl(syncService, messageService, roomService, mergeWithLocalEchosUseCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun decryptingFetcherFactory() = DecryptingFetcherFactory(context, base64)
|
internal fun decryptingFetcherFactory(roomId: RoomId) = DecryptingFetcherFactory(context, base64, roomId)
|
||||||
}
|
}
|
|
@ -258,6 +258,7 @@ private fun MessageImage(content: BubbleContent<RoomEvent.Image>) {
|
||||||
painter = rememberAsyncImagePainter(
|
painter = rememberAsyncImagePainter(
|
||||||
model = ImageRequest.Builder(context)
|
model = ImageRequest.Builder(context)
|
||||||
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
||||||
|
.memoryCacheKey(content.message.imageMeta.url)
|
||||||
.data(content.message)
|
.data(content.message)
|
||||||
.build()
|
.build()
|
||||||
),
|
),
|
||||||
|
@ -437,6 +438,7 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
||||||
painter = rememberAsyncImagePainter(
|
painter = rememberAsyncImagePainter(
|
||||||
model = ImageRequest.Builder(context)
|
model = ImageRequest.Builder(context)
|
||||||
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
||||||
|
.memoryCacheKey(replyingTo.imageMeta.url)
|
||||||
.data(replyingTo)
|
.data(replyingTo)
|
||||||
.build()
|
.build()
|
||||||
),
|
),
|
||||||
|
@ -478,7 +480,8 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
||||||
modifier = Modifier.size(message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
modifier = Modifier.size(message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||||
painter = rememberAsyncImagePainter(
|
painter = rememberAsyncImagePainter(
|
||||||
model = ImageRequest.Builder(context)
|
model = ImageRequest.Builder(context)
|
||||||
.data(content.message)
|
.data(message)
|
||||||
|
.memoryCacheKey(message.imageMeta.url)
|
||||||
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
||||||
.build()
|
.build()
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue