From 0730cd069c44c832a4a038a287e372945d6d7437 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 21:12:43 +0100 Subject: [PATCH] adding support for sending clear images --- .../kotlin/app/dapk/st/graph/AppModule.kt | 40 +++++++- .../domain/localecho/LocalEchoPersistence.kt | 9 ++ .../app/dapk/st/messenger/LocalEchoMapper.kt | 12 ++- .../messenger/MergeWithLocalEchosUseCase.kt | 2 +- .../dapk/st/messenger/MessengerViewModel.kt | 2 +- .../st/messenger/MessengerViewModelTest.kt | 2 +- .../notifications/NotificationRendererTest.kt | 1 - .../app/dapk/st/share/ShareEntryScreen.kt | 2 +- .../dapk/st/matrix/message/ApiSendResponse.kt | 6 ++ .../dapk/st/matrix/message/MessageService.kt | 30 ++++-- .../message/internal/DefaultMessageService.kt | 13 ++- .../message/internal/SendMessageUseCase.kt | 89 ++++++++++++------ .../st/matrix/message/internal/SendRequest.kt | 12 +++ .../sync/internal/sync/RoomEventFactory.kt | 1 + test-harness/src/test/kotlin/SmokeTest.kt | 16 +++- test-harness/src/test/kotlin/test/Test.kt | 48 ++++++++++ .../src/test/kotlin/test/TestMatrix.kt | 29 +++++- .../src/test/resources/test-image.png | Bin 0 -> 54591 bytes 18 files changed, 260 insertions(+), 54 deletions(-) create mode 100644 test-harness/src/test/resources/test-image.png diff --git a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt index 9a94db7..0776f5b 100644 --- a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt +++ b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt @@ -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") + } +} \ No newline at end of file diff --git a/domains/store/src/main/kotlin/app/dapk/st/domain/localecho/LocalEchoPersistence.kt b/domains/store/src/main/kotlin/app/dapk/st/domain/localecho/LocalEchoPersistence.kt index f560c1c..3ae9a1b 100644 --- a/domains/store/src/main/kotlin/app/dapk/st/domain/localecho/LocalEchoPersistence.kt +++ b/domains/store/src/main/kotlin/app/dapk/st/domain/localecho/LocalEchoPersistence.kt @@ -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) + ) + ) } } } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/LocalEchoMapper.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/LocalEchoMapper.kt index 478a654..a27894f 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/LocalEchoMapper.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/LocalEchoMapper.kt @@ -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), + ) + } } } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MergeWithLocalEchosUseCase.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MergeWithLocalEchosUseCase.kt index e7cd891..ba23d34 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MergeWithLocalEchosUseCase.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MergeWithLocalEchosUseCase.kt @@ -25,7 +25,7 @@ internal class MergeWithLocalEchosUseCaseImpl( return roomState.copy(events = sortedEvents) } - private fun uniqueEchos(echos: List, stateByEventId: Map, member: RoomMember): List { + private fun uniqueEchos(echos: List, stateByEventId: Map, member: RoomMember): List { return with(localEventMapper) { echos .filter { echo -> echo.eventId == null || stateByEventId[echo.eventId] == null } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerViewModel.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerViewModel.kt index 47e7676..86930b0 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerViewModel.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerViewModel.kt @@ -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(), diff --git a/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerViewModelTest.kt b/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerViewModelTest.kt index 3a88489..450bf5d 100644 --- a/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerViewModelTest.kt +++ b/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerViewModelTest.kt @@ -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( { copy(roomId = A_ROOM_ID) }, diff --git a/features/notifications/src/test/kotlin/app/dapk/st/notifications/NotificationRendererTest.kt b/features/notifications/src/test/kotlin/app/dapk/st/notifications/NotificationRendererTest.kt index bd50bd7..ed128f2 100644 --- a/features/notifications/src/test/kotlin/app/dapk/st/notifications/NotificationRendererTest.kt +++ b/features/notifications/src/test/kotlin/app/dapk/st/notifications/NotificationRendererTest.kt @@ -51,7 +51,6 @@ class NotificationRendererTest { } notificationRenderer.render(NotificationState(emptyMap(), removedRooms, emptySet(), emptySet())) - verifyExpects() } diff --git a/features/share-entry/src/main/kotlin/app/dapk/st/share/ShareEntryScreen.kt b/features/share-entry/src/main/kotlin/app/dapk/st/share/ShareEntryScreen.kt index 85ddb3b..1dbc7e7 100644 --- a/features/share-entry/src/main/kotlin/app/dapk/st/share/ShareEntryScreen.kt +++ b/features/share-entry/src/main/kotlin/app/dapk/st/share/ShareEntryScreen.kt @@ -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 diff --git a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/ApiSendResponse.kt b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/ApiSendResponse.kt index ebcefaf..97fdc57 100644 --- a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/ApiSendResponse.kt +++ b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/ApiSendResponse.kt @@ -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, ) \ No newline at end of file diff --git a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/MessageService.kt b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/MessageService.kt index 7ef72a5..2684194 100644 --- a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/MessageService.kt +++ b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/MessageService.kt @@ -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 = 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) } } diff --git a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/DefaultMessageService.kt b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/DefaultMessageService.kt index c02ab8d..14d4f08 100644 --- a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/DefaultMessageService.kt +++ b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/DefaultMessageService.kt @@ -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) diff --git a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendMessageUseCase.kt b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendMessageUseCase.kt index e35fb0f..4166a2c 100644 --- a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendMessageUseCase.kt +++ b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendMessageUseCase.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendRequest.kt b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendRequest.kt index 5183f72..5cf1d64 100644 --- a/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendRequest.kt +++ b/matrix/services/message/src/main/kotlin/app/dapk/st/matrix/message/internal/SendRequest.kt @@ -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( @@ -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( + 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()}" \ No newline at end of file diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomEventFactory.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomEventFactory.kt index 4dfcd7b..6541121 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomEventFactory.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomEventFactory.kt @@ -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, diff --git a/test-harness/src/test/kotlin/SmokeTest.kt b/test-harness/src/test/kotlin/SmokeTest.kt index 46e9f90..d153626 100644 --- a/test-harness/src/test/kotlin/SmokeTest.kt +++ b/test-harness/src/test/kotlin/SmokeTest.kt @@ -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.automaticVerification(testMatrix: TestMatri // do nothing } } -} \ No newline at end of file +} + +private fun loadResourceStream(name: String) = Thread.currentThread().contextClassLoader.getResourceAsStream(name)!! +private fun loadResourceFile(name: String) = Paths.get(Thread.currentThread().contextClassLoader.getResource(name)!!.toURI()).toFile() diff --git a/test-harness/src/test/kotlin/test/Test.kt b/test-harness/src/test/kotlin/test/Test.kt index 5de0fc8..91da2a0 100644 --- a/test-harness/src/test/kotlin/test/Test.kt +++ b/test-harness/src/test/kotlin/test/Test.kt @@ -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().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) } \ No newline at end of file diff --git a/test-harness/src/test/kotlin/test/TestMatrix.kt b/test-harness/src/test/kotlin/test/TestMatrix.kt index 9a3ea97..7a5996a 100644 --- a/test-harness/src/test/kotlin/test/TestMatrix.kt +++ b/test-harness/src/test/kotlin/test/TestMatrix.kt @@ -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() + ) + } + } \ No newline at end of file diff --git a/test-harness/src/test/resources/test-image.png b/test-harness/src/test/resources/test-image.png new file mode 100644 index 0000000000000000000000000000000000000000..3127893daad08ca86de9a0bc23043a626155a43f GIT binary patch literal 54591 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 literal 0 HcmV?d00001