From dc4786ecf0b4f5719b8e9562090f5c0921618c68 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 30 Jul 2019 19:13:09 +0200 Subject: [PATCH] Room upgrade: add rx flux and handle failures more precisely --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 4 +++ .../java/im/vector/matrix/rx/RxSession.kt | 4 +++ .../android/api/session/room/RoomService.kt | 2 +- .../session/room/failure/CreateRoomFailure.kt | 25 +++++++++++++++ .../session/room/failure/JoinRoomFailure.kt | 25 +++++++++++++++ .../internal/database/RealmQueryLatch.kt | 32 +++++++++---------- .../session/room/DefaultRoomService.kt | 4 +-- .../session/room/create/CreateRoomTask.kt | 10 ++++-- .../room/membership/joining/JoinRoomTask.kt | 10 ++++-- .../vector/riotx/core/error/ErrorFormatter.kt | 8 +++-- .../createdirect/CreateDirectRoomActivity.kt | 13 +++++--- .../home/room/detail/RoomDetailFragment.kt | 3 +- .../home/room/detail/RoomDetailViewModel.kt | 21 +++++------- vector/src/main/res/values/strings_riotX.xml | 2 ++ 14 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/CreateRoomFailure.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/JoinRoomFailure.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 419fb6c913..e62b7df57f 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -45,6 +45,10 @@ class RxRoom(private val room: Room) { room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it) } + fun joinRoom(viaServers: List = emptyList()): Single = Single.create { + room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it) + } + } fun Room.rx(): RxRoom { diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 97661cebd1..f3fb06a45a 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -63,6 +63,10 @@ class RxSession(private val session: Session) { session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it) } + fun joinRoom(roomId: String, viaServers: List = emptyList()): Single = Single.create { + session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it) + } + } fun Session.rx(): RxSession { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index b3f060d706..7ec50bd2ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -39,7 +39,7 @@ interface RoomService { */ fun joinRoom(roomId: String, viaServers: List = emptyList(), - callback: MatrixCallback) + callback: MatrixCallback): Cancelable /** * Get a room from a roomId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/CreateRoomFailure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/CreateRoomFailure.kt new file mode 100644 index 0000000000..086dc621ca --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/CreateRoomFailure.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019 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 im.vector.matrix.android.api.session.room.failure + +import im.vector.matrix.android.api.failure.Failure + +sealed class CreateRoomFailure : Failure.FeatureFailure() { + + object CreatedWithTimeout: CreateRoomFailure() + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/JoinRoomFailure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/JoinRoomFailure.kt new file mode 100644 index 0000000000..4c7dd62ad6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/failure/JoinRoomFailure.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019 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 im.vector.matrix.android.api.session.room.failure + +import im.vector.matrix.android.api.failure.Failure + +sealed class JoinRoomFailure : Failure.FeatureFailure() { + + object JoinedWithTimeout : JoinRoomFailure() + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt index 1fc60d8098..3e3ffad45c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt @@ -16,45 +16,45 @@ package im.vector.matrix.android.internal.database -import android.os.Handler -import android.os.HandlerThread +import im.vector.matrix.android.internal.util.createBackgroundHandler import io.realm.* -import timber.log.Timber import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference -private const val THREAD_NAME = "REALM_QUERY_LATCH" class RealmQueryLatch(private val realmConfiguration: RealmConfiguration, private val realmQueryBuilder: (Realm) -> RealmQuery) { - @Throws(InterruptedException::class) - fun await(timeout: Long = Long.MAX_VALUE, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) { - val latch = CountDownLatch(1) - val handlerThread = HandlerThread(THREAD_NAME + hashCode()) - handlerThread.start() - val handler = Handler(handlerThread.looper) - val runnable = Runnable { - val realm = Realm.getInstance(realmConfiguration) - val result = realmQueryBuilder(realm).findAllAsync() + private companion object { + val QUERY_LATCH_HANDLER = createBackgroundHandler("REALM_QUERY_LATCH") + } + @Throws(InterruptedException::class) + fun await(timeout: Long, timeUnit: TimeUnit) { + val realmRef = AtomicReference() + val latch = CountDownLatch(1) + QUERY_LATCH_HANDLER.post { + val realm = Realm.getInstance(realmConfiguration) + realmRef.set(realm) + val result = realmQueryBuilder(realm).findAllAsync() result.addChangeListener(object : RealmChangeListener> { override fun onChange(t: RealmResults) { if (t.isNotEmpty()) { result.removeChangeListener(this) - realm.close() latch.countDown() } } }) } - handler.post(runnable) try { latch.await(timeout, timeUnit) } catch (exception: InterruptedException) { throw exception } finally { - handlerThread.quit() + QUERY_LATCH_HANDLER.post { + realmRef.getAndSet(null).close() + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 78c19af3e2..5a7f82b04b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -67,8 +67,8 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona ) } - override fun joinRoom(roomId: String, viaServers: List, callback: MatrixCallback) { - joinRoomTask + override fun joinRoom(roomId: String, viaServers: List, callback: MatrixCallback): Cancelable { + return joinRoomTask .configureWith(JoinRoomTask.Params(roomId, viaServers)) .dispatchTo(callback) .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index f9cad783a6..6091f6b96c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -17,7 +17,9 @@ package im.vector.matrix.android.internal.session.room.create import arrow.core.Try +import arrow.core.recoverWith import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse import im.vector.matrix.android.internal.database.RealmQueryLatch @@ -57,9 +59,11 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro realm.where(RoomEntity::class.java) .equalTo(RoomEntityFields.ROOM_ID, roomId) } - Try { - rql.await(timeout = 20L, timeUnit = TimeUnit.SECONDS) - roomId + try { + rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES) + Try.just(roomId) + } catch (exception: Exception) { + Try.raise(CreateRoomFailure.CreatedWithTimeout) } }.flatMap { roomId -> if (params.isDirect()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index a8e43236b4..bc76a211fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -17,6 +17,8 @@ package im.vector.matrix.android.internal.session.room.membership.joining import arrow.core.Try +import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure +import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure import im.vector.matrix.android.internal.database.RealmQueryLatch import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntityFields @@ -50,9 +52,11 @@ internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: Room realm.where(RoomEntity::class.java) .equalTo(RoomEntityFields.ROOM_ID, roomId) } - Try { - rql.await(20L, TimeUnit.SECONDS) - roomId + try { + rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES) + Try.just(roomId) + } catch (exception: Exception) { + Try.raise(JoinRoomFailure.JoinedWithTimeout) } }.flatMap { roomId -> setReadMarkers(roomId) diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index 7619d433d7..9d478f34e8 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -30,12 +30,16 @@ class ErrorFormatter @Inject constructor(val stringProvider: StringProvider) { } fun toHumanReadable(throwable: Throwable?): String { - return when (throwable) { null -> "" is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network) + is Failure.ServerError -> { + throwable.error.message.takeIf { it.isNotEmpty() } + ?: throwable.error.code.takeIf { it.isNotEmpty() } + ?: stringProvider.getString(R.string.unknown_error) + } else -> throwable.localizedMessage + ?: stringProvider.getString(R.string.unknown_error) } - } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt index 13bc93686f..8c40e5691d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt @@ -29,6 +29,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.viewModel +import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter @@ -38,6 +39,7 @@ import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData import kotlinx.android.synthetic.main.activity.* +import timber.log.Timber import javax.inject.Inject class CreateDirectRoomActivity : SimpleFragmentActivity() { @@ -91,10 +93,13 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private fun renderCreationFailure(error: Throwable) { hideWaitingView() - AlertDialog.Builder(this) - .setMessage(errorFormatter.toHumanReadable(error)) - .setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() } - .show() + if (error is CreateRoomFailure.CreatedWithTimeout) { + finish() + } else + AlertDialog.Builder(this) + .setMessage(errorFormatter.toHumanReadable(error)) + .setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() } + .show() } private fun renderCreationSuccess(roomId: String?) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 738b726c29..1742b405f3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -65,6 +65,7 @@ import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.* @@ -608,7 +609,7 @@ class RoomDetailFragment : // TODO Better handling progress vectorBaseActivity.showWaitingView() vectorBaseActivity.waiting_view_status_text.visibility = View.VISIBLE - vectorBaseActivity.waiting_view_status_text.text = getString(R.string.join) + vectorBaseActivity.waiting_view_status_text.text = getString(R.string.joining_room) } is Success -> { navigator.openRoom(vectorBaseActivity, async()) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 983851ffc7..304e09f883 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -140,8 +140,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro ?: return val roomId = tombstoneContent.replacementRoom - // TODO replace with rx flux - if (session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN) { + val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN + if (isRoomJoined) { setState { copy(tombstoneEventHandling = Success(roomId)) } } else { val viaServer = MatrixPatterns.extractServerNameFromId(action.event.senderId).let { @@ -151,17 +151,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro listOf(it) } } - setState { copy(tombstoneEventHandling = Loading()) } - session.joinRoom(roomId, viaServer, object : MatrixCallback { - override fun onSuccess(data: Unit) { - setState { copy(tombstoneEventHandling = Success(roomId)) } - - } - - override fun onFailure(failure: Throwable) { - setState { copy(tombstoneEventHandling = Fail(failure)) } - } - }) + session.rx() + .joinRoom(roomId, viaServer) + .map { roomId } + .execute { + copy(tombstoneEventHandling = it) + } } } diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 45dc5c5308..6bcec316dd 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -7,4 +7,6 @@ "No result found, use Add by matrix ID to search on server." "Start typing to get results" "Filter by username or ID…" + + "Joining room…" \ No newline at end of file