From e89b04e2f62c2d3fcf6bdc0ab97334dd32f78c63 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Fri, 25 Feb 2022 16:29:36 +0100
Subject: [PATCH] feat: unify state holder api

---
 build.gradle.kts                              |  2 +-
 .../client/session/BaseInitHandler.kt         | 14 ++++-------
 .../client/session/ClientSession.kt           | 14 ++++-------
 .../protocol/io/CoroutineChannel.kt           | 14 ++++-------
 .../protocol/syncables/HeartBeatHandler.kt    | 13 +++++-----
 .../protocol/syncables/ObjectRepository.kt    | 14 ++++-------
 .../syncables/StatefulSyncableObject.kt       | 14 ++++-------
 .../libquassel/protocol/util/StateHolder.kt   | 17 +++++++++++++
 .../protocol/util/StateHolderExtensions.kt    | 25 +++++++++++++++++++
 9 files changed, 74 insertions(+), 53 deletions(-)
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index c8b02b8..63be78d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -15,4 +15,4 @@ plugins {
 }
 
 group = "de.justjanne.libquassel"
-version = "0.7.0"
+version = "0.8.0"
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
index 1b14319..90dadc8 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
@@ -12,13 +12,14 @@ package de.justjanne.libquassel.client.session
 import de.justjanne.libquassel.client.util.CoroutineQueue
 import de.justjanne.libquassel.protocol.syncables.ObjectIdentifier
 import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.update
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class BaseInitHandler(
   private val session: ClientSession
-) {
+) : StateHolder<BaseInitHandlerState> {
   private val coroutineQueue = CoroutineQueue<Unit>()
 
   fun sync(stub: SyncableStub) {
@@ -45,12 +46,7 @@ class BaseInitHandler(
     coroutineQueue.wait()
   } else Unit
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): BaseInitHandlerState = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<BaseInitHandlerState> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(BaseInitHandlerState())
+  override fun state(): BaseInitHandlerState = state.value
+  override fun flow(): Flow<BaseInitHandlerState> = state
+  private val state = MutableStateFlow(BaseInitHandlerState())
 }
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
index f9d4448..58d4c6b 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
@@ -38,6 +38,7 @@ import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
 import de.justjanne.libquassel.protocol.syncables.state.CertManagerState
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.log.info
 import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantMap
@@ -51,7 +52,7 @@ class ClientSession(
   protocolFeatures: ProtocolFeatures,
   protocols: List<ProtocolMeta>,
   sslContext: SSLContext
-) : Session {
+) : Session, StateHolder<ClientSessionState> {
   override val side = ProtocolSide.CLIENT
 
   override val rpcHandler = ClientRpcHandler(this)
@@ -202,14 +203,9 @@ class ClientSession(
 
   override val networkConfig get() = state().networkConfig
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): ClientSessionState = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<ClientSessionState> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(
+  override fun state(): ClientSessionState = state.value
+  override fun flow(): Flow<ClientSessionState> = state
+  private val state = MutableStateFlow(
     ClientSessionState(
       networks = mapOf(),
       identities = mapOf(),
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt
index 9ac7289..22e3de9 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt
@@ -9,6 +9,7 @@
 
 package de.justjanne.libquassel.protocol.io
 
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.update
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asCoroutineDispatcher
@@ -21,7 +22,7 @@ import java.nio.ByteBuffer
 import java.util.concurrent.Executors
 import javax.net.ssl.SSLContext
 
-class CoroutineChannel {
+class CoroutineChannel : StateHolder<CoroutineChannelState> {
   private lateinit var channel: StreamChannel
   private val writeContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
   private val readContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
@@ -81,12 +82,7 @@ class CoroutineChannel {
     }
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): CoroutineChannelState = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<CoroutineChannelState> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(CoroutineChannelState())
+  override fun state(): CoroutineChannelState = state.value
+  override fun flow(): Flow<CoroutineChannelState> = state
+  private val state = MutableStateFlow(CoroutineChannelState())
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
index 5435687..268c0c5 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
@@ -9,11 +9,12 @@
 
 package de.justjanne.libquassel.protocol.syncables
 
+import de.justjanne.libquassel.protocol.util.StateHolder
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.threeten.bp.Instant
 
-class HeartBeatHandler {
+class HeartBeatHandler : StateHolder<Long?> {
   private var lastReceived: Instant? = null
 
   /**
@@ -30,14 +31,12 @@ class HeartBeatHandler {
   fun recomputeLatency(current: Instant, force: Boolean) {
     val last = lastReceived?.toEpochMilli() ?: return
     val roundtripLatency = current.toEpochMilli() - last
-    if (force || roundtripLatency > this.roundtripLatency.value ?: return) {
+    if (force || roundtripLatency > (this.roundtripLatency.value ?: return)) {
       this.roundtripLatency.value = roundtripLatency
     }
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<Long?> = roundtripLatency
-
-  @PublishedApi
-  internal val roundtripLatency = MutableStateFlow<Long?>(null)
+  override fun flow(): Flow<Long?> = roundtripLatency
+  override fun state(): Long? = roundtripLatency.value
+  private val roundtripLatency = MutableStateFlow<Long?>(null)
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
index eeab61c..a783468 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
@@ -9,11 +9,12 @@
 
 package de.justjanne.libquassel.protocol.syncables
 
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class ObjectRepository {
+class ObjectRepository : StateHolder<ObjectRepositoryState> {
   fun add(syncable: SyncableStub): Boolean {
     val identifier = ObjectIdentifier(syncable)
     if (syncable is StatefulSyncableStub) {
@@ -78,12 +79,7 @@ class ObjectRepository {
     return find(T::class.java.simpleName, objectName) as? T
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state() = flow().value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow() = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(ObjectRepositoryState())
+  override fun state() = flow().value
+  override fun flow() = state
+  private val state = MutableStateFlow(ObjectRepositoryState())
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
index 0b10671..e3260e3 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
@@ -10,6 +10,7 @@
 package de.justjanne.libquassel.protocol.syncables
 
 import de.justjanne.libquassel.protocol.session.Session
+import de.justjanne.libquassel.protocol.util.StateHolder
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -17,7 +18,7 @@ abstract class StatefulSyncableObject<T>(
   session: Session?,
   className: String,
   state: T
-) : SyncableObject(session, className), StatefulSyncableStub {
+) : SyncableObject(session, className), StatefulSyncableStub, StateHolder<T> {
   override fun toString(): String {
     return "$className(objectName=$objectName, state=${state()})"
   }
@@ -38,12 +39,7 @@ abstract class StatefulSyncableObject<T>(
     return result
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): T = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<T> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(state)
+  override fun state(): T = state.value
+  override fun flow(): Flow<T> = state
+  protected val state = MutableStateFlow(state)
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt
new file mode 100644
index 0000000..03284a9
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2022 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+import kotlinx.coroutines.flow.Flow
+
+interface StateHolder<T> {
+  fun state(): T
+  fun flow(): Flow<T>
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt
new file mode 100644
index 0000000..780c6d2
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt
@@ -0,0 +1,25 @@
+/*
+ * libquassel
+ * Copyright (c) 2022 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+@ExperimentalCoroutinesApi
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> Flow<StateHolder<T>?>.flatMap(): Flow<T?> =
+  flatMapLatest { it?.flow() ?: emptyFlow() }
+
+@ExperimentalCoroutinesApi
+inline fun <reified T> Flow<Iterable<StateHolder<T>>?>.combineLatest(): Flow<List<T>> =
+  flatMapLatest { combine(it?.map(StateHolder<T>::flow).orEmpty(), ::listOf) }
-- 
GitLab