Skip to content
Snippets Groups Projects
Select Git revision
  • main default protected
  • wip
  • ChenZhangg-Modify_GRADLE_1
  • jetpack-compose-rewrite
  • demo-jump-in-history
  • attachments
  • 1.7.0 protected
  • 1.6.2 protected
  • 1.6.1 protected
  • 1.6.0 protected
  • 1.5.3 protected
  • 1.5.2 protected
  • 1.5.1 protected
  • 1.5.0 protected
  • 1.4.4 protected
  • 1.4.3 protected
  • 1.4.2 protected
  • 1.4.1 protected
  • 1.4.0 protected
  • v1.3.3 protected
  • v1.3.2 protected
  • v1.3.1 protected
  • v1.3.0 protected
  • v1.2.28 protected
  • v1.2.27 protected
  • v1.2.26 protected
26 results

SessionManager.kt

Blame
  • SessionManager.kt 6.75 KiB
    /*
     * Quasseldroid - Quassel client for Android
     *
     * Copyright (c) 2019 Janne Koschinski
     * Copyright (c) 2019 The Quassel Project
     *
     * This program is free software: you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 3 as published
     * by the Free Software Foundation.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License along
     * with this program. If not, see <http://www.gnu.org/licenses/>.
     */
    
    package de.kuschku.libquassel.session
    
    import de.kuschku.libquassel.connection.ConnectionState
    import de.kuschku.libquassel.connection.HostnameVerifier
    import de.kuschku.libquassel.connection.SocketAddress
    import de.kuschku.libquassel.protocol.ClientData
    import de.kuschku.libquassel.protocol.message.HandshakeMessage
    import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers
    import de.kuschku.libquassel.util.compatibility.HandlerService
    import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
    import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.*
    import de.kuschku.libquassel.util.helpers.or
    import io.reactivex.Observable
    import io.reactivex.disposables.Disposable
    import io.reactivex.functions.BiFunction
    import io.reactivex.subjects.BehaviorSubject
    import javax.net.ssl.X509TrustManager
    
    class SessionManager(
      offlineSession: ISession,
      private val backlogStorage: BacklogStorage,
      private val notificationManager: NotificationManager?,
      val handlerService: HandlerService,
      private val heartBeatFactory: () -> HeartBeatRunner,
      private val exceptionHandler: (Throwable) -> Unit
    ) {
      private var disconnectFromCore: (() -> Unit)? = null
      private var initCallback: ((Session) -> Unit)? = null
    
      fun setDisconnectFromCore(callback: (() -> Unit)?) {
        this.disconnectFromCore = callback
      }
    
      fun setInitCallback(callback: ((Session) -> Unit)?) {
        this.initCallback = callback
      }
    
      fun close() = session.or(lastSession).close()
    
      private var lastClientData: ClientData? = null
      private var lastTrustManager: X509TrustManager? = null
      private var lastHostnameVerifier: HostnameVerifier? = null
      private var lastAddress: SocketAddress? = null
      private var lastUserData: Pair<String, String>? = null
      private var lastShouldReconnect = false
    
      private var inProgressSession = BehaviorSubject.createDefault(ISession.NULL)
      private var lastSession: ISession = offlineSession
      val state: Observable<ConnectionState> = inProgressSession.switchMap(ISession::state)
    
      private val initStatus: Observable<Pair<Int, Int>> = inProgressSession.switchMap(ISession::initStatus)
      val session: Observable<ISession> = state.map { connectionState ->
        if (connectionState == ConnectionState.CONNECTED)
          inProgressSession.value
        else
          lastSession
      }
    
      private var hasErrored: Boolean = false
    
      val error = inProgressSession.switchMap(ISession::error)
    
      val connectionError = inProgressSession.switchMap(ISession::connectionError)
    
      val connectionProgress: Observable<Triple<ConnectionState, Int, Int>> = Observable.combineLatest(
        state, initStatus,
        BiFunction<ConnectionState, Pair<Int, Int>, Triple<ConnectionState, Int, Int>> { t1, t2 ->
          Triple(t1, t2.first, t2.second)
        })
    
      val disposables = mutableListOf<Disposable>()
    
      init {
        log(INFO, "Session", "Session created")
    
        disposables.add(state.subscribe {
          if (it == ConnectionState.CONNECTED) {
            lastSession.close()
          }
        })
    
        // This should preload them
        Invokers
      }
    
      fun login(user: String, pass: String) {
        inProgressSession.value.login(user, pass)
      }
    
      fun setupCore(setupData: HandshakeMessage.CoreSetupData) {
        inProgressSession.value.setupCore(setupData)
      }
    
      fun connect(
        clientData: ClientData,
        trustManager: X509TrustManager,
        hostnameVerifier: HostnameVerifier,
        address: SocketAddress,
        userData: Pair<String, String>,
        shouldReconnect: Boolean = false
      ) {
        log(DEBUG, "SessionManager", "Connecting")
        inProgressSession.value.close()
        lastClientData = clientData
        lastTrustManager = trustManager
        lastHostnameVerifier = hostnameVerifier
        lastAddress = address
        lastUserData = userData
        lastShouldReconnect = shouldReconnect
        hasErrored = false
        inProgressSession.onNext(
          Session(
            address,
            userData,
            trustManager,
            hostnameVerifier,
            clientData,
            handlerService,
            heartBeatFactory,
            disconnectFromCore,
            initCallback,
            exceptionHandler,
            ::hasErroredCallback,
            notificationManager,
            backlogStorage
          )
        )
      }
    
      fun hasErroredCallback(error: Error) {
        log(WARN, "SessionManager", "Callback Error occured: $error")
        hasErrored = true
      }
    
      /**
       * @return if an autoreconnect has been necessary
       */
      fun autoReconnect(forceReconnect: Boolean = false) = if (!hasErrored) {
        state.or(ConnectionState.DISCONNECTED).let {
          if (it == ConnectionState.CLOSED) {
            log(INFO, "SessionManager", "Autoreconnect triggered")
            reconnect(forceReconnect)
            true
          } else {
            log(INFO, "SessionManager", "Autoreconnect failed: state is $it")
            false
          }
        }
      } else {
        log(INFO, "SessionManager", "Autoreconnect failed: hasErrored")
        false
      }
    
      fun reconnect(forceReconnect: Boolean = false) {
        if (lastShouldReconnect || forceReconnect) {
          val clientData = lastClientData
          val trustManager = lastTrustManager
          val hostnameVerifier = lastHostnameVerifier
          val address = lastAddress
          val userData = lastUserData
    
          if (clientData != null && trustManager != null && hostnameVerifier != null && address != null && userData != null) {
            connect(clientData, trustManager, hostnameVerifier, address, userData, forceReconnect)
          } else {
            log(INFO, "SessionManager", "Reconnect failed: not enough data available")
          }
        } else {
          log(INFO, "SessionManager", "Reconnect failed: reconnect not allowed")
        }
      }
    
      fun disconnect(forever: Boolean) {
        if (forever)
          backlogStorage.clearMessages()
        inProgressSession.value.close()
        inProgressSession.onNext(ISession.NULL)
      }
    
      fun ifDisconnected(closure: (ISession) -> Unit) {
        state.or(ConnectionState.DISCONNECTED).let {
          if (it == ConnectionState.CLOSED || it == ConnectionState.DISCONNECTED) {
            closure(inProgressSession.value)
          }
        }
      }
    
      fun dispose() {
        setDisconnectFromCore(null)
        setInitCallback(null)
        for (disposable in disposables) {
          if (!disposable.isDisposed)
            disposable.dispose()
        }
      }
    }