Ver Fonte

ComponentStorage

Him188 há 4 anos atrás
pai
commit
51c1450202
22 ficheiros alterados com 509 adições e 141 exclusões
  1. 1 0
      mirai-core/build.gradle.kts
  2. 2 2
      mirai-core/src/commonMain/kotlin/AbstractBot.kt
  3. 24 56
      mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
  4. 6 3
      mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt
  5. 68 0
      mirai-core/src/commonMain/kotlin/network/handler/component/ComponentKey.kt
  6. 27 0
      mirai-core/src/commonMain/kotlin/network/handler/component/ComponentStorage.kt
  7. 57 0
      mirai-core/src/commonMain/kotlin/network/handler/component/ConcurrentComponentStorage.kt
  8. 27 0
      mirai-core/src/commonMain/kotlin/network/handler/component/MutableComponentStorage.kt
  9. 15 0
      mirai-core/src/commonMain/kotlin/network/handler/component/NoSuchComponentException.kt
  10. 3 0
      mirai-core/src/commonMain/kotlin/network/handler/components/AccountSecretsManager.kt
  11. 28 10
      mirai-core/src/commonMain/kotlin/network/handler/components/BdhSessionSyncer.kt
  12. 3 0
      mirai-core/src/commonMain/kotlin/network/handler/components/ContactUpdater.kt
  13. 30 13
      mirai-core/src/commonMain/kotlin/network/handler/components/PacketCodec.kt
  14. 42 23
      mirai-core/src/commonMain/kotlin/network/handler/components/ServerList.kt
  15. 21 6
      mirai-core/src/commonMain/kotlin/network/handler/components/SsoProcessor.kt
  16. 5 10
      mirai-core/src/commonMain/kotlin/network/handler/context/NetworkHandlerContext.kt
  17. 7 4
      mirai-core/src/commonMain/kotlin/network/handler/impl/netty/NettyNetworkHandler.kt
  18. 3 0
      mirai-core/src/commonMain/kotlin/network/handler/state/StateObserver.kt
  19. 8 7
      mirai-core/src/commonTest/kotlin/network/ServerListTest.kt
  20. 33 0
      mirai-core/src/commonTest/kotlin/network/component/ComponentKeyTest.kt
  21. 85 0
      mirai-core/src/commonTest/kotlin/network/component/ComponentStorageTest.kt
  22. 14 7
      mirai-core/src/commonTest/kotlin/network/handler/testUtils.kt

+ 1 - 0
mirai-core/build.gradle.kts

@@ -79,6 +79,7 @@ kotlin {
         commonTest {
             dependencies {
                 implementation(kotlin("script-runtime"))
+                runtimeOnly(`slf4j-simple`)
             }
         }
 

+ 2 - 2
mirai-core/src/commonMain/kotlin/AbstractBot.kt

@@ -33,7 +33,7 @@ import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
 import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
 import net.mamoe.mirai.internal.contact.uin
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
-import net.mamoe.mirai.internal.network.handler.components.ServerList
+import net.mamoe.mirai.internal.network.handler.components.ServerListImpl
 import net.mamoe.mirai.supervisorJob
 import net.mamoe.mirai.utils.*
 import kotlin.coroutines.CoroutineContext
@@ -172,7 +172,7 @@ internal abstract class AbstractBot constructor(
     // network
     ///////////////////////////////////////////////////////////////////////////
 
-    internal val serverListNew = ServerList() // TODO: 2021/4/16 load server list from cache (add a provider)
+    internal val serverListNew = ServerListImpl() // TODO: 2021/4/16 load server list from cache (add a provider)
     // bot.bdhSyncer.loadServerListFromCache()
 
     val network: NetworkHandler by lazy { createNetworkHandler(coroutineContext) }

+ 24 - 56
mirai-core/src/commonMain/kotlin/QQAndroidBot.kt

@@ -17,13 +17,15 @@ import net.mamoe.mirai.contact.Group
 import net.mamoe.mirai.contact.OtherClientInfo
 import net.mamoe.mirai.internal.contact.OtherClientImpl
 import net.mamoe.mirai.internal.contact.checkIsGroupImpl
-import net.mamoe.mirai.internal.network.*
-import net.mamoe.mirai.internal.network.handler.*
-import net.mamoe.mirai.internal.network.handler.components.BdhSessionSyncer
-import net.mamoe.mirai.internal.network.handler.components.SsoProcessor
+import net.mamoe.mirai.internal.network.Packet
+import net.mamoe.mirai.internal.network.handler.NetworkHandler
+import net.mamoe.mirai.internal.network.handler.component.ConcurrentComponentStorage
+import net.mamoe.mirai.internal.network.handler.component.set
+import net.mamoe.mirai.internal.network.handler.components.*
 import net.mamoe.mirai.internal.network.handler.context.NetworkHandlerContextImpl
 import net.mamoe.mirai.internal.network.handler.context.SsoProcessorContextImpl
 import net.mamoe.mirai.internal.network.handler.impl.netty.NettyNetworkHandlerFactory
+import net.mamoe.mirai.internal.network.handler.logger
 import net.mamoe.mirai.internal.network.handler.selector.FactoryKeepAliveNetworkHandlerSelector
 import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
 import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver
@@ -32,9 +34,9 @@ import net.mamoe.mirai.internal.network.handler.state.StateObserver
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
 import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
-import net.mamoe.mirai.internal.utils.ScheduledJob
-import net.mamoe.mirai.internal.utils.friendCacheFile
-import net.mamoe.mirai.utils.*
+import net.mamoe.mirai.utils.BotConfiguration
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.systemProp
 import kotlin.contracts.contract
 import kotlin.coroutines.CoroutineContext
 
@@ -71,7 +73,7 @@ internal class QQAndroidBot constructor(
 ) : AbstractBot(configuration, account.id) {
     override val bot: QQAndroidBot get() = this
 
-    val bdhSyncer: BdhSessionSyncer by lazy { BdhSessionSyncer(configuration, serverListNew, network.logger) }
+    val bdhSyncer: BdhSessionSyncer by lazy { BdhSessionSyncerImpl(configuration, serverListNew, network.logger) }
     internal var firstLoginSucceed: Boolean = false
 
     ///////////////////////////////////////////////////////////////////////////
@@ -80,9 +82,20 @@ internal class QQAndroidBot constructor(
 
     // TODO: 2021/4/14         bdhSyncer.loadFromCache()  when login
 
-    private val ssoProcessor: SsoProcessor by lazy { SsoProcessor(SsoProcessorContextImpl(this)) }
+    private val components: ConcurrentComponentStorage by lazy {
+        ConcurrentComponentStorage().apply {
+            set(
+                SsoProcessor,
+                SsoProcessorImpl(SsoProcessorContextImpl(bot))
+            ) // put sso processor at the first to make `client` faster.
 
-    val client get() = ssoProcessor.client
+            set(StateObserver, debugConfiguration.stateObserver)
+            set(ContactCacheService, ContactCacheServiceImpl(bot))
+            set(ContactUpdater, ContactUpdaterImpl(bot, this))
+        }
+    }
+
+    val client get() = components[SsoProcessor].client
 
     override suspend fun sendLogout() {
         network.sendWithoutExpect(StatSvc.Register.offline(client))
@@ -91,9 +104,8 @@ internal class QQAndroidBot constructor(
     override fun createNetworkHandler(coroutineContext: CoroutineContext): NetworkHandler {
         val context = NetworkHandlerContextImpl(
             this,
-            ssoProcessor,
             configuration.networkLoggerSupplier(this),
-            debugConfiguration.stateObserver
+            components
         )
         return SelectorNetworkHandler(
             context,
@@ -136,48 +148,4 @@ internal class QQAndroidBot constructor(
     fun getGroupByUinOrNull(uin: Long): Group? {
         return groups.firstOrNull { it.checkIsGroupImpl(); it.uin == uin }
     }
-
-
-    ///////////////////////////////////////////////////////////////////////////
-    // contact cache
-    ///////////////////////////////////////////////////////////////////////////
-
-    inline val json get() = configuration.json
-
-    val friendListCache: FriendListCache? by lazy {
-        if (!configuration.contactListCache.friendListCacheEnabled) return@lazy null
-        val file = configuration.friendCacheFile()
-        val ret = file.loadNotBlankAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache()
-
-        @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-        bot.eventChannel.parentScope(this@QQAndroidBot)
-            .subscribeAlways<net.mamoe.mirai.event.events.FriendInfoChangeEvent> {
-                friendListSaver?.notice()
-            }
-        ret
-    }
-
-    val groupMemberListCaches: GroupMemberListCaches? by lazy {
-        if (!configuration.contactListCache.groupMemberListCacheEnabled) {
-            return@lazy null
-        }
-        GroupMemberListCaches(this)
-    }
-
-    private val friendListSaver: ScheduledJob? by lazy {
-        if (!configuration.contactListCache.friendListCacheEnabled) return@lazy null
-        ScheduledJob(coroutineContext, configuration.contactListCache.saveIntervalMillis) {
-            runBIO { saveFriendCache() }
-        }
-    }
-
-    fun saveFriendCache() {
-        val friendListCache = friendListCache ?: return
-
-        configuration.friendCacheFile().run {
-            createFileIfNotExists()
-            writeText(JsonForCache.encodeToString(FriendListCache.serializer(), friendListCache))
-            bot.network.context.logger.info { "Saved ${friendListCache.list.size} friends to local cache." }
-        }
-    }
 }

+ 6 - 3
mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt

@@ -14,6 +14,7 @@ import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.handler.components.PacketCodec
 import net.mamoe.mirai.internal.network.handler.components.RawIncomingPacket
 import net.mamoe.mirai.internal.network.handler.context.NetworkHandlerContext
+import net.mamoe.mirai.internal.network.handler.state.StateObserver
 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.utils.*
@@ -137,7 +138,7 @@ internal abstract class NetworkHandlerSupport(
          */
         @Throws(Exception::class)
         suspend fun resumeConnection() {
-            val observer = context.stateObserver
+            val observer = context.getOrNull(StateObserver)
             if (observer != null) {
                 observer.beforeStateResume(this@NetworkHandlerSupport, _state)
                 val result = kotlin.runCatching { resumeConnection0() }
@@ -180,10 +181,12 @@ internal abstract class NetworkHandlerSupport(
     protected inline fun <S : BaseStateImpl> setState(crossinline new: () -> S): S = synchronized(this) {
         // we can add hooks here for debug.
 
+        val stateObserver = context.getOrNull(StateObserver)
+
         val impl = try {
             new() // inline only once
         } catch (e: Throwable) {
-            context.stateObserver?.exceptionOnCreatingNewState(this, _state, e)
+            stateObserver?.exceptionOnCreatingNewState(this, _state, e)
             throw e
         }
 
@@ -192,7 +195,7 @@ internal abstract class NetworkHandlerSupport(
         old.cancel(CancellationException("State is switched from $old to $impl"))
         _state = impl
 
-        context.stateObserver?.stateChanged(this, old, impl)
+        stateObserver?.stateChanged(this, old, impl)
 
         return impl
     }

+ 68 - 0
mirai-core/src/commonMain/kotlin/network/handler/component/ComponentKey.kt

@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler.component
+
+import kotlin.reflect.KClass
+import kotlin.reflect.KClassifier
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeParameter
+import kotlin.reflect.full.allSupertypes
+
+/**
+ * A key for specific component [T]. Component are not polymorphic.
+ *
+ * @param T is a type hint.
+ */
+internal interface ComponentKey<out T : Any> {
+    /**
+     * Get name of `T`.
+     *
+     * - If [qualified] is `false`, example: `PacketCodec`.
+     * - If [qualified] is `true`, example: `net.mamoe.mirai.internal.network.handler.components.PacketCodec`.
+     */
+    fun componentName(qualified: Boolean = false): String {
+        return getComponentTypeArgumentClassifier().renderClassifier(fullName = qualified)
+    }
+
+    fun smartToString(qualified: Boolean = false): String {
+        return "ComponentKey<${componentName(qualified)}>"
+    }
+
+    private companion object {
+        private fun KClassifier?.renderClassifier(
+            fullName: Boolean
+        ): String {
+            return when (val classifier = this) {
+                null -> "?"
+                is KClass<*> -> classifier.run { if (fullName) qualifiedName else simpleName } ?: "?"
+                is KTypeParameter -> classifier.renderTypeParameter(fullName)
+                else -> "?"
+            }
+        }
+
+        private fun KType.renderType(fullName: Boolean) = classifier.renderClassifier(fullName)
+
+        private fun KTypeParameter.renderTypeParameter(fullName: Boolean): String {
+            val upperBounds = upperBounds
+            return when (upperBounds.size) {
+                0 -> toString()
+                1 -> "ComponentKey<${upperBounds[0].renderType(fullName)}>"
+                else -> "ComponentKey<${upperBounds.joinToString(" & ") { it.renderType(fullName) }}>"
+            }
+        }
+
+        private fun ComponentKey<*>.getComponentTypeArgumentClassifier(): KClassifier? {
+            val thisType = this::class.allSupertypes.find { it.classifier == COMPONENT_KEY_K_CLASS }
+            return thisType?.arguments?.firstOrNull()?.type?.classifier
+        }
+
+        val COMPONENT_KEY_K_CLASS = ComponentKey::class
+    }
+}

+ 27 - 0
mirai-core/src/commonMain/kotlin/network/handler/component/ComponentStorage.kt

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler.component
+
+import org.jetbrains.annotations.TestOnly
+
+/**
+ * Facade for [component][ComponentKey]s. Implementation must be thread-safe.
+ * @see MutableComponentStorage
+ * @see ConcurrentComponentStorage
+ */
+internal interface ComponentStorage {
+    @get:TestOnly
+    val size: Int
+
+    @Throws(NoSuchComponentException::class)
+    operator fun <T : Any> get(key: ComponentKey<T>): T
+    fun <T : Any> getOrNull(key: ComponentKey<T>): T?
+}
+

+ 57 - 0
mirai-core/src/commonMain/kotlin/network/handler/component/ConcurrentComponentStorage.kt

@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler.component
+
+import net.mamoe.mirai.utils.systemProp
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * A thread-safe implementation of [MutableComponentStorage]
+ */
+internal class ConcurrentComponentStorage(
+    private val showAllComponents: Boolean = SHOW_ALL_COMPONENTS
+) : ComponentStorage, MutableComponentStorage {
+    private val map = ConcurrentHashMap<ComponentKey<*>, Any?>()
+
+    override val size: Int get() = map.size
+
+    override operator fun <T : Any> get(key: ComponentKey<T>): T {
+        return getOrNull(key) ?: throw NoSuchComponentException(key, this)
+    }
+
+    override fun <T : Any> getOrNull(key: ComponentKey<T>): T? {
+        @Suppress("UNCHECKED_CAST")
+        return map[key] as T?
+    }
+
+    override operator fun <T : Any> set(key: ComponentKey<T>, value: @UnsafeVariance T) {
+        map[key] = value
+    }
+
+    override fun <T : Any> remove(key: ComponentKey<T>): T? {
+        @Suppress("UNCHECKED_CAST")
+        return map.remove(key) as T?
+    }
+
+    override fun toString(): String {
+        if (showAllComponents) {
+            return buildString {
+                append("ConcurrentComponentStorage(size=").append(map.size).append(") {").appendLine()
+                for ((key, value) in map) {
+                    append("  ").append(key.componentName(qualified = false)).append(": ").append(value).appendLine()
+                }
+                append('}')
+            }
+        }
+        return "ConcurrentComponentStorage(size=${map.size})"
+    }
+}
+
+private val SHOW_ALL_COMPONENTS = systemProp("mirai.debug.network.show.all.components", false)

+ 27 - 0
mirai-core/src/commonMain/kotlin/network/handler/component/MutableComponentStorage.kt

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler.component
+
+/**
+ * Facade for [component][ComponentKey]s.
+ */
+internal interface MutableComponentStorage : ComponentStorage {
+    override operator fun <T : Any> get(key: ComponentKey<T>): T
+    operator fun <T : Any> set(key: ComponentKey<T>, value: @UnsafeVariance T)
+    fun <T : Any> remove(key: ComponentKey<T>): T?
+}
+
+internal operator fun <T : Any> MutableComponentStorage.set(key: ComponentKey<T>, value: @UnsafeVariance T?) {
+    if (value == null) {
+        remove(key)
+    } else {
+        set(key, value)
+    }
+}

+ 15 - 0
mirai-core/src/commonMain/kotlin/network/handler/component/NoSuchComponentException.kt

@@ -0,0 +1,15 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler.component
+
+internal data class NoSuchComponentException(
+    val key: ComponentKey<*>,
+    val storage: ComponentStorage
+) : NoSuchElementException("No such component: $key")

+ 3 - 0
mirai-core/src/commonMain/kotlin/network/handler/components/AccountSecretsManager.kt

@@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.network.handler.components
 
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.internal.BotAccount
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
 import net.mamoe.mirai.internal.network.handler.context.AccountSecrets
 import net.mamoe.mirai.internal.network.handler.context.AccountSecretsImpl
 import net.mamoe.mirai.internal.utils.actualCacheDir
@@ -33,6 +34,8 @@ import java.io.File
 internal interface AccountSecretsManager {
     fun saveSecrets(account: BotAccount, secrets: AccountSecrets)
     fun getSecrets(account: BotAccount): AccountSecrets?
+
+    companion object : ComponentKey<BdhSessionSyncer>
 }
 
 internal fun AccountSecretsManager.getSecretsOrCreate(account: BotAccount, device: DeviceInfo): AccountSecrets {

+ 28 - 10
mirai-core/src/commonMain/kotlin/network/handler/components/BdhSessionSyncer.kt

@@ -15,6 +15,7 @@ import kotlinx.serialization.KSerializer
 import kotlinx.serialization.builtins.SetSerializer
 import net.mamoe.mirai.internal.network.JsonForCache
 import net.mamoe.mirai.internal.network.ProtoBufForCache
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
 import net.mamoe.mirai.internal.network.handler.context.BdhSession
 import net.mamoe.mirai.internal.utils.actualCacheDir
 import net.mamoe.mirai.utils.BotConfiguration
@@ -24,19 +25,36 @@ import java.io.File
 private val ServerListSerializer: KSerializer<Set<ServerAddress>> =
     SetSerializer(ServerAddress.serializer())
 
+internal interface BdhSessionSyncer {
+    var bdhSession: CompletableDeferred<BdhSession>
+    val hasSession: Boolean
+
+    fun overrideSession(
+        session: BdhSession,
+        doSave: Boolean = true
+    )
+
+    fun loadServerListFromCache()
+    fun loadFromCache()
+    fun saveServerListToCache()
+    fun saveToCache()
+
+    companion object : ComponentKey<BdhSessionSyncer>
+}
+
 @OptIn(ExperimentalCoroutinesApi::class)
-internal class BdhSessionSyncer(
+internal class BdhSessionSyncerImpl(
     private val configuration: BotConfiguration,
     private val serverList: ServerList,
     private val logger: MiraiLogger,
-) {
-    var bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
-    val hasSession: Boolean
+) : BdhSessionSyncer {
+    override var bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
+    override val hasSession: Boolean
         get() = kotlin.runCatching { bdhSession.getCompleted() }.isSuccess
 
-    fun overrideSession(
+    override fun overrideSession(
         session: BdhSession,
-        doSave: Boolean = true
+        doSave: Boolean
     ) {
         bdhSession.complete(session)
         bdhSession = CompletableDeferred(session)
@@ -50,7 +68,7 @@ internal class BdhSessionSyncer(
     private val serverListCacheFile: File
         get() = configuration.actualCacheDir().resolve("servers.json")
 
-    fun loadServerListFromCache() {
+    override fun loadServerListFromCache() {
         val serverListCacheFile = this.serverListCacheFile
         if (serverListCacheFile.isFile) {
             logger.verbose("Loading server list from cache.")
@@ -65,7 +83,7 @@ internal class BdhSessionSyncer(
         }
     }
 
-    fun loadFromCache() {
+    override fun loadFromCache() {
         val sessionCacheFile = this.sessionCacheFile
         if (sessionCacheFile.isFile) {
             logger.verbose("Loading BdhSession from cache file")
@@ -83,7 +101,7 @@ internal class BdhSessionSyncer(
         }
     }
 
-    fun saveServerListToCache() {
+    override fun saveServerListToCache() {
         val serverListCacheFile = this.serverListCacheFile
         serverListCacheFile.parentFile?.mkdirs()
 
@@ -100,7 +118,7 @@ internal class BdhSessionSyncer(
         }
     }
 
-    fun saveToCache() {
+    override fun saveToCache() {
         val sessionCacheFile = this.sessionCacheFile
         sessionCacheFile.parentFile?.mkdirs()
         if (bdhSession.isCompleted) {

+ 3 - 0
mirai-core/src/commonMain/kotlin/network/handler/components/ContactUpdater.kt

@@ -28,6 +28,7 @@ import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
 import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
 import net.mamoe.mirai.internal.contact.toMiraiFriendInfo
 import net.mamoe.mirai.internal.network.Packet
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
 import net.mamoe.mirai.internal.network.handler.logger
 import net.mamoe.mirai.internal.network.isValid
 import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum
@@ -44,6 +45,8 @@ internal interface ContactUpdater {
     suspend fun loadAll(registerResp: SvcRespRegister)
 
     fun closeAllContacts(e: CancellationException)
+
+    companion object : ComponentKey<ContactUpdater>
 }
 
 internal class ContactUpdaterImpl(

+ 30 - 13
mirai-core/src/commonMain/kotlin/network/handler/components/PacketDecoder.kt → mirai-core/src/commonMain/kotlin/network/handler/components/PacketCodec.kt

@@ -11,6 +11,8 @@ package net.mamoe.mirai.internal.network.handler.components
 
 import kotlinx.io.core.*
 import net.mamoe.mirai.internal.QQAndroidBot
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
+import net.mamoe.mirai.internal.network.handler.components.PacketCodec.Companion.PacketLogger
 import net.mamoe.mirai.internal.network.handler.context.SsoSession
 import net.mamoe.mirai.internal.network.protocol.packet.*
 import net.mamoe.mirai.internal.utils.crypto.TEA
@@ -18,28 +20,43 @@ import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
 import net.mamoe.mirai.utils.*
 import kotlin.io.use
 
+
 /**
  * Packet decoders.
  *
  * - Transforms [ByteReadPacket] to [RawIncomingPacket]
  */
-internal object PacketCodec {
-    val PACKET_DEBUG = systemProp("mirai.debug.network.packet.logger", true)
-
-    /**
-     * 数据包相关的调试输出.
-     * 它默认是关闭的.
-     */
-    internal val PacketLogger: MiraiLoggerWithSwitch by lazy {
-        MiraiLogger.create("Packet").withSwitch(PACKET_DEBUG)
-    }
-
+internal interface PacketCodec {
     /**
      * It's caller's responsibility to close [input]
      * @param input received from sockets.
      * @return decoded
      */
-    fun decodeRaw(client: SsoSession, input: ByteReadPacket): RawIncomingPacket = input.run {
+    fun decodeRaw(client: SsoSession, input: ByteReadPacket): RawIncomingPacket
+
+    /**
+     * Process [RawIncomingPacket] using [IncomingPacketFactory.decode].
+     *
+     * This function wraps exceptions into [IncomingPacket]
+     */
+    suspend fun processBody(bot: QQAndroidBot, input: RawIncomingPacket): IncomingPacket?
+
+    companion object : ComponentKey<PacketCodec> {
+        val PACKET_DEBUG = systemProp("mirai.debug.network.packet.logger", true)
+
+        /**
+         * 数据包相关的调试输出.
+         * 它默认是关闭的.
+         */
+        internal val PacketLogger: MiraiLoggerWithSwitch by lazy {
+            MiraiLogger.create("Packet").withSwitch(PACKET_DEBUG)
+        }
+    }
+}
+
+internal class PacketCodecImpl : PacketCodec {
+
+    override fun decodeRaw(client: SsoSession, input: ByteReadPacket): RawIncomingPacket = input.run {
         // login
         val flag1 = readInt()
 
@@ -210,7 +227,7 @@ internal object PacketCodec {
      *
      * This function wraps exceptions into [IncomingPacket]
      */
-    suspend fun processBody(bot: QQAndroidBot, input: RawIncomingPacket): IncomingPacket? {
+    override suspend fun processBody(bot: QQAndroidBot, input: RawIncomingPacket): IncomingPacket? {
         val factory = KnownPacketFactories.findPacketFactory(input.commandName) ?: return null
 
         return kotlin.runCatching {

+ 42 - 23
mirai-core/src/commonMain/kotlin/network/handler/components/ServerList.kt

@@ -10,6 +10,8 @@
 package net.mamoe.mirai.internal.network.handler.components
 
 import kotlinx.serialization.Serializable
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
+import net.mamoe.mirai.internal.network.handler.components.ServerList.Companion.DEFAULT_SERVER_LIST
 import java.net.InetSocketAddress
 import java.util.*
 
@@ -26,32 +28,64 @@ internal data class ServerAddress(
     fun toSocketAddress(): InetSocketAddress = InetSocketAddress.createUnresolved(host, port)
 }
 
+internal interface ServerList {
+    fun setPreferred(list: Collection<ServerAddress>)
+    fun getPreferred(): Set<ServerAddress>
+
+    fun refresh()
+
+    /**
+     * [Poll][Queue.poll] from current address list. Returns `null` if current address list is empty.
+     */
+    fun pollCurrent(): ServerAddress?
+
+    /**
+     * [Poll][Queue.poll] from current address list, before which the list is filled with preferred addresses or default list if empty.
+     */
+    fun pollAny(): ServerAddress
+
+    companion object : ComponentKey<ServerList> {
+        val DEFAULT_SERVER_LIST: Set<ServerAddress> =
+            """msfwifi.3g.qq.com:8080, 14.215.138.110:8080, 113.96.12.224:8080,
+                |157.255.13.77:14000, 120.232.18.27:443, 
+                |183.3.235.162:14000, 163.177.89.195:443, 183.232.94.44:80, 
+                |203.205.255.224:8080, 203.205.255.221:8080""".trimMargin()
+                .splitToSequence(",").filterNot(String::isBlank)
+                .map { it.trim() }
+                .map {
+                    val host = it.substringBefore(':')
+                    val port = it.substringAfter(':').toInt()
+                    ServerAddress(host, port)
+                }.shuffled().toMutableSet()
+    }
+}
+
 /**
  * Queue of servers. Pop each time when trying to connect.
  */
-internal class ServerList(
+internal class ServerListImpl(
     initial: Collection<ServerAddress> = emptyList()
-) {
+) : ServerList {
     @Volatile
-    private var preferred: Set<ServerAddress> = DefaultServerList
+    private var preferred: Set<ServerAddress> = DEFAULT_SERVER_LIST
 
     @Volatile
     private var current: Queue<ServerAddress> = ArrayDeque(initial)
 
     @Synchronized
-    fun setPreferred(list: Collection<ServerAddress>) {
+    override fun setPreferred(list: Collection<ServerAddress>) {
         require(list.isNotEmpty()) { "list cannot be empty." }
         preferred = list.toSet()
     }
 
-    fun getPreferred() = preferred
+    override fun getPreferred() = preferred
 
     init {
         refresh()
     }
 
     @Synchronized
-    fun refresh() {
+    override fun refresh() {
         current = preferred.toCollection(ArrayDeque(current.size))
         check(current.isNotEmpty()) {
             "Internal error: failed to fill server list. No server available."
@@ -62,7 +96,7 @@ internal class ServerList(
      * [Poll][Queue.poll] from current address list. Returns `null` if current address list is empty.
      */
     @Synchronized
-    fun pollCurrent(): ServerAddress? {
+    override fun pollCurrent(): ServerAddress? {
         return current.poll()
     }
 
@@ -70,23 +104,8 @@ internal class ServerList(
      * [Poll][Queue.poll] from current address list, before which the list is filled with preferred addresses or default list if empty.
      */
     @Synchronized
-    fun pollAny(): ServerAddress {
+    override fun pollAny(): ServerAddress {
         if (current.isEmpty()) refresh()
         return current.remove()
     }
-
-    companion object {
-        internal val DefaultServerList: Set<ServerAddress> =
-            """msfwifi.3g.qq.com:8080, 14.215.138.110:8080, 113.96.12.224:8080,
-                |157.255.13.77:14000, 120.232.18.27:443, 
-                |183.3.235.162:14000, 163.177.89.195:443, 183.232.94.44:80, 
-                |203.205.255.224:8080, 203.205.255.221:8080""".trimMargin()
-                .splitToSequence(",").filterNot(String::isBlank)
-                .map { it.trim() }
-                .map {
-                    val host = it.substringBefore(':')
-                    val port = it.substringAfter(':').toInt()
-                    ServerAddress(host, port)
-                }.shuffled().toMutableSet()
-    }
 }

+ 21 - 6
mirai-core/src/commonMain/kotlin/network/handler/components/SsoProcessor.kt

@@ -13,6 +13,7 @@ import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.QQAndroidClient
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
 import net.mamoe.mirai.internal.network.handler.context.AccountSecretsImpl
 import net.mamoe.mirai.internal.network.handler.context.SsoProcessorContext
 import net.mamoe.mirai.internal.network.handler.context.SsoSession
@@ -31,6 +32,20 @@ import net.mamoe.mirai.utils.LoginSolver
 import net.mamoe.mirai.utils.info
 import net.mamoe.mirai.utils.withExceptionCollector
 
+internal interface SsoProcessor {
+    val ssoContext: SsoProcessorContext
+    val client: QQAndroidClient
+    val ssoSession: SsoSession
+
+    /**
+     * Do login. Throws [LoginFailedException] if failed
+     */
+    @Throws(LoginFailedException::class)
+    suspend fun login(handler: NetworkHandler)
+
+    companion object : ComponentKey<SsoProcessor>
+}
+
 /**
  * Strategy that performs the process of single sing-on (SSO). (login)
  *
@@ -38,19 +53,19 @@ import net.mamoe.mirai.utils.withExceptionCollector
  *
  * Used by [NettyNetworkHandler.StateConnecting].
  */
-internal class SsoProcessor(
-    internal val ssoContext: SsoProcessorContext,
-) {
+internal class SsoProcessorImpl(
+    override val ssoContext: SsoProcessorContext,
+) : SsoProcessor {
     @Volatile
-    internal var client = createClient(ssoContext.bot)
+    override var client = createClient(ssoContext.bot)
 
-    internal val ssoSession: SsoSession get() = client
+    override val ssoSession: SsoSession get() = client
 
     /**
      * Do login. Throws [LoginFailedException] if failed
      */
     @Throws(LoginFailedException::class)
-    suspend fun login(handler: NetworkHandler) = withExceptionCollector {
+    override suspend fun login(handler: NetworkHandler) = withExceptionCollector {
         if (client.wLoginSigInfoInitialized) {
             kotlin.runCatching {
                 FastLoginImpl(handler).doLogin()

+ 5 - 10
mirai-core/src/commonMain/kotlin/network/handler/context/NetworkHandlerContext.kt

@@ -11,31 +11,26 @@ package net.mamoe.mirai.internal.network.handler.context
 
 import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
-import net.mamoe.mirai.internal.network.handler.components.SsoProcessor
-import net.mamoe.mirai.internal.network.handler.state.StateObserver
+import net.mamoe.mirai.internal.network.handler.component.ComponentStorage
 import net.mamoe.mirai.utils.MiraiLogger
 
 /**
  * Immutable context for [NetworkHandler]
  * @see NetworkHandlerContextImpl
  */
-internal interface NetworkHandlerContext {
+internal interface NetworkHandlerContext : ComponentStorage {
     val bot: QQAndroidBot
     // however migration requires a major change.
 
     val logger: MiraiLogger
-    val ssoProcessor: SsoProcessor
-
-    val stateObserver: StateObserver?
 }
 
 internal class NetworkHandlerContextImpl(
     override val bot: QQAndroidBot,
-    override val ssoProcessor: SsoProcessor,
     override val logger: MiraiLogger,
-    override val stateObserver: StateObserver?,
-) : NetworkHandlerContext {
+    storage: ComponentStorage
+) : NetworkHandlerContext, ComponentStorage by storage {
     override fun toString(): String {
-        return "NetworkHandlerContextImpl(bot=${bot.id}, stateObserver=$stateObserver)"
+        return "NetworkHandlerContextImpl(storage=$)"
     }
 }

+ 7 - 4
mirai-core/src/commonMain/kotlin/network/handler/impl/netty/NettyNetworkHandler.kt

@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.consumeAsFlow
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport
-import net.mamoe.mirai.internal.network.handler.components.PacketCodec
+import net.mamoe.mirai.internal.network.handler.components.PacketCodecImpl
 import net.mamoe.mirai.internal.network.handler.components.RawIncomingPacket
 import net.mamoe.mirai.internal.network.handler.components.SsoProcessor
 import net.mamoe.mirai.internal.network.handler.context.NetworkHandlerContext
@@ -63,7 +63,10 @@ internal class NettyNetworkHandler(
     private inner class ByteBufToIncomingPacketDecoder : SimpleChannelInboundHandler<ByteBuf>(ByteBuf::class.java) {
         override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
             ctx.fireChannelRead(msg.toReadPacket().use { packet ->
-                PacketCodec.decodeRaw(context.ssoProcessor.ssoSession, packet)
+                PacketCodecImpl().decodeRaw(
+                    context[SsoProcessor].ssoSession,
+                    packet
+                ) // TODO: 2021/4/17 components integration
             })
         }
     }
@@ -124,7 +127,7 @@ internal class NettyNetworkHandler(
             launch(CoroutineName("PacketDecodePipeline processor")) {
                 // 'single thread' processor
                 channel.consumeAsFlow().collect { raw ->
-                    val result = PacketCodec.processBody(context.bot, raw)
+                    val result = PacketCodecImpl().processBody(context.bot, raw) // TODO: 2021/4/17 components
                     if (result == null) {
                         collectUnknownPacket(raw)
                     } else collectReceived(result)
@@ -175,7 +178,7 @@ internal class NettyNetworkHandler(
 
         private val connectResult = async {
             val connection = connection.await()
-            context.ssoProcessor.login(this@NettyNetworkHandler)
+            context[SsoProcessor].login(this@NettyNetworkHandler)
             setStateForJobCompletion { StateOK(connection) }
         }.apply {
             invokeOnCompletion { error ->

+ 3 - 0
mirai-core/src/commonMain/kotlin/network/handler/state/StateObserver.kt

@@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.network.handler.state
 
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
 
 /**
  * Stateless observer of state changes.
@@ -48,4 +49,6 @@ internal interface StateObserver {
     ) {
 
     }
+
+    companion object : ComponentKey<StateObserver>
 }

+ 8 - 7
mirai-core/src/commonTest/kotlin/network/ServerListTest.kt

@@ -11,28 +11,29 @@ package net.mamoe.mirai.internal.network
 
 import net.mamoe.mirai.internal.network.handler.components.ServerAddress
 import net.mamoe.mirai.internal.network.handler.components.ServerList
+import net.mamoe.mirai.internal.network.handler.components.ServerListImpl
 import kotlin.test.*
 
 internal class ServerListTest {
 
     @Test
     fun canInitializeDefaults() {
-        assertNotEquals(0, ServerList.DefaultServerList.size)
+        assertNotEquals(0, ServerList.DEFAULT_SERVER_LIST.size)
     }
 
     @Test
     fun `can poll current for initial`() {
-        assertNotNull(ServerList().pollCurrent())
+        assertNotNull(ServerListImpl().pollCurrent())
     }
 
     @Test
     fun `not empty for initial`() {
-        assertNotNull(ServerList().pollAny())
+        assertNotNull(ServerListImpl().pollAny())
     }
 
     @Test
     fun `poll current will end with null`() {
-        val instance = ServerList()
+        val instance = ServerListImpl()
         repeat(100) {
             instance.pollCurrent()
         }
@@ -41,7 +42,7 @@ internal class ServerListTest {
 
     @Test
     fun `poll any is always not null`() {
-        val instance = ServerList()
+        val instance = ServerListImpl()
         repeat(100) {
             instance.pollAny()
         }
@@ -51,13 +52,13 @@ internal class ServerListTest {
     @Test
     fun `preferred cannot be empty`() {
         assertFailsWith<IllegalArgumentException> {
-            ServerList().setPreferred(emptyList())
+            ServerListImpl().setPreferred(emptyList())
         }
     }
 
     @Test
     fun `use preferred`() {
-        val instance = ServerList()
+        val instance = ServerListImpl()
         val addr = ServerAddress("test", 1)
         instance.setPreferred(listOf(addr))
         repeat(100) {

+ 33 - 0
mirai-core/src/commonTest/kotlin/network/component/ComponentKeyTest.kt

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.component
+
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+private class TestComponent {
+    companion object : ComponentKey<TestComponent>
+}
+
+internal class ComponentKeyTest {
+
+    @Test
+    fun testComponentName() {
+        assertEquals("TestComponent", TestComponent.componentName(false))
+        assertEquals(TestComponent::class.qualifiedName!!, TestComponent.smartToString(true))
+    }
+
+    @Test
+    fun `test smartToString`() {
+        assertEquals("ComponentKey<TestComponent>", TestComponent.smartToString(false))
+        assertEquals("ComponentKey<${TestComponent::class.qualifiedName!!}>", TestComponent.smartToString(true))
+    }
+}

+ 85 - 0
mirai-core/src/commonTest/kotlin/network/component/ComponentStorageTest.kt

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.component
+
+import net.mamoe.mirai.internal.network.handler.component.ComponentKey
+import net.mamoe.mirai.internal.network.handler.component.ConcurrentComponentStorage
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+private data class TestComponent2(
+    val value: Int
+) {
+    companion object : ComponentKey<TestComponent2>
+}
+
+private data class TestComponent3(
+    val value: Int
+) {
+    companion object : ComponentKey<TestComponent3>
+}
+
+internal class ComponentStorageTest {
+    @Test
+    fun `can put component`() {
+        val storage = ConcurrentComponentStorage().apply {
+            set(TestComponent2, TestComponent2(1))
+        }
+        assertEquals(1, storage.size)
+    }
+
+    @Test
+    fun `can put multiple components with different key`() {
+        val storage = ConcurrentComponentStorage().apply {
+            set(TestComponent2, TestComponent2(1))
+            set(TestComponent3, TestComponent3(1))
+        }
+        assertEquals(2, storage.size)
+    }
+
+    @Test
+    fun `can get component`() {
+        val storage = ConcurrentComponentStorage().apply {
+            set(TestComponent2, TestComponent2(1))
+        }
+        assertEquals(1, storage.size)
+        assertEquals(TestComponent2(1), storage[TestComponent2])
+    }
+
+    @Test
+    fun `can override component with same key`() {
+        val storage = ConcurrentComponentStorage().apply {
+            set(TestComponent2, TestComponent2(1))
+            set(TestComponent2, TestComponent2(2))
+        }
+        assertEquals(1, storage.size)
+        assertEquals(TestComponent2(2), storage[TestComponent2])
+    }
+
+    @Test
+    fun `test toString non debug`() {
+        val storage = ConcurrentComponentStorage(showAllComponents = false).apply {
+            set(TestComponent2, TestComponent2(1))
+        }
+        assertEquals("ConcurrentComponentStorage(size=1)", storage.toString())
+    }
+
+    @Test
+    fun `test toString debugging`() {
+        val storage = ConcurrentComponentStorage(showAllComponents = true).apply {
+            set(TestComponent2, TestComponent2(1))
+        }
+        assertEquals(
+            """ConcurrentComponentStorage(size=1) {
+            |  TestComponent2: TestComponent2(value=1)
+            |}""".trimMargin(), storage.toString()
+        )
+    }
+}

+ 14 - 7
mirai-core/src/commonTest/kotlin/network/handler/testUtils.kt

@@ -12,7 +12,10 @@ package net.mamoe.mirai.internal.network.handler
 import kotlinx.coroutines.CompletableDeferred
 import net.mamoe.mirai.internal.MockBot
 import net.mamoe.mirai.internal.QQAndroidBot
+import net.mamoe.mirai.internal.network.handler.component.ComponentStorage
+import net.mamoe.mirai.internal.network.handler.component.ConcurrentComponentStorage
 import net.mamoe.mirai.internal.network.handler.components.SsoProcessor
+import net.mamoe.mirai.internal.network.handler.components.SsoProcessorImpl
 import net.mamoe.mirai.internal.network.handler.context.NetworkHandlerContext
 import net.mamoe.mirai.internal.network.handler.context.SsoProcessorContextImpl
 import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver
@@ -23,16 +26,20 @@ import net.mamoe.mirai.utils.MiraiLogger
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.atomic.AtomicInteger
 
-
 internal class TestNetworkHandlerContext(
     override val bot: QQAndroidBot = MockBot(),
     override val logger: MiraiLogger = MiraiLogger.create("Test"),
-    override val ssoProcessor: SsoProcessor = SsoProcessor(SsoProcessorContextImpl(bot)),
-    override val stateObserver: StateObserver? = SafeStateObserver(
-        LoggingStateObserver(MiraiLogger.create("States")),
-        MiraiLogger.create("StateObserver errors")
-    ),
-) : NetworkHandlerContext
+    components: ComponentStorage = ConcurrentComponentStorage().apply {
+        set(SsoProcessor, SsoProcessorImpl(SsoProcessorContextImpl(bot)))
+        set(
+            StateObserver,
+            SafeStateObserver(
+                LoggingStateObserver(MiraiLogger.create("States")),
+                MiraiLogger.create("StateObserver errors")
+            )
+        )
+    }
+) : NetworkHandlerContext, ComponentStorage by components
 
 internal open class TestNetworkHandler(
     context: NetworkHandlerContext,