Room upgrade: add rx flux and handle failures more precisely

This commit is contained in:
ganfra 2019-07-30 19:13:09 +02:00
parent f4df27c2dc
commit dc4786ecf0
14 changed files with 118 additions and 45 deletions

View file

@ -45,6 +45,10 @@ class RxRoom(private val room: Room) {
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
}
fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
}
}
fun Room.rx(): RxRoom {

View file

@ -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<String> = emptyList()): Single<Unit> = Single.create {
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
}
}
fun Session.rx(): RxSession {

View file

@ -39,7 +39,7 @@ interface RoomService {
*/
fun joinRoom(roomId: String,
viaServers: List<String> = emptyList(),
callback: MatrixCallback<Unit>)
callback: MatrixCallback<Unit>): Cancelable
/**
* Get a room from a roomId

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {
@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<Realm>()
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<RealmResults<E>> {
override fun onChange(t: RealmResults<E>) {
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()
}
}
}

View file

@ -67,8 +67,8 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
)
}
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>) {
joinRoomTask
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
return joinRoomTask
.configureWith(JoinRoomTask.Params(roomId, viaServers))
.dispatchTo(callback)
.executeBy(taskExecutor)

View file

@ -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<String>(CreateRoomFailure.CreatedWithTimeout)
}
}.flatMap { roomId ->
if (params.isDirect()) {

View file

@ -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<String>(JoinRoomFailure.JoinedWithTimeout)
}
}.flatMap { roomId ->
setReadMarkers(roomId)

View file

@ -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)
else -> throwable.localizedMessage
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)
}
}
}

View file

@ -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,6 +93,9 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
private fun renderCreationFailure(error: Throwable) {
hideWaitingView()
if (error is CreateRoomFailure.CreatedWithTimeout) {
finish()
} else
AlertDialog.Builder(this)
.setMessage(errorFormatter.toHumanReadable(error))
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }

View file

@ -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())

View file

@ -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<Unit> {
override fun onSuccess(data: Unit) {
setState { copy(tombstoneEventHandling = Success(roomId)) }
session.rx()
.joinRoom(roomId, viaServer)
.map { roomId }
.execute {
copy(tombstoneEventHandling = it)
}
override fun onFailure(failure: Throwable) {
setState { copy(tombstoneEventHandling = Fail(failure)) }
}
})
}
}

View file

@ -7,4 +7,6 @@
<string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string>
<string name="direct_room_start_search">"Start typing to get results"</string>
<string name="direct_room_filter_hint">"Filter by username or ID…"</string>
<string name="joining_room">"Joining room…"</string>
</resources>