[MatrixSDK] finish mapping timeline and makes it compile

This commit is contained in:
ganfra 2023-03-13 20:18:16 +01:00
parent fb85f35525
commit 801eecfe8d
44 changed files with 370 additions and 242 deletions

View file

@ -32,12 +32,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.SlidingSyncMode
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
import org.matrix.rustcomponents.sdk.SlidingSyncViewBuilder
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.io.File
@ -181,9 +181,9 @@ class RustMatrixClient constructor(
} catch (failure: Throwable) {
Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
}
client.destroy()
baseDirectory.deleteSessionDirectory(userID = client.userId())
sessionStore.removeSession(client.userId())
client.destroy()
}
override suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) {
@ -199,23 +199,30 @@ class RustMatrixClient constructor(
}
@OptIn(ExperimentalUnsignedTypes::class)
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> =
override suspend fun loadMediaContent(url: String): Result<ByteArray> =
withContext(dispatchers.io) {
runCatching {
client.getMediaContent(source).toUByteArray().toByteArray()
mediaSourceFromUrl(url).use { source ->
client.getMediaContent(source).toUByteArray().toByteArray()
}
}
}
@OptIn(ExperimentalUnsignedTypes::class)
override suspend fun loadMediaThumbnailForSource(
source: MediaSource,
override suspend fun loadMediaThumbnail(
url: String,
width: Long,
height: Long
): Result<ByteArray> =
withContext(dispatchers.io) {
runCatching {
client.getMediaThumbnail(source, width.toULong(), height.toULong()).toUByteArray()
.toByteArray()
mediaSourceFromUrl(url).use { source ->
client.getMediaThumbnail(
source = source,
width = width.toULong(),
height = height.toULong()
).toUByteArray().toByteArray()
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import org.matrix.rustcomponents.sdk.AuthenticationException as RustAuthenticationException
fun Throwable.mapAuthenticationException(): Throwable {
return when (this) {
is RustAuthenticationException.ClientMissing -> AuthenticationException.ClientMissing(this.message!!)
is RustAuthenticationException.Generic -> AuthenticationException.Generic(this.message!!)
is RustAuthenticationException.InvalidServerName -> AuthenticationException.InvalidServerName(this.message!!)
is RustAuthenticationException.SessionMissing -> AuthenticationException.SessionMissing(this.message!!)
is RustAuthenticationException.SlidingSyncNotAvailable -> AuthenticationException.SlidingSyncNotAvailable(this.message!!)
else -> this
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
fun HomeserverLoginDetails.map(): MatrixHomeServerDetails = use {
MatrixHomeServerDetails(
url = url(),
supportsPasswordLogin = supportsPasswordLogin(),
authenticationIssuer = authenticationIssuer()
)
}

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
@ -26,7 +27,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.impl.RustMatrixClient
import io.element.android.libraries.matrix.impl.util.logError
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope
@ -39,7 +39,6 @@ import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@ -63,10 +62,10 @@ class RustMatrixAuthenticationService @Inject constructor(
sessionStore.getLatestSession()?.userId?.let { UserId(it) }
}
override suspend fun restoreSession(sessionId: SessionId) = withContext(coroutineDispatchers.io) {
val sessionData = sessionStore.getSession(sessionId.value)
if (sessionData != null) {
try {
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> = withContext(coroutineDispatchers.io) {
runCatching {
val sessionData = sessionStore.getSession(sessionId.value)
if (sessionData != null) {
val client = ClientBuilder()
.basePath(baseDirectory.absolutePath)
.homeserverUrl(sessionData.homeserverUrl)
@ -74,36 +73,39 @@ class RustMatrixAuthenticationService @Inject constructor(
.use { it.build() }
client.restoreSession(sessionData.toSession())
createMatrixClient(client)
} catch (throwable: Throwable) {
logError(throwable)
null
} else {
throw IllegalStateException("No session to restore with id $sessionId")
}
} else null
}.mapFailure { failure ->
failure.mapAuthenticationException()
}
}
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver
override suspend fun setHomeserver(homeserver: String) {
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
withContext(coroutineDispatchers.io) {
authService.configureHomeserver(homeserver)
val homeServerDetails = authService.homeserverDetails()?.use { MatrixHomeServerDetails(it) }
if (homeServerDetails != null) {
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
runCatching {
authService.configureHomeserver(homeserver)
val homeServerDetails = authService.homeserverDetails()?.map()
if (homeServerDetails != null) {
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
}
}
}.mapFailure { failure ->
failure.mapAuthenticationException()
}
}
override suspend fun login(username: String, password: String): SessionId =
override suspend fun login(username: String, password: String): Result<SessionId> =
withContext(coroutineDispatchers.io) {
val client = try {
authService.login(username, password, "ElementX Android", null)
} catch (failure: Throwable) {
Timber.e(failure, "Fail login")
throw failure
runCatching {
val client = authService.login(username, password, "ElementX Android", null)
val sessionData = client.use { it.session().toSessionData() }
sessionStore.storeData(sessionData)
SessionId(sessionData.userId)
}
val sessionData = client.use { it.session().toSessionData() }
sessionStore.storeData(sessionData)
SessionId(sessionData.userId)
}.mapFailure { failure ->
failure.mapAuthenticationException()
}
private fun createMatrixClient(client: Client): MatrixClient {

View file

@ -18,23 +18,15 @@ package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MediaResolver
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
if (url.isNullOrEmpty()) return null
return mediaSourceFromUrl(url).use { mediaSource ->
resolve(mediaSource, kind)
}
}
private suspend fun resolve(mediaSource: MediaSource, kind: MediaResolver.Kind): ByteArray? {
return when (kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(mediaSource)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
mediaSource,
is MediaResolver.Kind.Content -> client.loadMediaContent(url)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnail(
url,
kind.width.toLong(),
kind.height.toLong()
)

View file

@ -17,17 +17,17 @@
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.media.useUrl
import org.matrix.rustcomponents.sdk.Message
@ -38,40 +38,40 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat
class EventMessageMapper {
fun map(message: Message): TimelineEventMessageContent = message.use {
fun map(message: Message): MessageContent = message.use {
val content = message.msgtype().use { type ->
when (type) {
is MessageType.Audio -> {
AudioMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map())
AudioMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
is MessageType.File -> {
FileMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map())
FileMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
is MessageType.Image -> {
ImageMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map())
ImageMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
is MessageType.Notice -> {
NoticeMessageContent(type.content.body, type.content.formatted?.map())
NoticeMessageType(type.content.body, type.content.formatted?.map())
}
is MessageType.Text -> {
TextMessageContent(type.content.body, type.content.formatted?.map())
TextMessageType(type.content.body, type.content.formatted?.map())
}
is MessageType.Emote -> {
EmoteMessageContent(type.content.body, type.content.formatted?.map())
EmoteMessageType(type.content.body, type.content.formatted?.map())
}
is MessageType.Video -> {
VideoMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map())
VideoMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
null -> {
UnknownMessageContent
UnknownMessageType
}
}
}
TimelineEventMessageContent(
MessageContent(
body = message.body(),
inReplyTo = message.inReplyTo()?.let { UserId(it) },
isEdited = message.isEdited(),
content = content
type = content
)
}
}

View file

@ -17,31 +17,33 @@
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLike
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseState
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChange
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembership
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) {
fun map(content: TimelineItemContent): TimelineEventContent = content.use {
when (val kind = content.kind()) {
is TimelineItemContentKind.FailedToParseMessageLike -> {
FailedToParseMessageLike(
FailedToParseMessageLikeContent(
eventType = kind.eventType,
error = kind.error
)
}
is TimelineItemContentKind.FailedToParseState -> {
FailedToParseState(
FailedToParseStateContent(
eventType = kind.eventType,
stateKey = kind.stateKey,
error = kind.error
@ -56,7 +58,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
}
}
is TimelineItemContentKind.ProfileChange -> {
ProfileChange(
ProfileChangeContent(
displayName = kind.displayName,
prevDisplayName = kind.prevDisplayName,
avatarUrl = kind.avatarUrl,
@ -67,7 +69,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
RedactedContent
}
is TimelineItemContentKind.RoomMembership -> {
RoomMembership(
RoomMembershipContent(
UserId(kind.userId),
MembershipChange.JOINED
)
@ -83,8 +85,18 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
)
}
is TimelineItemContentKind.UnableToDecrypt -> {
UnknownContent
UnableToDecryptContent(
data = kind.msg.map()
)
}
}
}
}
private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data {
return when (this) {
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId)
is RustEncryptedMessage.OlmV1Curve25519AesSha2 -> UnableToDecryptContent.Data.OlmV1Curve25519AesSha2(senderKey)
RustEncryptedMessage.Unknown -> UnableToDecryptContent.Data.Unknown
}
}