Rework some MatrixRoom api and fix rust 'destroyed' crash

This commit is contained in:
ganfra 2023-07-25 12:06:36 +02:00
parent 0ef49ca6ca
commit daf23c5541
6 changed files with 76 additions and 71 deletions

View file

@ -20,6 +20,7 @@ import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
@ -161,13 +162,16 @@ class RoomLoadedFlowNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
// Rely on the View Lifecycle instead of the Node Lifecycle, // Rely on the View Lifecycle in addition to the Node Lifecycle,
// because this node enters 'onDestroy' before his children, so it can leads to // because this node enters 'onDestroy' before his children, so it can leads to
// using the room in a child node where it's already closed. // using the room in a child node where it's already closed.
DisposableEffect(Unit) { DisposableEffect(Unit) {
inputs.room.open() inputs.room.subscribeToSync()
onDispose { onDispose {
inputs.room.close() inputs.room.unsubscribeFromSync()
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
inputs.room.destroy()
}
} }
} }
Children( Children(

View file

@ -63,7 +63,11 @@ interface MatrixRoom : Closeable {
val timeline: MatrixTimeline val timeline: MatrixTimeline
fun open(): Result<Unit> fun destroy()
fun subscribeToSync()
fun unsubscribeFromSync()
suspend fun userDisplayName(userId: UserId): Result<String?> suspend fun userDisplayName(userId: UserId): Result<String?>
@ -133,6 +137,8 @@ interface MatrixRoom : Closeable {
zoomLevel: Int? = null, zoomLevel: Int? = null,
assetType: AssetType? = null, assetType: AssetType? = null,
): Result<Unit> ): Result<Unit>
override fun close() = destroy()
} }

View file

@ -40,9 +40,6 @@ import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.room.location.toInner
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.backPaginationStatusFlow
import io.element.android.libraries.matrix.impl.timeline.eventOrigin
import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow
import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -51,11 +48,7 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.EventItemOrigin
import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListItem
@ -65,7 +58,6 @@ import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.util.concurrent.atomic.AtomicBoolean
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class RustMatrixRoom( class RustMatrixRoom(
@ -89,7 +81,6 @@ class RustMatrixRoom(
private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId") private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId")
private val _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown) private val _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown)
private val isInit = AtomicBoolean(false)
private val _syncUpdateFlow = MutableStateFlow(0L) private val _syncUpdateFlow = MutableStateFlow(0L)
private val _timeline by lazy { private val _timeline by lazy {
RustMatrixTimeline( RustMatrixTimeline(
@ -98,6 +89,7 @@ class RustMatrixRoom(
roomCoroutineScope = roomCoroutineScope, roomCoroutineScope = roomCoroutineScope,
dispatcher = roomDispatcher, dispatcher = roomDispatcher,
lastLoginTimestamp = sessionData.loginTimestamp, lastLoginTimestamp = sessionData.loginTimestamp,
onNewSyncedEvent = { _syncUpdateFlow.value = systemClock.epochMillis() }
) )
} }
@ -107,48 +99,27 @@ class RustMatrixRoom(
override val timeline: MatrixTimeline = _timeline override val timeline: MatrixTimeline = _timeline
override fun open(): Result<Unit> { override fun subscribeToSync() {
return if (isInit.getAndSet(true)) { val settings = RoomSubscription(
Result.failure(IllegalStateException("Listener already registered")) requiredState = listOf(
} else { RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
val settings = RoomSubscription( RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""),
requiredState = listOf( RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""),
RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""), ),
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""), timelineLimit = null
RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""), )
), roomListItem.subscribe(settings)
timelineLimit = null
)
roomListItem.subscribe(settings)
roomCoroutineScope.launch(roomDispatcher) {
innerRoom.timelineDiffFlow { initialList ->
_timeline.postItems(initialList)
}.onEach { diff ->
if (diff.eventOrigin() == EventItemOrigin.SYNC) {
_syncUpdateFlow.value = systemClock.epochMillis()
}
_timeline.postDiff(diff)
}.launchIn(this)
innerRoom.backPaginationStatusFlow()
.onEach {
_timeline.postPaginationStatus(it)
}.launchIn(this)
fetchMembers()
}
Result.success(Unit)
}
} }
override fun close() { override fun unsubscribeFromSync() {
if (isInit.getAndSet(false)) { roomListItem.unsubscribe()
roomCoroutineScope.cancel() }
roomListItem.unsubscribe()
innerRoom.destroy() override fun destroy() {
roomListItem.destroy() roomCoroutineScope.cancel()
} innerRoom.destroy()
roomListItem.destroy()
} }
override val name: String? override val name: String?
@ -365,12 +336,6 @@ class RustMatrixRoom(
} }
} }
private suspend fun fetchMembers() = withContext(roomDispatcher) {
runCatching {
innerRoom.fetchMembers()
}
}
override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit> = withContext(roomDispatcher) { override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit> = withContext(roomDispatcher) {
runCatching { runCatching {
innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason) innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason)

View file

@ -26,8 +26,8 @@ import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessage
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import kotlinx.coroutines.CompletableDeferred
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -37,17 +37,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.BackPaginationStatus import org.matrix.rustcomponents.sdk.BackPaginationStatus
import org.matrix.rustcomponents.sdk.EventItemOrigin
import org.matrix.rustcomponents.sdk.PaginationOptions import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import java.util.Date import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
private const val INITIAL_MAX_SIZE = 50 private const val INITIAL_MAX_SIZE = 50
@ -57,6 +61,7 @@ class RustMatrixTimeline(
private val innerRoom: Room, private val innerRoom: Room,
private val dispatcher: CoroutineDispatcher, private val dispatcher: CoroutineDispatcher,
private val lastLoginTimestamp: Date?, private val lastLoginTimestamp: Date?,
private val onNewSyncedEvent: () -> Unit,
) : MatrixTimeline { ) : MatrixTimeline {
private val initLatch = CompletableDeferred<Unit>() private val initLatch = CompletableDeferred<Unit>()
@ -93,13 +98,40 @@ class RustMatrixTimeline(
override val paginationState: StateFlow<MatrixTimeline.PaginationState> = _paginationState.asStateFlow() override val paginationState: StateFlow<MatrixTimeline.PaginationState> = _paginationState.asStateFlow()
init {
Timber.d("Initialize timeline for room ${matrixRoom.roomId}")
roomCoroutineScope.launch(dispatcher) {
innerRoom.timelineDiffFlow { initialList ->
postItems(initialList)
}.onEach { diff ->
if (diff.eventOrigin() == EventItemOrigin.SYNC) {
onNewSyncedEvent()
}
postDiff(diff)
}.launchIn(this)
innerRoom.backPaginationStatusFlow()
.onEach {
postPaginationStatus(it)
}.launchIn(this)
fetchMembers()
}
}
private suspend fun fetchMembers() = withContext(dispatcher) {
runCatching {
innerRoom.fetchMembers()
}
}
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
override val timelineItems: Flow<List<MatrixTimelineItem>> = _timelineItems.sample(50) override val timelineItems: Flow<List<MatrixTimelineItem>> = _timelineItems.sample(50)
.mapLatest { items -> .mapLatest { items ->
encryptedHistoryPostProcessor.process(items) encryptedHistoryPostProcessor.process(items)
} }
internal suspend fun postItems(items: List<TimelineItem>) { private suspend fun postItems(items: List<TimelineItem>) {
// Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap. // Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap.
items.chunked(INITIAL_MAX_SIZE).reversed().forEach { items.chunked(INITIAL_MAX_SIZE).reversed().forEach {
timelineDiffProcessor.postItems(it) timelineDiffProcessor.postItems(it)
@ -108,12 +140,12 @@ class RustMatrixTimeline(
initLatch.complete(Unit) initLatch.complete(Unit)
} }
internal suspend fun postDiff(timelineDiff: TimelineDiff) { private suspend fun postDiff(timelineDiff: TimelineDiff) {
initLatch.await() initLatch.await()
timelineDiffProcessor.postDiff(timelineDiff) timelineDiffProcessor.postDiff(timelineDiff)
} }
internal fun postPaginationStatus(status: BackPaginationStatus) { private fun postPaginationStatus(status: BackPaginationStatus) {
_paginationState.getAndUpdate { currentPaginationState -> _paginationState.getAndUpdate { currentPaginationState ->
if (hasEncryptionHistoryBanner()) { if (hasEncryptionHistoryBanner()) {
return@getAndUpdate currentPaginationState.copy( return@getAndUpdate currentPaginationState.copy(

View file

@ -100,7 +100,6 @@ class FakeMatrixRoom(
private val _sentLocations = mutableListOf<SendLocationInvocation>() private val _sentLocations = mutableListOf<SendLocationInvocation>()
val sentLocations: List<SendLocationInvocation> = _sentLocations val sentLocations: List<SendLocationInvocation> = _sentLocations
var invitedUserId: UserId? = null var invitedUserId: UserId? = null
private set private set
@ -128,9 +127,11 @@ class FakeMatrixRoom(
override val timeline: MatrixTimeline = matrixTimeline override val timeline: MatrixTimeline = matrixTimeline
override fun open(): Result<Unit> { override fun subscribeToSync() = Unit
return Result.success(Unit)
} override fun unsubscribeFromSync() = Unit
override fun destroy() = Unit
override suspend fun userDisplayName(userId: UserId): Result<String?> = simulateLongTask { override suspend fun userDisplayName(userId: UserId): Result<String?> = simulateLongTask {
userDisplayNameResult userDisplayNameResult
@ -283,8 +284,6 @@ class FakeMatrixRoom(
return sendLocationResult return sendLocationResult
} }
override fun close() = Unit
fun givenLeaveRoomError(throwable: Throwable?) { fun givenLeaveRoomError(throwable: Throwable?) {
this.leaveRoomError = throwable this.leaveRoomError = throwable
} }

View file

@ -87,7 +87,6 @@ class RoomListScreen(
Singleton.appScope.launch { Singleton.appScope.launch {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
matrixClient.getRoom(roomId)!!.use { room -> matrixClient.getRoom(roomId)!!.use { room ->
room.open()
room.timeline.paginateBackwards(20, 50) room.timeline.paginateBackwards(20, 50)
} }
} }