ganfra
6 months ago
12 changed files with 411 additions and 0 deletions
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.api.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId |
||||||
|
|
||||||
|
data class RoomDescription( |
||||||
|
val roomId: RoomId, |
||||||
|
val name: String?, |
||||||
|
val topic: String?, |
||||||
|
val alias: String?, |
||||||
|
val avatarUrl: String?, |
||||||
|
val joinRule: JoinRule, |
||||||
|
val isWorldReadable: Boolean, |
||||||
|
val joinedMembers: Long |
||||||
|
) { |
||||||
|
enum class JoinRule { |
||||||
|
PUBLIC, |
||||||
|
KNOCK, |
||||||
|
UNKNOWN |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.api.roomdirectory |
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow |
||||||
|
|
||||||
|
interface RoomDirectorySearch { |
||||||
|
suspend fun updateQuery(query: String?, batchSize: Int) |
||||||
|
suspend fun loadMore() |
||||||
|
suspend fun hasMoreToLoad(): Boolean |
||||||
|
val results: SharedFlow<List<RoomDescription>> |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.api.roomdirectory |
||||||
|
|
||||||
|
interface RoomDirectoryService { |
||||||
|
fun search(): RoomDirectorySearch |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId |
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription |
||||||
|
import org.matrix.rustcomponents.sdk.PublicRoomJoinRule |
||||||
|
import org.matrix.rustcomponents.sdk.RoomDescription as RustRoomDescription |
||||||
|
|
||||||
|
class RoomDescriptionMapper { |
||||||
|
|
||||||
|
fun map(roomDescription: RustRoomDescription): RoomDescription { |
||||||
|
return RoomDescription( |
||||||
|
roomId = RoomId(roomDescription.roomId), |
||||||
|
name = roomDescription.name, |
||||||
|
topic = roomDescription.topic, |
||||||
|
avatarUrl = roomDescription.avatarUrl, |
||||||
|
alias = roomDescription.alias, |
||||||
|
joinRule = when (roomDescription.joinRule) { |
||||||
|
PublicRoomJoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC |
||||||
|
PublicRoomJoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK |
||||||
|
null -> RoomDescription.JoinRule.UNKNOWN |
||||||
|
}, |
||||||
|
isWorldReadable = roomDescription.isWorldReadable, |
||||||
|
joinedMembers = roomDescription.joinedMembers.toLong(), |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy |
||||||
|
import kotlinx.coroutines.channels.Channel |
||||||
|
import kotlinx.coroutines.channels.awaitClose |
||||||
|
import kotlinx.coroutines.channels.trySendBlocking |
||||||
|
import kotlinx.coroutines.flow.Flow |
||||||
|
import kotlinx.coroutines.flow.buffer |
||||||
|
import kotlinx.coroutines.flow.callbackFlow |
||||||
|
import kotlinx.coroutines.flow.catch |
||||||
|
import org.matrix.rustcomponents.sdk.RoomDirectorySearch |
||||||
|
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntriesListener |
||||||
|
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate |
||||||
|
import timber.log.Timber |
||||||
|
|
||||||
|
internal fun RoomDirectorySearch.resultsFlow(): Flow<List<RoomDirectorySearchEntryUpdate>> = |
||||||
|
callbackFlow { |
||||||
|
val listener = object : RoomDirectorySearchEntriesListener { |
||||||
|
override fun onUpdate(roomEntriesUpdate: List<RoomDirectorySearchEntryUpdate>) { |
||||||
|
trySendBlocking(roomEntriesUpdate) |
||||||
|
} |
||||||
|
} |
||||||
|
val result = results(listener) |
||||||
|
awaitClose { |
||||||
|
result.cancelAndDestroy() |
||||||
|
} |
||||||
|
}.catch { |
||||||
|
Timber.d(it, "timelineDiffFlow() failed") |
||||||
|
}.buffer(Channel.UNLIMITED) |
@ -0,0 +1,97 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription |
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow |
||||||
|
import kotlinx.coroutines.sync.Mutex |
||||||
|
import kotlinx.coroutines.sync.withLock |
||||||
|
import kotlinx.coroutines.withContext |
||||||
|
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate |
||||||
|
import timber.log.Timber |
||||||
|
import kotlin.coroutines.CoroutineContext |
||||||
|
|
||||||
|
class RoomDirectorySearchProcessor( |
||||||
|
private val roomDescriptions: MutableSharedFlow<List<RoomDescription>>, |
||||||
|
private val coroutineContext: CoroutineContext, |
||||||
|
private val roomDescriptionMapper: RoomDescriptionMapper, |
||||||
|
) { |
||||||
|
|
||||||
|
private val mutex = Mutex() |
||||||
|
|
||||||
|
suspend fun postUpdates(updates: List<RoomDirectorySearchEntryUpdate>) { |
||||||
|
updateRoomDescriptions { |
||||||
|
Timber.v("Update room descriptions from postUpdates (with ${updates.size} items) on ${Thread.currentThread()}") |
||||||
|
updates.forEach { update -> |
||||||
|
applyUpdate(update) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private suspend fun MutableList<RoomDescription>.applyUpdate(update: RoomDirectorySearchEntryUpdate) { |
||||||
|
when (update) { |
||||||
|
is RoomDirectorySearchEntryUpdate.Append -> { |
||||||
|
val roomSummaries = update.values.map(roomDescriptionMapper::map) |
||||||
|
addAll(roomSummaries) |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.PushBack -> { |
||||||
|
val roomSummary = roomDescriptionMapper.map(update.value) |
||||||
|
add(roomSummary) |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.PushFront -> { |
||||||
|
val roomSummary = roomDescriptionMapper.map(update.value) |
||||||
|
add(0, roomSummary) |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.Set -> { |
||||||
|
val roomSummary = roomDescriptionMapper.map(update.value) |
||||||
|
this[update.index.toInt()] = roomSummary |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.Insert -> { |
||||||
|
val roomSummary = roomDescriptionMapper.map(update.value) |
||||||
|
add(update.index.toInt(), roomSummary) |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.Remove -> { |
||||||
|
removeAt(update.index.toInt()) |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.Reset -> { |
||||||
|
clear() |
||||||
|
addAll(update.values.map(roomDescriptionMapper::map)) |
||||||
|
} |
||||||
|
RoomDirectorySearchEntryUpdate.PopBack -> { |
||||||
|
removeLastOrNull() |
||||||
|
} |
||||||
|
RoomDirectorySearchEntryUpdate.PopFront -> { |
||||||
|
removeFirstOrNull() |
||||||
|
} |
||||||
|
RoomDirectorySearchEntryUpdate.Clear -> { |
||||||
|
clear() |
||||||
|
} |
||||||
|
is RoomDirectorySearchEntryUpdate.Truncate -> { |
||||||
|
subList(update.length.toInt(), size).clear() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private suspend fun updateRoomDescriptions(block: suspend MutableList<RoomDescription>.() -> Unit) = withContext(coroutineContext) { |
||||||
|
mutex.withLock { |
||||||
|
val current = roomDescriptions.replayCache.lastOrNull() |
||||||
|
val mutableRoomSummaries = current.orEmpty().toMutableList() |
||||||
|
block(mutableRoomSummaries) |
||||||
|
roomDescriptions.emit(mutableRoomSummaries) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription |
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch |
||||||
|
import kotlinx.coroutines.CoroutineDispatcher |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow |
||||||
|
import kotlinx.coroutines.flow.SharedFlow |
||||||
|
import kotlinx.coroutines.flow.launchIn |
||||||
|
import kotlinx.coroutines.flow.onEach |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySearch |
||||||
|
|
||||||
|
class RustRoomDirectorySearch( |
||||||
|
private val inner: InnerRoomDirectorySearch, |
||||||
|
private val sessionCoroutineScope: CoroutineScope, |
||||||
|
private val sessionDispatcher: CoroutineDispatcher, |
||||||
|
) : RoomDirectorySearch { |
||||||
|
|
||||||
|
private val _results: MutableStateFlow<List<RoomDescription>> = |
||||||
|
MutableStateFlow(emptyList()) |
||||||
|
|
||||||
|
private val processor = RoomDirectorySearchProcessor(_results, sessionDispatcher, RoomDescriptionMapper()) |
||||||
|
|
||||||
|
init { |
||||||
|
sessionCoroutineScope.launch(sessionDispatcher) { |
||||||
|
inner |
||||||
|
.resultsFlow() |
||||||
|
.onEach { updates -> |
||||||
|
processor.postUpdates(updates) |
||||||
|
} |
||||||
|
.launchIn(this) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override suspend fun updateQuery(query: String?, batchSize: Int) { |
||||||
|
inner.search(query, batchSize.toUInt()) |
||||||
|
} |
||||||
|
|
||||||
|
override suspend fun loadMore() { |
||||||
|
inner.nextPage() |
||||||
|
} |
||||||
|
|
||||||
|
override suspend fun hasMoreToLoad(): Boolean { |
||||||
|
return !inner.isAtLastPage() |
||||||
|
} |
||||||
|
|
||||||
|
override val results: SharedFlow<List<RoomDescription>> = _results |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch |
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService |
||||||
|
import kotlinx.coroutines.CoroutineDispatcher |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import org.matrix.rustcomponents.sdk.Client |
||||||
|
|
||||||
|
class RustRoomDirectoryService( |
||||||
|
private val client: Client, |
||||||
|
private val sessionCoroutineScope: CoroutineScope, |
||||||
|
private val sessionDispatcher: CoroutineDispatcher, |
||||||
|
) : RoomDirectoryService { |
||||||
|
|
||||||
|
override fun search(): RoomDirectorySearch { |
||||||
|
return RustRoomDirectorySearch(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.test.roomdirectory |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch |
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService |
||||||
|
|
||||||
|
class FakeRoomDirectoryService : RoomDirectoryService { |
||||||
|
override fun search(): RoomDirectorySearch { |
||||||
|
TODO("Not yet implemented") |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue