From ef7393169e82857089f478903a8bed785f097011 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Tue, 26 Sep 2017 14:00:34 +0200 Subject: [PATCH] Implemented proper offline functionality handling --- .../quasseldroid_ng/service/QuasselService.kt | 54 ++++++++++--------- .../quasseldroid_ng/ui/MainActivity.kt | 10 ++-- .../de/kuschku/libquassel/session/Backend.kt | 2 +- .../libquassel/session/CoreConnection.kt | 11 ++-- .../libquassel/session/ICoreConnection.kt | 31 ----------- .../de/kuschku/libquassel/session/ISession.kt | 19 +++++++ .../libquassel/session/ProtocolHandler.kt | 5 +- .../de/kuschku/libquassel/session/Session.kt | 50 +++++++---------- .../libquassel/session/SessionManager.kt | 49 +++++++++++++++++ .../kuschku/libquassel/ConnectionUnitTest.kt | 7 +-- 10 files changed, 130 insertions(+), 108 deletions(-) delete mode 100644 lib/src/main/java/de/kuschku/libquassel/session/ICoreConnection.kt create mode 100644 lib/src/main/java/de/kuschku/libquassel/session/ISession.kt create mode 100644 lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt index adb777f4f..b0e60d6a7 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt @@ -7,7 +7,8 @@ import android.os.Handler import android.os.HandlerThread import de.kuschku.libquassel.protocol.* import de.kuschku.libquassel.session.Backend -import de.kuschku.libquassel.session.Session +import de.kuschku.libquassel.session.ISession +import de.kuschku.libquassel.session.SessionManager import de.kuschku.libquassel.session.SocketAddress import de.kuschku.quasseldroid_ng.BuildConfig import de.kuschku.quasseldroid_ng.R @@ -18,20 +19,31 @@ import java.security.cert.X509Certificate import javax.net.ssl.X509TrustManager class QuasselService : LifecycleService() { - private lateinit var session: Session + private lateinit var sessionManager: SessionManager + + private lateinit var clientData: ClientData + + private val trustManager = object : X509TrustManager { + override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) { + } + + override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) { + } + + override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() + } private val backendImplementation = object : Backend { - override fun session() = session + override fun sessionManager() = sessionManager override fun connect(address: SocketAddress, user: String, pass: String) { disconnect() val handlerService = AndroidHandlerService() - session.connect(address, handlerService) - session.userData = user to pass + sessionManager.connect(clientData, trustManager, address, handlerService, user to pass) } override fun disconnect() { - session.cleanUp() + sessionManager.disconnect() } } @@ -56,7 +68,7 @@ class QuasselService : LifecycleService() { } } - override fun session() = backendImplementation.session() + override fun sessionManager() = backendImplementation.sessionManager() } private lateinit var database: QuasselDatabase @@ -64,26 +76,16 @@ class QuasselService : LifecycleService() { override fun onCreate() { super.onCreate() database = QuasselDatabase.Creator.init(application) - session = Session( - clientData = ClientData( - identifier = "${resources.getString(R.string.app_name)} ${BuildConfig.VERSION_NAME}", - buildDate = Instant.ofEpochSecond(BuildConfig.GIT_COMMIT_DATE), - clientFeatures = Quassel_Features.of(*Quassel_Feature.values()), - protocolFeatures = Protocol_Features.of( - Protocol_Feature.Compression, - Protocol_Feature.TLS - ), - supportedProtocols = byteArrayOf(0x02) + sessionManager = SessionManager(ISession.NULL) + clientData = ClientData( + identifier = "${resources.getString(R.string.app_name)} ${BuildConfig.VERSION_NAME}", + buildDate = Instant.ofEpochSecond(BuildConfig.GIT_COMMIT_DATE), + clientFeatures = Quassel_Features.of(*Quassel_Feature.values()), + protocolFeatures = Protocol_Features.of( + Protocol_Feature.Compression, + Protocol_Feature.TLS ), - trustManager = object : X509TrustManager { - override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) { - } - - override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) { - } - - override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() - } + supportedProtocols = byteArrayOf(0x02) ) } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt index 933598260..93f8a9a3c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt @@ -19,7 +19,6 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.util.helper.stickyMapNotNull import de.kuschku.quasseldroid_ng.util.helper.stickySwitchMapNotNull -import de.kuschku.quasseldroid_ng.util.helper.switchMapNullable import org.threeten.bp.ZoneOffset import org.threeten.bp.ZonedDateTime import org.threeten.bp.format.DateTimeFormatter @@ -49,12 +48,9 @@ class MainActivity : ServiceBoundActivity() { @BindView(R.id.errorList) lateinit var errorList: TextView - private val state = backend.stickyMapNotNull(null, Backend::session) - .switchMapNullable(null) { session -> - LiveDataReactiveStreams.fromPublisher(session.connectionPublisher) - } - .stickySwitchMapNotNull(ConnectionState.DISCONNECTED) { connection -> - LiveDataReactiveStreams.fromPublisher(connection.state) + private val state = backend.stickyMapNotNull(null, Backend::sessionManager) + .stickySwitchMapNotNull(ConnectionState.DISCONNECTED) { session -> + LiveDataReactiveStreams.fromPublisher(session.state) } private var snackbar: Snackbar? = null diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt b/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt index 904e78e92..a0535645b 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt @@ -3,5 +3,5 @@ package de.kuschku.libquassel.session interface Backend { fun connect(address: SocketAddress, user: String, pass: String) fun disconnect() - fun session(): Session + fun sessionManager(): SessionManager } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt b/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt index 9b26573ff..c778ad2c7 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt @@ -21,6 +21,7 @@ import io.reactivex.BackpressureStrategy import io.reactivex.subjects.BehaviorSubject import org.threeten.bp.ZoneOffset import org.threeten.bp.format.DateTimeFormatter +import java.io.Closeable import java.lang.Thread.UncaughtExceptionHandler import java.net.Socket import java.net.SocketException @@ -30,7 +31,7 @@ class CoreConnection( private val session: Session, private val address: SocketAddress, private val handlerService: HandlerService -) : Thread(), ICoreConnection { +) : Thread(), Closeable { companion object { private const val TAG = "CoreConnection" } @@ -43,7 +44,7 @@ class CoreConnection( private val chainedBuffer = ChainedByteBuffer(direct = true) private val internalState = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED) - override val state = internalState.toFlowable(BackpressureStrategy.LATEST) + val state = internalState.toFlowable(BackpressureStrategy.LATEST) private var channel: WrappedChannel? = null @@ -57,7 +58,7 @@ class CoreConnection( channel = WrappedChannel.ofSocket(socket) } - override fun setState(value: ConnectionState) { + fun setState(value: ConnectionState) { log(INFO, "CoreConnection", value.name) internalState.onNext(value) } @@ -122,7 +123,7 @@ class CoreConnection( } } - override fun dispatch(message: HandshakeMessage) { + fun dispatch(message: HandshakeMessage) { handlerService.parse { try { val data = HandshakeMessage.serialize(message) @@ -136,7 +137,7 @@ class CoreConnection( } } - override fun dispatch(message: SignalProxyMessage) { + fun dispatch(message: SignalProxyMessage) { handlerService.parse { try { val data = SignalProxyMessage.serialize(message) diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ICoreConnection.kt b/lib/src/main/java/de/kuschku/libquassel/session/ICoreConnection.kt deleted file mode 100644 index e23d42b46..000000000 --- a/lib/src/main/java/de/kuschku/libquassel/session/ICoreConnection.kt +++ /dev/null @@ -1,31 +0,0 @@ -package de.kuschku.libquassel.session - -import de.kuschku.libquassel.protocol.message.HandshakeMessage -import de.kuschku.libquassel.protocol.message.SignalProxyMessage -import io.reactivex.BackpressureStrategy -import io.reactivex.subjects.BehaviorSubject -import org.reactivestreams.Publisher - -interface ICoreConnection { - val state: Publisher<ConnectionState> - fun close() - fun dispatch(message: HandshakeMessage) - fun dispatch(message: SignalProxyMessage) - fun start() - fun join() - fun setState(value: ConnectionState) - - companion object { - val NULL = object : ICoreConnection { - override fun setState(value: ConnectionState) = Unit - override fun start() = Unit - override fun join() = Unit - override fun close() = Unit - override fun dispatch(message: HandshakeMessage) = Unit - override fun dispatch(message: SignalProxyMessage) = Unit - override val state: Publisher<ConnectionState> - get() = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED) - .toFlowable(BackpressureStrategy.LATEST) - } - } -} diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt new file mode 100644 index 000000000..64054b6ea --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt @@ -0,0 +1,19 @@ +package de.kuschku.libquassel.session + +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.subjects.BehaviorSubject +import java.io.Closeable + +interface ISession : Closeable { + val state: Flowable<ConnectionState> + + companion object { + val NULL = object : ISession { + override fun close() = Unit + override val state: Flowable<ConnectionState> + = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED) + .toFlowable(BackpressureStrategy.LATEST) + } + } +} diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt index 5f364a5ee..c8fefde5c 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt @@ -11,8 +11,9 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.WARN import de.kuschku.libquassel.util.compatibility.log import org.threeten.bp.Instant +import java.io.Closeable -abstract class ProtocolHandler : SignalProxy, AuthHandler { +abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable { private val objectStorage: ObjectStorage = ObjectStorage(this) private val rpcHandler: RpcHandler = RpcHandler(this) @@ -170,7 +171,7 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler { toInit.remove(syncableObject) } - open fun cleanUp() { + override fun close() { objectStorage.clear() toInit.clear() } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt index 180c9e307..d45a5f85d 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt @@ -5,25 +5,26 @@ import de.kuschku.libquassel.protocol.message.HandshakeMessage import de.kuschku.libquassel.protocol.message.SignalProxyMessage import de.kuschku.libquassel.quassel.QuasselFeature import de.kuschku.libquassel.quassel.syncables.* -import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers import de.kuschku.libquassel.util.compatibility.HandlerService import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO import de.kuschku.libquassel.util.compatibility.log import de.kuschku.libquassel.util.hasFlag -import io.reactivex.BackpressureStrategy import io.reactivex.Flowable -import io.reactivex.subjects.BehaviorSubject import org.threeten.bp.Instant import javax.net.ssl.X509TrustManager class Session( val clientData: ClientData, - val trustManager: X509TrustManager -) : ProtocolHandler() { + val trustManager: X509TrustManager, + address: SocketAddress, + handlerService: HandlerService, + private val userData: Pair<String, String> +) : ProtocolHandler(), ISession { var coreFeatures: Quassel_Features = Quassel_Feature.NONE - var userData: Pair<String, String>? = null + private val coreConnection = CoreConnection(this, address, handlerService) + override val state: Flowable<ConnectionState> = coreConnection.state private var aliasManager: AliasManager? = null private var backlogManager: BacklogManager? = null @@ -38,34 +39,21 @@ class Session( private var networks = mutableMapOf<NetworkId, Network>() private var networkConfig: NetworkConfig? = null - private val connection = BehaviorSubject.createDefault(ICoreConnection.NULL) - val connectionPublisher: Flowable<ICoreConnection> = connection.toFlowable( - BackpressureStrategy.LATEST) - init { - log(INFO, "Session", "Session created") - - // This should preload them - Invokers - } - - fun connect(address: SocketAddress, handlerService: HandlerService) { - val coreConnection = CoreConnection(this, address, handlerService) - connection.onNext(coreConnection) coreConnection.start() } override fun handle(f: HandshakeMessage.ClientInitAck): Boolean { coreFeatures = f.coreFeatures ?: Quassel_Feature.NONE dispatch(HandshakeMessage.ClientLogin( - user = userData?.first, - password = userData?.second + user = userData.first, + password = userData.second )) return true } override fun handle(f: HandshakeMessage.SessionInit): Boolean { - connection.value.setState(ConnectionState.INIT) + coreConnection.setState(ConnectionState.INIT) f.networkIds?.forEach { val network = Network(it.value(-1), this) @@ -117,7 +105,7 @@ class Session( } override fun onInitDone() { - connection.value.setState(ConnectionState.CONNECTED) + coreConnection.setState(ConnectionState.CONNECTED) log(INFO, "Session", "Initialization finished") } @@ -130,20 +118,19 @@ class Session( override fun dispatch(message: SignalProxyMessage) { log(DEBUG, "Session", "> $message") - connection.value.dispatch(message) + coreConnection.dispatch(message) } override fun dispatch(message: HandshakeMessage) { log(DEBUG, "Session", "> $message") - connection.value.dispatch(message) + coreConnection.dispatch(message) } override fun network(id: NetworkId): Network? = networks[id] override fun identity(id: IdentityId): Identity? = identities[id] - override fun cleanUp() { - connection.value.close() - connection.onNext(ICoreConnection.NULL) + override fun close() { + coreConnection.close() aliasManager = null backlogManager = null @@ -159,9 +146,10 @@ class Session( identities.clear() networks.clear() - super.cleanUp() + super.close() } - fun connection(): ICoreConnection? - = connection.value + fun join() { + coreConnection.join() + } } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt new file mode 100644 index 000000000..f2e461552 --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -0,0 +1,49 @@ +package de.kuschku.libquassel.session + +import de.kuschku.libquassel.protocol.ClientData +import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers +import de.kuschku.libquassel.util.compatibility.HandlerService +import de.kuschku.libquassel.util.compatibility.LoggingHandler +import de.kuschku.libquassel.util.compatibility.log +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.subjects.BehaviorSubject +import javax.net.ssl.X509TrustManager + +class SessionManager( + private val offlineSession: ISession +) { + init { + log(LoggingHandler.LogLevel.INFO, "Session", "Session created") + + // This should preload them + Invokers + } + + fun connect( + clientData: ClientData, + trustManager: X509TrustManager, + address: SocketAddress, + handlerService: HandlerService, + userData: Pair<String, String> + ) { + inProgressSession.value.close() + inProgressSession.onNext(Session(clientData, trustManager, address, handlerService, userData)) + } + + fun disconnect() { + inProgressSession.value.close() + inProgressSession.onNext(offlineSession) + } + + private var inProgressSession = BehaviorSubject.createDefault(offlineSession) + private val inProgressSessionPublisher: Flowable<ISession> + = inProgressSession.toFlowable(BackpressureStrategy.LATEST) + val state = inProgressSessionPublisher.switchMap { it.state } + val session = state.map { connectionState -> + if (connectionState == ConnectionState.CONNECTED) + inProgressSession.value + else + offlineSession + } +} diff --git a/lib/src/test/java/de/kuschku/libquassel/ConnectionUnitTest.kt b/lib/src/test/java/de/kuschku/libquassel/ConnectionUnitTest.kt index 7d7f5009f..a4af220db 100644 --- a/lib/src/test/java/de/kuschku/libquassel/ConnectionUnitTest.kt +++ b/lib/src/test/java/de/kuschku/libquassel/ConnectionUnitTest.kt @@ -46,10 +46,7 @@ class ConnectionUnitTest { } override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() - }) - session.userData = user to pass - - session.connect(SocketAddress(host, port), JavaHandlerService()) - session.connection()?.join() + }, SocketAddress(host, port), JavaHandlerService(), user to pass) + session.join() } } -- GitLab