Commit abe34222 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Fix reconnection loop issue when add new server

parent 65d5c3a7
......@@ -95,7 +95,13 @@ public class DDPClientImpl {
disposables.clear();
}
},
err -> task.trySetError(new DDPClientCallback.Connect.Timeout(client))
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
......@@ -175,7 +181,13 @@ public class DDPClientImpl {
disposables.clear();
}
},
err -> task.trySetError(new DDPClientCallback.Ping.Timeout(client))
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
......@@ -185,7 +197,9 @@ public class DDPClientImpl {
}
}
/* package */ void sub(final TaskCompletionSource<DDPSubscription.Ready> task, String name,
/* package */
void sub(final TaskCompletionSource<DDPSubscription.Ready> task, String name,
JSONArray params, String id) {
final boolean requested =
sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params));
......@@ -220,7 +234,13 @@ public class DDPClientImpl {
}
}
},
RCLog::e
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
......@@ -254,6 +274,11 @@ public class DDPClientImpl {
}
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
......@@ -297,7 +322,9 @@ public class DDPClientImpl {
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new DDPClientCallback.RPC.Timeout(client));
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
......
......@@ -11,6 +11,10 @@ object ConnectionStatusManager {
}
private const val DEBUG = false
private val DEFAULT_CALLBACK = object : TransitionCallback {
override fun onTransitioned(success: Boolean) {
}
}
private val stateMachine: EnumStateMachine<State>
init {
......@@ -28,23 +32,46 @@ object ConnectionStatusManager {
fun currentState() = stateMachine.currentState()
@Synchronized
fun setOnline(callback: TransitionCallback) {
fun setOnline(callback: TransitionCallback = DEFAULT_CALLBACK) {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.ONLINE, callback)
}
@Synchronized
fun setConnecting(callback: TransitionCallback) {
fun setOnline() {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.ONLINE, DEFAULT_CALLBACK)
}
@Synchronized
fun setConnecting(callback: TransitionCallback = DEFAULT_CALLBACK) {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.CONNECTING, callback)
}
@Synchronized
fun setConnectionError(callback: TransitionCallback) {
fun setConnecting() {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.CONNECTING, DEFAULT_CALLBACK)
}
@Synchronized
fun setConnectionError(callback: TransitionCallback = DEFAULT_CALLBACK) {
KeepAliveJob.schedule()
tryTransitionTo(State.OFFLINE, callback)
}
@Synchronized
fun setConnectionError() {
KeepAliveJob.schedule()
tryTransitionTo(State.OFFLINE, DEFAULT_CALLBACK)
}
@Synchronized
fun setOffline() {
stateMachine.reset()
}
private fun tryTransitionTo(newState: State, callback: TransitionCallback) {
try {
stateMachine.transition(newState)
......
......@@ -72,7 +72,7 @@ object RocketChatCache {
return getString(KEY_SELECTED_SERVER_HOSTNAME, null)
}
fun setSelectedRoomId(roomId: String) {
fun setSelectedRoomId(roomId: String?) {
try {
val jsonObject = getSelectedRoomIdJsonObject()
jsonObject.put(getSelectedServerHostname(), roomId)
......@@ -81,7 +81,6 @@ object RocketChatCache {
RCLog.e(e)
Logger.report(e)
}
}
@Throws(JSONException::class)
......@@ -234,10 +233,11 @@ object RocketChatCache {
fun clearSelectedHostnameReferences() {
val hostname = getSelectedServerHostname()
if (hostname != null) {
setString(KEY_OPENED_ROOMS, null)
removeSiteName(hostname)
removeHostname(hostname)
removeSiteUrl(hostname)
setSelectedServerHostname(null)
setSelectedServerHostname(getFirstLoggedHostnameIfAny())
}
}
......@@ -308,7 +308,7 @@ object RocketChatCache {
fun setSessionToken(sessionToken: String) {
val selectedServerHostname = getSelectedServerHostname() ?:
throw IllegalStateException("Trying to set sessionToken to null hostname")
val sessions = getSessionToken()
val sessions = getString(KEY_SESSION_TOKEN, null)
try {
val jsonObject = if (sessions == null) JSONObject() else JSONObject(sessions)
jsonObject.put(selectedServerHostname, sessionToken)
......
......@@ -118,7 +118,7 @@ public class MethodCallHelper {
} else if (exception instanceof DDPClientCallback.RPC.Timeout) {
return Task.forError(new MethodCall.Timeout());
} else if (exception instanceof DDPClientCallback.Closed) {
return Task.forError(new Exception("Oops, your connection seems off..."));
return Task.forError(new Exception(exception.getMessage()));
} else {
return Task.forError(exception);
}
......
......@@ -17,6 +17,7 @@ import chat.rocket.core.models.PublicSetting
import chat.rocket.core.repositories.LoginServiceConfigurationRepository
import chat.rocket.core.repositories.PublicSettingRepository
import com.hadisatrio.optional.Optional
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.subscribeBy
......@@ -74,6 +75,8 @@ class LoginPresenter(private val loginServiceConfigurationRepository: LoginServi
}
private fun doLogin(username: String, password: String, optional: Optional<PublicSetting>) {
addSubscription(
Completable.create {
call(username, password, optional)
.continueWith(object : Continuation<Void, Any?> {
override fun then(task: Task<Void>?): Any? {
......@@ -89,10 +92,17 @@ class LoginPresenter(private val loginServiceConfigurationRepository: LoginServi
view.showError(error.message)
}
}
return Completable.complete()
}
return null
}
}, Task.UI_THREAD_EXECUTOR)
}.subscribeBy(
onError = {
view.showError(it.message)
}
)
)
}
private fun call(username: String, password: String, optional: Optional<PublicSetting>): Task<Void> {
......
......@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.log.RCLog;
......@@ -260,6 +261,7 @@ import io.reactivex.subjects.BehaviorSubject;
private Single<Boolean> connectToServer(String hostname) {
return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) {
ConnectionStatusManager.INSTANCE.setConnectionError();
return Single.error(new IllegalArgumentException("hostname not found"));
}
......@@ -270,8 +272,10 @@ import io.reactivex.subjects.BehaviorSubject;
}
if (serviceInterface != null) {
ConnectionStatusManager.INSTANCE.setConnecting();
return serviceInterface.ensureConnectionToServer(hostname);
} else {
ConnectionStatusManager.INSTANCE.setConnectionError();
return Single.error(new ThreadLooperNotPreparedException("not prepared"));
}
});
......
......@@ -38,7 +38,8 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
/**
* ensure RocketChatService alive.
*/
/*package*/static void keepAlive(Context context) {
/*package*/
static void keepAlive(Context context) {
context.startService(new Intent(context, RocketChatService.class));
}
......@@ -99,15 +100,15 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
return Single.defer(() -> {
webSocketThreadLock.acquire();
int connectivityState = ConnectivityManager.getInstance(getApplicationContext()).getConnectivityState(hostname);
boolean isDisconnected = connectivityState < ServerConnectivity.STATE_CONNECTED ;
boolean isDisconnected = connectivityState < ServerConnectivity.STATE_CONNECTED;
if (currentWebSocketThread != null && existsThreadForHostname(hostname) && !isDisconnected) {
webSocketThreadLock.release();
return Single.just(currentWebSocketThread);
}
if (currentWebSocketThread != null) {
if (isDisconnected) {
return currentWebSocketThread.terminate(true)
boolean hasFailed = existsThreadForHostname(hostname);
return currentWebSocketThread.terminate(hasFailed)
.doAfterTerminate(() -> currentWebSocketThread = null)
.flatMap(terminated ->
RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
......@@ -123,8 +124,6 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
})
);
}
return Single.just(currentWebSocketThread);
}
return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> {
......
......@@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import bolts.Task;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.RxHelper;
......@@ -123,26 +124,26 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
/**
* terminate WebSocket thread.
* Terminate WebSocket thread. If {@code hasFailed} is {@code true} it means that a connection was
* in progress but failed and got offline. If it's {@code false} means that the user explicitly
* disconnected from server either by logging out or by means of changing servers.
*
* @param isDisconnected {@code true} If we're trying to terminate a disconnected a websocket
* thread which means it has failed.
* @param hasFailed {@code true} if the termination is due to a network error, otherwise
* return false
*/
@DebugLog
/* package */ Single<Boolean> terminate(boolean isDisconnected) {
/* package */ Single<Boolean> terminate(boolean hasFailed) {
if (isAlive()) {
return Single.create(emitter -> {
new Handler(getLooper()).post(() -> {
return Single.create(emitter -> new Handler(getLooper()).post(() -> {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
int reason = (isDisconnected) ?
int reason = (hasFailed) ?
DDPClient.REASON_NETWORK_ERROR : DDPClient.REASON_CLOSED_BY_USER;
unregisterListenersAndClose(reason);
connectivityManager.notifyConnectionLost(hostname,
isDisconnected ? DDPClient.REASON_NETWORK_ERROR : DDPClient.REASON_CLOSED_BY_USER);
connectivityManager.notifyConnectionLost(hostname, reason);
RocketChatWebSocketThread.super.quit();
ConnectionStatusManager.INSTANCE.setOffline();
emitter.onSuccess(true);
});
});
}));
} else {
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR);
......@@ -166,7 +167,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
@DebugLog
/* package */ Single<Boolean> keepAlive() {
return checkIfConnectionAlive()
.flatMap(alive -> alive ? Single.just(true) : connectWithExponentialBackoff());
.flatMap(alive -> connectWithExponentialBackoff());
}
@DebugLog
......@@ -227,7 +228,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
.onSuccessTask(task -> {
final String newSession = task.getResult().session;
connectivityManager.notifyConnectionEstablished(hostname, newSession);
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
RxWebSocketCallback.Close result = _task.getResult();
if (result.code == DDPClient.REASON_NETWORK_ERROR) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment