adding support for sending clear images

This commit is contained in:
Adam Brown 2022-08-09 21:12:43 +01:00
parent 3859aa7f0b
commit 0730cd069c
18 changed files with 260 additions and 54 deletions

View File

@ -2,8 +2,11 @@ package app.dapk.st.graph
import android.app.Application
import android.app.PendingIntent
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import app.dapk.db.DapkDb
import app.dapk.st.BuildConfig
@ -33,6 +36,7 @@ import app.dapk.st.matrix.http.ktor.KtorMatrixHttpClientFactory
import app.dapk.st.matrix.message.MessageEncrypter
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.message.installMessageService
import app.dapk.st.matrix.message.internal.ImageContentReader
import app.dapk.st.matrix.message.messageService
import app.dapk.st.matrix.push.installPushService
import app.dapk.st.matrix.push.pushService
@ -86,10 +90,10 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
private val workModule = WorkModule(context)
private val imageLoaderModule = ImageLoaderModule(context)
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers)
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver)
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker)
val coreAndroidModule = app.dapk.st.core.CoreAndroidModule(intentFactory = object : IntentFactory {
val coreAndroidModule = CoreAndroidModule(intentFactory = object : IntentFactory {
override fun notificationOpenApp(context: Context) = PendingIntent.getActivity(
context,
1000,
@ -137,7 +141,7 @@ internal class FeatureModules internal constructor(
private val domainModules: DomainModules,
private val trackingModule: TrackingModule,
private val workModule: WorkModule,
private val coreAndroidModule: app.dapk.st.core.CoreAndroidModule,
private val coreAndroidModule: CoreAndroidModule,
imageLoaderModule: ImageLoaderModule,
context: Context,
buildMeta: BuildMeta,
@ -213,6 +217,7 @@ internal class MatrixModules(
private val workModule: WorkModule,
private val logger: MatrixLogger,
private val coroutineDispatchers: CoroutineDispatchers,
private val contentResolver: ContentResolver,
) {
val matrix by unsafeLazy {
@ -253,7 +258,8 @@ internal class MatrixModules(
base64 = base64,
coroutineDispatchers = coroutineDispatchers,
)
installMessageService(store.localEchoStore, BackgroundWorkAdapter(workModule.workScheduler())) { serviceProvider ->
val imageContentReader = AndroidImageContentReader(contentResolver)
installMessageService(store.localEchoStore, BackgroundWorkAdapter(workModule.workScheduler()), imageContentReader) { serviceProvider ->
MessageEncrypter { message ->
val result = serviceProvider.cryptoService().encrypt(
roomId = when (message) {
@ -273,6 +279,7 @@ internal class MatrixModules(
)
)
)
is MessageService.Message.ImageMessage -> TODO()
}
)
@ -344,12 +351,14 @@ internal class MatrixModules(
apiEvent.content.methods,
apiEvent.content.timestampPosix,
)
is ApiToDeviceEvent.VerificationReady -> Verification.Event.Ready(
apiEvent.sender,
apiEvent.content.fromDevice,
apiEvent.content.transactionId,
apiEvent.content.methods,
)
is ApiToDeviceEvent.VerificationStart -> Verification.Event.Started(
apiEvent.sender,
apiEvent.content.fromDevice,
@ -360,6 +369,7 @@ internal class MatrixModules(
apiEvent.content.short,
apiEvent.content.transactionId,
)
is ApiToDeviceEvent.VerificationCancel -> TODO()
is ApiToDeviceEvent.VerificationAccept -> TODO()
is ApiToDeviceEvent.VerificationKey -> Verification.Event.Key(
@ -367,6 +377,7 @@ internal class MatrixModules(
apiEvent.content.transactionId,
apiEvent.content.key
)
is ApiToDeviceEvent.VerificationMac -> Verification.Event.Mac(
apiEvent.sender,
apiEvent.content.transactionId,
@ -417,3 +428,24 @@ internal class DomainModules(
val pushModule by unsafeLazy { PushModule(matrixModules.push, errorTracker) }
val taskRunnerModule by unsafeLazy { TaskRunnerModule(TaskRunnerAdapter(matrixModules.matrix::run, AppTaskRunner(matrixModules.push))) }
}
internal class AndroidImageContentReader(private val contentResolver: ContentResolver) : ImageContentReader {
override fun read(uri: String): ImageContentReader.ImageContent {
val androidUri = Uri.parse(uri)
val fileStream = contentResolver.openInputStream(androidUri) ?: throw IllegalArgumentException("Could not process $uri")
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeStream(fileStream, null, options)
return contentResolver.openInputStream(androidUri)?.use { stream ->
val output = stream.readBytes()
ImageContentReader.ImageContent(
height = options.outHeight,
width = options.outWidth,
size = output.size.toLong(),
fileName = androidUri.lastPathSegment ?: "file",
content = output
)
} ?: throw IllegalArgumentException("Could not process $uri")
}
}

View File

@ -58,6 +58,7 @@ class LocalEchoPersistence(
database.transaction {
when (message) {
is MessageService.Message.TextMessage -> database.localEchoQueries.delete(message.localId)
is MessageService.Message.ImageMessage -> database.localEchoQueries.delete(message.localId)
}
}
} catch (error: Exception) {
@ -86,6 +87,14 @@ class LocalEchoPersistence(
Json.encodeToString(MessageService.LocalEcho.serializer(), localEcho)
)
)
is MessageService.Message.ImageMessage -> database.localEchoQueries.insert(
DbLocalEcho(
message.localId,
message.roomId.value,
Json.encodeToString(MessageService.LocalEcho.serializer(), localEcho)
)
)
}
}
}

View File

@ -8,7 +8,7 @@ import app.dapk.st.matrix.sync.RoomEvent
internal class LocalEchoMapper(private val metaMapper: MetaMapper) {
fun MessageService.LocalEcho.toMessage(member: RoomMember): RoomEvent.Message {
fun MessageService.LocalEcho.toMessage(member: RoomMember): RoomEvent {
return when (val message = this.message) {
is MessageService.Message.TextMessage -> {
RoomEvent.Message(
@ -19,7 +19,15 @@ internal class LocalEchoMapper(private val metaMapper: MetaMapper) {
meta = metaMapper.toMeta(this)
)
}
is MessageService.Message.ImageMessage -> TODO()
is MessageService.Message.ImageMessage -> {
RoomEvent.Image(
eventId = this.eventId ?: EventId(this.localId),
author = member,
utcTimestamp = message.timestampUtc,
meta = metaMapper.toMeta(this),
imageMeta = RoomEvent.Image.ImageMeta(100, 100, message.content.uri, null),
)
}
}
}

View File

@ -25,7 +25,7 @@ internal class MergeWithLocalEchosUseCaseImpl(
return roomState.copy(events = sortedEvents)
}
private fun uniqueEchos(echos: List<MessageService.LocalEcho>, stateByEventId: Map<EventId, RoomEvent>, member: RoomMember): List<RoomEvent.Message> {
private fun uniqueEchos(echos: List<MessageService.LocalEcho>, stateByEventId: Map<EventId, RoomEvent>, member: RoomMember): List<RoomEvent> {
return with(localEventMapper) {
echos
.filter { echo -> echo.eventId == null || stateByEventId[echo.eventId] == null }

View File

@ -109,7 +109,7 @@ internal class MessengerViewModel(
viewModelScope.launch {
messageService.scheduleMessage(
MessageService.Message.ImageMessage(
MessageService.Message.Content.ImageContent(uri = copy.values.first().uri.value),
MessageService.Message.Content.ApiImageContent(uri = copy.values.first().uri.value),
roomId = roomState.roomOverview.roomId,
sendEncrypted = roomState.roomOverview.isEncrypted,
localId = localIdFactory.create(),

View File

@ -71,7 +71,7 @@ class MessengerViewModelTest {
val state = aMessengerStateWithEvent(AN_EVENT_ID, A_SELF_ID)
fakeObserveTimelineUseCase.given(A_ROOM_ID, A_SELF_ID).returns(flowOf(state))
viewModel.test().post(MessengerAction.OnMessengerVisible(A_ROOM_ID))
viewModel.test().post(MessengerAction.OnMessengerVisible(A_ROOM_ID, emptyList()))
assertStates<MessengerScreenState>(
{ copy(roomId = A_ROOM_ID) },

View File

@ -51,7 +51,6 @@ class NotificationRendererTest {
}
notificationRenderer.render(NotificationState(emptyMap(), removedRooms, emptySet(), emptySet()))
verifyExpects()
}

View File

@ -84,7 +84,7 @@ private fun Content(listState: LazyListState, state: Content, onClick: (Item) ->
@Composable
private fun DirectoryItem(item: Item, onClick: (Item) -> Unit) {
val roomName = item.roomName
val roomName = item.roomName.ifEmpty { "Empty " }
Box(
Modifier

View File

@ -1,10 +1,16 @@
package app.dapk.st.matrix.message
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.MxUrl
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ApiSendResponse(
@SerialName("event_id") val eventId: EventId,
)
@Serializable
data class ApiUploadResponse(
@SerialName("content_uri") val contentUri: MxUrl,
)

View File

@ -4,11 +4,9 @@ import app.dapk.st.matrix.MatrixService
import app.dapk.st.matrix.MatrixServiceInstaller
import app.dapk.st.matrix.MatrixServiceProvider
import app.dapk.st.matrix.ServiceDepFactory
import app.dapk.st.matrix.common.AlgorithmName
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.MessageType
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.*
import app.dapk.st.matrix.message.internal.DefaultMessageService
import app.dapk.st.matrix.message.internal.ImageContentReader
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -49,7 +47,7 @@ interface MessageService : MatrixService {
@Serializable
@SerialName("image_message")
data class ImageMessage(
@SerialName("content") val content: Content.ImageContent,
@SerialName("content") val content: Content.ApiImageContent,
@SerialName("send_encrypted") val sendEncrypted: Boolean,
@SerialName("room_id") val roomId: RoomId,
@SerialName("local_id") val localId: String,
@ -65,10 +63,25 @@ interface MessageService : MatrixService {
) : Content()
@Serializable
data class ImageContent(
data class ApiImageContent(
@SerialName("uri") val uri: String,
@SerialName("msgtype") val type: String = MessageType.IMAGE.value,
) : Content()
@Serializable
data class ImageContent(
@SerialName("url") val url: MxUrl,
@SerialName("body") val filename: String,
@SerialName("info") val info: Info,
@SerialName("msgtype") val type: String = MessageType.IMAGE.value,
) : Content() {
@Serializable
data class Info(
@SerialName("h") val height: Int,
@SerialName("w") val width: Int,
@SerialName("size") val size: Long,
)
}
}
}
@ -127,10 +140,11 @@ interface MessageService : MatrixService {
fun MatrixServiceInstaller.installMessageService(
localEchoStore: LocalEchoStore,
backgroundScheduler: BackgroundScheduler,
imageContentReader: ImageContentReader,
messageEncrypter: ServiceDepFactory<MessageEncrypter> = ServiceDepFactory { MissingMessageEncrypter },
) {
this.install { (httpClient, _, installedServices) ->
SERVICE_KEY to DefaultMessageService(httpClient, localEchoStore, backgroundScheduler, messageEncrypter.create(installedServices))
SERVICE_KEY to DefaultMessageService(httpClient, localEchoStore, backgroundScheduler, messageEncrypter.create(installedServices), imageContentReader)
}
}

View File

@ -20,16 +20,20 @@ internal class DefaultMessageService(
private val localEchoStore: LocalEchoStore,
private val backgroundScheduler: BackgroundScheduler,
messageEncrypter: MessageEncrypter,
imageContentReader: ImageContentReader,
) : MessageService, MatrixTaskRunner {
private val sendMessageUseCase = SendMessageUseCase(httpClient, messageEncrypter)
private val sendMessageUseCase = SendMessageUseCase(httpClient, messageEncrypter, imageContentReader)
private val sendEventMessageUseCase = SendEventMessageUseCase(httpClient)
override suspend fun canRun(task: MatrixTaskRunner.MatrixTask) = task.type == MATRIX_MESSAGE_TASK_TYPE
override suspend fun canRun(task: MatrixTaskRunner.MatrixTask) = task.type == MATRIX_MESSAGE_TASK_TYPE || task.type == MATRIX_IMAGE_MESSAGE_TASK_TYPE
override suspend fun run(task: MatrixTaskRunner.MatrixTask): MatrixTaskRunner.TaskResult {
require(task.type == MATRIX_MESSAGE_TASK_TYPE)
val message = Json.decodeFromString(MessageService.Message.TextMessage.serializer(), task.jsonPayload)
val message = when(task.type) {
MATRIX_MESSAGE_TASK_TYPE -> Json.decodeFromString(MessageService.Message.TextMessage.serializer(), task.jsonPayload)
MATRIX_IMAGE_MESSAGE_TASK_TYPE -> Json.decodeFromString(MessageService.Message.ImageMessage.serializer(), task.jsonPayload)
else -> throw IllegalStateException("Unhandled task type: ${task.type}")
}
return try {
sendMessage(message)
MatrixTaskRunner.TaskResult.Success
@ -70,6 +74,7 @@ internal class DefaultMessageService(
Json.encodeToString(MessageService.Message.TextMessage.serializer(), this)
)
}
is MessageService.Message.ImageMessage -> BackgroundScheduler.Task(
type = MATRIX_IMAGE_MESSAGE_TASK_TYPE,
Json.encodeToString(MessageService.Message.ImageMessage.serializer(), this)

View File

@ -9,6 +9,7 @@ import app.dapk.st.matrix.message.MessageService
internal class SendMessageUseCase(
private val httpClient: MatrixHttpClient,
private val messageEncrypter: MessageEncrypter,
private val imageContentReader: ImageContentReader,
) {
suspend fun sendMessage(message: MessageService.Message): EventId {
@ -23,6 +24,7 @@ internal class SendMessageUseCase(
content = messageEncrypter.encrypt(message),
)
}
false -> {
sendRequest(
roomId = message.roomId,
@ -34,37 +36,68 @@ internal class SendMessageUseCase(
}
httpClient.execute(request).eventId
}
is MessageService.Message.ImageMessage -> {
// upload image, then send message
// POST /_matrix/media/v3/upload
// message.content.uri
/**
* {
"content": {
"body": "filename.jpg",
"info": {
"h": 398,
"mimetype": "image/jpeg",
"size": 31037,
"w": 394
},
"msgtype": "m.image",
"url": "mxc://example.org/JWEIFJgwEIhweiWJE"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}
*/
TODO()
is MessageService.Message.ImageMessage -> {
println("Sending message")
val imageContent = imageContentReader.read(message.content.uri)
println("content: ${imageContent.size}")
val uri = httpClient.execute(uploadRequest(imageContent.content, imageContent.fileName, "image/png")).contentUri
println("Got uri $uri")
val request = sendRequest(
roomId = message.roomId,
eventType = EventType.ROOM_MESSAGE,
txId = message.localId,
content = MessageService.Message.Content.ImageContent(
url = uri,
filename = "foobar.png",
MessageService.Message.Content.ImageContent.Info(
height = imageContent.height,
width = imageContent.width,
size = imageContent.size
)
),
)
httpClient.execute(request).eventId
}
}
}
}
interface ImageContentReader {
fun read(uri: String): ImageContent
data class ImageContent(
val height: Int,
val width: Int,
val size: Long,
val fileName: String,
val content: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ImageContent
if (height != other.height) return false
if (width != other.width) return false
if (size != other.size) return false
if (!content.contentEquals(other.content)) return false
return true
}
override fun hashCode(): Int {
var result = height
result = 31 * result + width
result = 31 * result + size.hashCode()
result = 31 * result + content.contentHashCode()
return result
}
}
}

View File

@ -6,9 +6,12 @@ import app.dapk.st.matrix.http.MatrixHttpClient
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest.Companion.httpRequest
import app.dapk.st.matrix.http.jsonBody
import app.dapk.st.matrix.message.ApiSendResponse
import app.dapk.st.matrix.message.ApiUploadResponse
import app.dapk.st.matrix.message.MessageEncrypter
import app.dapk.st.matrix.message.MessageService.EventMessage
import app.dapk.st.matrix.message.MessageService.Message
import io.ktor.content.*
import io.ktor.http.*
import java.util.*
internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, content: Message.Content) = httpRequest<ApiSendResponse>(
@ -17,6 +20,7 @@ internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, con
body = when (content) {
is Message.Content.TextContent -> jsonBody(Message.Content.TextContent.serializer(), content, MatrixHttpClient.jsonWithDefaults)
is Message.Content.ImageContent -> jsonBody(Message.Content.ImageContent.serializer(), content, MatrixHttpClient.jsonWithDefaults)
is Message.Content.ApiImageContent -> throw IllegalArgumentException()
}
)
@ -34,4 +38,12 @@ internal fun sendRequest(roomId: RoomId, eventType: EventType, content: EventMes
}
)
internal fun uploadRequest(body: ByteArray, filename: String, contentType: String) = httpRequest<ApiUploadResponse>(
path = "_matrix/media/r0/upload/?filename=$filename",
headers = listOf("Content-Type" to contentType),
method = MatrixHttpClient.Method.POST,
body = ByteArrayContent(body, ContentType.parse(contentType)),
)
fun txId() = "local.${UUID.randomUUID()}"

View File

@ -44,6 +44,7 @@ internal class RoomEventFactory(
private fun ApiTimelineEvent.TimelineMessage.readImageMeta(userCredentials: UserCredentials): RoomEvent.Image.ImageMeta {
val content = this.content as ApiTimelineEvent.TimelineMessage.Content.Image
println(content)
return RoomEvent.Image.ImageMeta(
content.info?.width,
content.info?.height,

View File

@ -22,6 +22,7 @@ import test.MatrixTestScope
import test.TestMatrix
import test.flowTest
import test.restoreLoginAndInitialSync
import java.nio.file.Paths
import java.util.*
private const val HTTPS_TEST_SERVER_URL = "https://localhost:8080/"
@ -73,6 +74,14 @@ class SmokeTest {
@Test
@Order(6)
fun `can send and receive clear image messages`() = testAfterInitialSync { alice, bob ->
val testImage = loadResourceFile("test-image.png")
alice.sendImageMessage(SharedState.sharedRoom, testImage, isEncrypted = false)
bob.expectImageMessage(SharedState.sharedRoom, testImage, SharedState.alice.roomMember)
}
@Test
@Order(7)
fun `can request and verify devices`() = testAfterInitialSync { alice, bob ->
alice.client.cryptoService().verificationAction(Verification.Action.Request(bob.userId(), bob.deviceId()))
alice.client.cryptoService().verificationState().automaticVerification(alice).expectAsync { it == Verification.State.Done }
@ -85,7 +94,7 @@ class SmokeTest {
fun `can import E2E room keys file`() = runTest {
val ignoredUser = TestUser("ignored", RoomMember(UserId("ignored"), null, null), "ignored")
val cryptoService = TestMatrix(ignoredUser, includeLogging = true).client.cryptoService()
val stream = Thread.currentThread().contextClassLoader.getResourceAsStream("element-keys.txt")!!
val stream = loadResourceStream("element-keys.txt")
val result = with(cryptoService) {
stream.importRoomKeys(password = "aaaaaa")
@ -182,4 +191,7 @@ private fun Flow<Verification.State>.automaticVerification(testMatrix: TestMatri
// do nothing
}
}
}
}
private fun loadResourceStream(name: String) = Thread.currentThread().contextClassLoader.getResourceAsStream(name)!!
private fun loadResourceFile(name: String) = Paths.get(Thread.currentThread().contextClassLoader.getResource(name)!!.toURI()).toFile()

View File

@ -5,17 +5,31 @@ package test
import TestMessage
import TestUser
import app.dapk.st.core.extensions.ifNull
import app.dapk.st.matrix.common.MxUrl
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.RoomMember
import app.dapk.st.matrix.common.convertMxUrToUrl
import app.dapk.st.matrix.http.MatrixHttpClient
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.message.messageService
import app.dapk.st.matrix.sync.RoomEvent
import app.dapk.st.matrix.sync.syncService
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.fail
import org.amshove.kluent.shouldBeEqualTo
import java.io.File
import java.math.BigInteger
import java.net.URL
import java.security.MessageDigest
import java.util.*
fun flowTest(block: suspend MatrixTestScope.() -> Unit) {
@ -131,6 +145,20 @@ class MatrixTestScope(private val testScope: TestScope) {
.assert(message)
}
suspend fun TestMatrix.expectImageMessage(roomId: RoomId, image: File, author: RoomMember) {
println("expecting ${image.absolutePath} from ${author.displayName}")
this.client.syncService().room(roomId)
.map {
it.events.filterIsInstance<RoomEvent.Image>().map {
println("found: ${it.imageMeta.url}")
val output = File(image.parentFile.absolutePath, "output.png")
HttpClient().request(it.imageMeta.url).bodyAsChannel().copyAndClose(output.writeChannel())
output.readBytes().md5Hash() to it.author
}.firstOrNull()
}
.assert(image.readBytes().md5Hash() to author)
}
suspend fun TestMatrix.sendTextMessage(roomId: RoomId, content: String, isEncrypted: Boolean) {
println("sending $content")
this.client.messageService().scheduleMessage(
@ -144,6 +172,21 @@ class MatrixTestScope(private val testScope: TestScope) {
)
}
suspend fun TestMatrix.sendImageMessage(roomId: RoomId, file: File, isEncrypted: Boolean) {
println("sending ${file.name}")
this.client.messageService().scheduleMessage(
MessageService.Message.ImageMessage(
content = MessageService.Message.Content.ApiImageContent(
uri = file.absolutePath,
),
roomId = roomId,
sendEncrypted = isEncrypted,
localId = "local.${UUID.randomUUID()}",
timestampUtc = System.currentTimeMillis(),
)
)
}
suspend fun TestMatrix.loginWithInitialSync() {
this.restoreLogin()
client.syncService().startSyncing().collectAsync(testScope) {
@ -162,5 +205,10 @@ class MatrixTestScope(private val testScope: TestScope) {
suspend fun release() {
inProgressInstances.forEach { it.release() }
}
}
private fun ByteArray.md5Hash(): String {
val md = MessageDigest.getInstance("MD5")
val bigInt = BigInteger(1, md.digest(this))
return String.format("%032x", bigInt)
}

View File

@ -22,6 +22,7 @@ import app.dapk.st.matrix.http.ktor.KtorMatrixHttpClientFactory
import app.dapk.st.matrix.message.MessageEncrypter
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.message.installMessageService
import app.dapk.st.matrix.message.internal.ImageContentReader
import app.dapk.st.matrix.message.messageService
import app.dapk.st.matrix.push.installPushService
import app.dapk.st.matrix.room.RoomMessenger
@ -41,7 +42,9 @@ import test.impl.InMemoryDatabase
import test.impl.InMemoryPreferences
import test.impl.InstantScheduler
import test.impl.PrintingErrorTracking
import java.io.File
import java.time.Clock
import javax.imageio.ImageIO
class TestMatrix(
private val user: TestUser,
@ -114,7 +117,7 @@ class TestMatrix(
coroutineDispatchers = coroutineDispatchers,
)
installMessageService(storeModule.localEchoStore, InstantScheduler(it)) { serviceProvider ->
installMessageService(storeModule.localEchoStore, InstantScheduler(it), JavaImageContentReader()) { serviceProvider ->
MessageEncrypter { message ->
val result = serviceProvider.cryptoService().encrypt(
roomId = when (message) {
@ -133,6 +136,7 @@ class TestMatrix(
)
)
)
is MessageService.Message.ImageMessage -> TODO()
}
)
@ -200,12 +204,14 @@ class TestMatrix(
apiEvent.content.methods,
apiEvent.content.timestampPosix,
)
is ApiToDeviceEvent.VerificationReady -> Verification.Event.Ready(
apiEvent.sender,
apiEvent.content.fromDevice,
apiEvent.content.transactionId,
apiEvent.content.methods,
)
is ApiToDeviceEvent.VerificationStart -> Verification.Event.Started(
apiEvent.sender,
apiEvent.content.fromDevice,
@ -216,6 +222,7 @@ class TestMatrix(
apiEvent.content.short,
apiEvent.content.transactionId,
)
is ApiToDeviceEvent.VerificationAccept -> Verification.Event.Accepted(
apiEvent.sender,
apiEvent.content.fromDevice,
@ -226,12 +233,14 @@ class TestMatrix(
apiEvent.content.short,
apiEvent.content.transactionId,
)
is ApiToDeviceEvent.VerificationCancel -> TODO()
is ApiToDeviceEvent.VerificationKey -> Verification.Event.Key(
apiEvent.sender,
apiEvent.content.transactionId,
apiEvent.content.key
)
is ApiToDeviceEvent.VerificationMac -> Verification.Event.Mac(
apiEvent.sender,
apiEvent.content.transactionId,
@ -288,6 +297,7 @@ class TestMatrix(
suspend fun deviceId() = storeModule.credentialsStore().credentials()!!.deviceId
suspend fun userId() = storeModule.credentialsStore().credentials()!!.userId
suspend fun credentials() = storeModule.credentialsStore().credentials()!!
suspend fun release() {
coroutineDispatchers.global.waitForCancel()
@ -316,4 +326,21 @@ class JavaBase64 : Base64 {
override fun decode(input: String): ByteArray {
return java.util.Base64.getDecoder().decode(input)
}
}
class JavaImageContentReader : ImageContentReader {
override fun read(uri: String): ImageContentReader.ImageContent {
val file = File(uri)
val size = file.length()
val image = ImageIO.read(file)
return ImageContentReader.ImageContent(
height = image.height,
width = image.width,
size = size,
fileName = file.name,
content = file.readBytes()
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB