Merge branch 'develop' into feature/fga/image_loading

This commit is contained in:
ganfra 2023-05-15 20:07:00 +02:00
commit 4b49d40801
154 changed files with 2161 additions and 832 deletions

View file

@ -14,6 +14,8 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.libraries.matrix.impl
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@ -44,7 +46,9 @@ import io.element.android.libraries.matrix.impl.verification.RustSessionVerifica
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
@ -54,7 +58,9 @@ import kotlinx.coroutines.withTimeout
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.SlidingSyncList
import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder
import org.matrix.rustcomponents.sdk.SlidingSyncListOnceBuilt
import org.matrix.rustcomponents.sdk.SlidingSyncMode
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
import org.matrix.rustcomponents.sdk.TaskHandle
@ -81,7 +87,7 @@ class RustMatrixClient constructor(
client = client,
dispatchers = dispatchers,
)
private val notificationService = RustNotificationService(baseDirectory, dispatchers)
private val notificationService = RustNotificationService(client)
private var slidingSyncUpdateJob: Job? = null
private val clientDelegate = object : ClientDelegate {
@ -104,7 +110,8 @@ class RustMatrixClient constructor(
notTags = emptyList()
)
private val visibleRoomsSlidingSyncList = SlidingSyncListBuilder()
private val visibleRoomsSlidingSyncList = MutableSharedFlow<SlidingSyncList>(replay = 1)
private val visibleRoomsSlidingSyncListBuilder = SlidingSyncListBuilder("CurrentlyVisibleRooms")
.timelineLimit(limit = 1u)
.requiredState(
requiredState = listOf(
@ -114,16 +121,19 @@ class RustMatrixClient constructor(
)
)
.filters(visibleRoomsSlidingSyncFilters)
.name(name = "CurrentlyVisibleRooms")
.syncMode(mode = SlidingSyncMode.SELECTIVE)
.addRange(0u, 20u)
.use {
it.build()
}
.onceBuilt(object : SlidingSyncListOnceBuilt {
override fun updateList(list: SlidingSyncList): SlidingSyncList {
visibleRoomsSlidingSyncList.tryEmit(list)
return list
}
})
private val invitesSlidingSyncFilters = visibleRoomsSlidingSyncFilters.copy(isInvite = true)
private val invitesSlidingSyncList = SlidingSyncListBuilder()
private val invitesSlidingSyncList = MutableSharedFlow<SlidingSyncList>(replay = 1)
private val invitesSlidingSyncListBuilder = SlidingSyncListBuilder("CurrentInvites")
.timelineLimit(limit = 1u)
.requiredState(
requiredState = listOf(
@ -133,20 +143,22 @@ class RustMatrixClient constructor(
)
)
.filters(invitesSlidingSyncFilters)
.name(name = "CurrentInvites")
.syncMode(mode = SlidingSyncMode.SELECTIVE)
.addRange(0u, 20u)
.use {
it.build()
}
.onceBuilt(object : SlidingSyncListOnceBuilt {
override fun updateList(list: SlidingSyncList): SlidingSyncList {
invitesSlidingSyncList.tryEmit(list)
return list
}
})
private val slidingSync = client
.slidingSync()
.homeserver("https://slidingsync.lab.matrix.org")
.withCommonExtensions()
.storageKey("ElementX")
.addList(visibleRoomsSlidingSyncList)
.addList(invitesSlidingSyncList)
.addList(visibleRoomsSlidingSyncListBuilder)
.addList(invitesSlidingSyncListBuilder)
.use {
it.build()
}
@ -158,7 +170,6 @@ class RustMatrixClient constructor(
slidingSync,
visibleRoomsSlidingSyncList,
dispatchers,
::onRestartSync
)
override val roomSummaryDataSource: RoomSummaryDataSource
@ -170,7 +181,6 @@ class RustMatrixClient constructor(
slidingSync,
invitesSlidingSyncList,
dispatchers,
::onRestartSync
)
override val invitesDataSource: RoomSummaryDataSource
@ -196,11 +206,6 @@ class RustMatrixClient constructor(
.launchIn(coroutineScope)
}
private fun onRestartSync() {
stopSync()
startSync()
}
override fun getRoom(roomId: RoomId): MatrixRoom? {
val slidingSyncRoom = slidingSync.getRoom(roomId.value) ?: return null
val fullRoom = slidingSyncRoom.fullRoom() ?: return null
@ -304,7 +309,10 @@ class RustMatrixClient constructor(
rustRoomSummaryDataSource.close()
rustInvitesDataSource.close()
client.setDelegate(null)
visibleRoomsSlidingSyncList.destroy()
visibleRoomsSlidingSyncListBuilder.destroy()
invitesSlidingSyncListBuilder.destroy()
visibleRoomsSlidingSyncList.resetReplayCache()
invitesSlidingSyncList.resetReplayCache()
slidingSync.destroy()
verificationService.destroy()
client.destroy()

View file

@ -21,5 +21,12 @@ import org.matrix.rustcomponents.sdk.AudioInfo as RustAudioInfo
fun RustAudioInfo.map(): AudioInfo = AudioInfo(
duration = duration?.toLong(),
size = size?.toLong()
size = size?.toLong(),
mimeType = mimetype
)
fun AudioInfo.map(): RustAudioInfo = RustAudioInfo(
duration = duration?.toULong(),
size = size?.toULong(),
mimetype = mimeType,
)

View file

@ -25,3 +25,10 @@ fun RustFileInfo.map(): FileInfo = FileInfo(
thumbnailInfo = thumbnailInfo?.map(),
thumbnailSource = thumbnailSource?.map()
)
fun FileInfo.map(): RustFileInfo = RustFileInfo(
mimetype = mimetype,
size = size?.toULong(),
thumbnailInfo = thumbnailInfo?.map(),
thumbnailSource = null
)

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.ImageInfo
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.ImageInfo as RustImageInfo
fun RustImageInfo.map(): ImageInfo = ImageInfo(
@ -28,3 +29,13 @@ fun RustImageInfo.map(): ImageInfo = ImageInfo(
thumbnailSource = thumbnailSource?.map(),
blurhash = blurhash
)
fun ImageInfo.map(): RustImageInfo = RustImageInfo(
height = height?.toULong(),
width = width?.toULong(),
mimetype = mimetype,
size = size?.toULong(),
thumbnailInfo = thumbnailInfo?.map(),
thumbnailSource = null,
blurhash = blurhash,
)

View file

@ -64,6 +64,7 @@ class RustMediaLoader(
mediaSourceFromUrl(source.url).use { mediaSource ->
val mediaFile = innerClient.getMediaFile(
mediaSource = mediaSource,
body = null,
mimeType = mimeType ?: "application/octet-stream"
)
RustMediaFile(mediaFile)

View file

@ -25,3 +25,10 @@ fun RustThumbnailInfo.map(): ThumbnailInfo = ThumbnailInfo(
mimetype = mimetype,
size = size?.toLong()
)
fun ThumbnailInfo.map(): RustThumbnailInfo = RustThumbnailInfo(
height = height?.toULong(),
width = width?.toULong(),
mimetype = mimetype,
size = size?.toULong()
)

View file

@ -29,3 +29,14 @@ fun RustVideoInfo.map(): VideoInfo = VideoInfo(
thumbnailSource = thumbnailSource?.map(),
blurhash = blurhash
)
fun VideoInfo.map(): RustVideoInfo = RustVideoInfo(
duration = duration?.toULong(),
height = height?.toULong(),
width = width?.toULong(),
mimetype = mimetype,
size = size?.toULong(),
thumbnailInfo = thumbnailInfo?.map(),
thumbnailSource = null,
blurhash = blurhash
)

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@ -36,7 +37,7 @@ class NotificationMapper @Inject constructor() {
senderDisplayName = it.senderDisplayName,
roomAvatarUrl = it.roomAvatarUrl,
isDirect = it.isDirect,
isEncrypted = it.isEncrypted,
isEncrypted = it.isEncrypted.orFalse(),
isNoisy = it.isNoisy
)
}

View file

@ -17,33 +17,30 @@
package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.use
import java.io.File
class RustNotificationService(
private val baseDirectory: File,
private val dispatchers: CoroutineDispatchers,
private val client: Client,
) : NotificationService {
private val notificationMapper: NotificationMapper = NotificationMapper()
override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?> {
return withContext(dispatchers.io) {
runCatching {
org.matrix.rustcomponents.sdk.NotificationService(
basePath = File(baseDirectory, "sessions").absolutePath,
userId = userId.value
).use {
// TODO Not implemented yet, see https://github.com/matrix-org/matrix-rust-sdk/issues/1628
it.getNotificationItem(roomId.value, eventId.value)?.let { notificationItem ->
notificationMapper.map(notificationItem)
}
}
}
override fun getNotification(
userId: SessionId,
roomId: RoomId,
eventId: EventId
): Result<NotificationData?> {
return runCatching {
client.getNotificationItem(roomId.value, eventId.value).use(notificationMapper::map)
}
}
}

View file

@ -24,17 +24,18 @@ import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember
object RoomMemberMapper {
fun map(roomMember: RustRoomMember): RoomMember =
fun map(roomMember: RustRoomMember): RoomMember = roomMember.use {
RoomMember(
UserId(roomMember.userId()),
roomMember.displayName(),
roomMember.avatarUrl(),
mapMembership(roomMember.membership()),
roomMember.isNameAmbiguous(),
roomMember.powerLevel(),
roomMember.normalizedPowerLevel(),
roomMember.isIgnored(),
UserId(it.userId()),
it.displayName(),
it.avatarUrl(),
mapMembership(it.membership()),
it.isNameAmbiguous(),
it.powerLevel(),
it.normalizedPowerLevel(),
it.isIgnored(),
)
}
fun mapMembership(membershipState: RustMembershipState): RoomMembershipState =
when (membershipState) {

View file

@ -21,10 +21,15 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@ -33,13 +38,13 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.onSubscription
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import java.io.File
class RustMatrixRoom(
override val sessionId: SessionId,
@ -156,9 +161,10 @@ class RustMatrixRoom(
override suspend fun sendMessage(message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
val transactionId = genTransactionId()
val content = messageEventContentFromMarkdown(message)
runCatching {
innerRoom.send(content, transactionId)
messageEventContentFromMarkdown(message).use { content ->
runCatching {
innerRoom.send(content, transactionId)
}
}
}
@ -203,4 +209,28 @@ class RustMatrixRoom(
}
}
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result<Unit> {
return runCatching {
innerRoom.sendImage(file.path, thumbnailFile.path, imageInfo.map())
}
}
override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result<Unit> {
return runCatching {
innerRoom.sendVideo(file.path, thumbnailFile.path, videoInfo.map())
}
}
override suspend fun sendAudio(file: File, audioInfo: AudioInfo): Result<Unit> {
return runCatching {
innerRoom.sendAudio(file.path, audioInfo.map())
}
}
override suspend fun sendFile(file: File, fileInfo: FileInfo): Result<Unit> {
return runCatching {
innerRoom.sendFile(file.path, fileInfo.map())
}
}
}

View file

@ -27,9 +27,12 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.SlidingSync
@ -44,9 +47,8 @@ import java.util.UUID
internal class RustRoomSummaryDataSource(
private val slidingSyncUpdateFlow: Flow<UpdateSummary>,
private val slidingSync: SlidingSync,
private val slidingSyncList: SlidingSyncList,
private val slidingSyncListFlow: Flow<SlidingSyncList>,
private val coroutineDispatchers: CoroutineDispatchers,
private val onRestartSync: () -> Unit,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) : RoomSummaryDataSource, Closeable {
@ -57,34 +59,35 @@ internal class RustRoomSummaryDataSource(
fun init() {
coroutineScope.launch {
val slidingSyncList = slidingSyncListFlow.first()
val summaries = slidingSyncList.currentRoomList().map(::buildSummaryForRoomListEntry)
updateRoomSummaries {
addAll(
slidingSyncList.currentRoomList().map(::buildSummaryForRoomListEntry)
)
addAll(summaries)
}
slidingSyncList.roomListDiff(this)
.onEach { diffs ->
updateRoomSummaries {
applyDiff(diffs)
}
}
.launchIn(this)
slidingSyncList.state(this)
.onEach { slidingSyncState ->
Timber.v("New sliding sync state: $slidingSyncState")
state.value = slidingSyncState
}.launchIn(this)
}
slidingSyncUpdateFlow
.onEach {
didReceiveSyncUpdate(it)
}.launchIn(coroutineScope)
slidingSyncList.roomListDiff(coroutineScope)
.onEach { diffs ->
updateRoomSummaries {
applyDiff(diffs)
}
}
.launchIn(coroutineScope)
slidingSyncList.state(coroutineScope)
.onEach { slidingSyncState ->
Timber.v("New sliding sync state: $slidingSyncState")
state.value = slidingSyncState
}.launchIn(coroutineScope)
}
override fun close() {
runBlocking { slidingSyncListFlow.firstOrNull() }?.close()
coroutineScope.cancel()
}
@ -95,8 +98,9 @@ internal class RustRoomSummaryDataSource(
override fun setSlidingSyncRange(range: IntRange) {
Timber.v("setVisibleRange=$range")
slidingSyncList.setRange(range.first.toUInt(), range.last.toUInt())
onRestartSync()
coroutineScope.launch {
slidingSyncListFlow.first().setRange(range.first.toUInt(), range.last.toUInt())
}
}
private suspend fun didReceiveSyncUpdate(summary: UpdateSummary) {

View file

@ -27,12 +27,12 @@ class MatrixTimelineItemMapper(
) {
fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use {
val asEvent = timelineItem.asEvent()
val asEvent = it.asEvent()
if (asEvent != null) {
val eventTimelineItem = eventTimelineItemMapper.map(asEvent)
return MatrixTimelineItem.Event(eventTimelineItem)
}
val asVirtual = timelineItem.asVirtual()
val asVirtual = it.asVirtual()
if (asVirtual != null) {
val virtualTimelineItem = virtualTimelineItemMapper.map(asVirtual)
return MatrixTimelineItem.Virtual(virtualTimelineItem)

View file

@ -38,7 +38,7 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat
class EventMessageMapper {
fun map(message: Message): MessageContent = message.use {
val type = message.msgtype().use { type ->
val type = it.msgtype().use { type ->
when (type) {
is MessageType.Audio -> {
AudioMessageType(type.content.body, type.content.source.map(), type.content.info?.map())
@ -67,9 +67,9 @@ class EventMessageMapper {
}
}
MessageContent(
body = message.body(),
inReplyTo = message.inReplyTo()?.eventId?.let(::EventId),
isEdited = message.isEdited(),
body = it.body(),
inReplyTo = it.inReplyTo()?.eventId?.let(::EventId),
isEdited = it.isEdited(),
type = type
)
}

View file

@ -31,18 +31,18 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use {
EventTimelineItem(
uniqueIdentifier = eventTimelineItem.uniqueIdentifier(),
eventId = eventTimelineItem.eventId()?.let { EventId(it) },
isEditable = eventTimelineItem.isEditable(),
isLocal = eventTimelineItem.isLocal(),
isOwn = eventTimelineItem.isOwn(),
isRemote = eventTimelineItem.isRemote(),
localSendState = eventTimelineItem.localSendState()?.map(),
reactions = eventTimelineItem.reactions().map(),
sender = UserId(eventTimelineItem.sender()),
senderProfile = eventTimelineItem.senderProfile().map(),
timestamp = eventTimelineItem.timestamp().toLong(),
content = contentMapper.map(eventTimelineItem.content())
uniqueIdentifier = it.uniqueIdentifier(),
eventId = it.eventId()?.let { EventId(it) },
isEditable = it.isEditable(),
isLocal = it.isLocal(),
isOwn = it.isOwn(),
isRemote = it.isRemote(),
localSendState = it.localSendState()?.map(),
reactions = it.reactions().map(),
sender = UserId(it.sender()),
senderProfile = it.senderProfile().map(),
timestamp = it.timestamp().toLong(),
content = contentMapper.map(it.content())
)
}
}

View file

@ -39,7 +39,7 @@ import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) {
fun map(content: TimelineItemContent): EventContent = content.use {
when (val kind = content.kind()) {
when (val kind = it.kind()) {
is TimelineItemContentKind.FailedToParseMessageLike -> {
FailedToParseMessageLikeContent(
eventType = kind.eventType,
@ -54,7 +54,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
)
}
TimelineItemContentKind.Message -> {
val message = content.asMessage()
val message = it.asMessage()
if (message == null) {
UnknownContent
} else {