From 4d7826f3d9fe3e891070bc2c85c97837ea805aa4 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 24 Jul 2020 20:11:10 +0800 Subject: [PATCH] API stabilization: rearrange implementations --- backend/mirai-console/build.gradle.kts | 1 + .../net/mamoe/mirai/console/MiraiConsole.kt | 76 ++--- .../mirai/console/MiraiConsoleFrontEnd.kt | 4 + .../console/MiraiConsoleImplementation.kt | 78 +++++ .../mirai/console/command/BuiltInCommands.kt | 1 - .../mirai/console/command/CommandSender.kt | 4 +- .../console/command/internal/internal.kt | 1 - .../net/mamoe/mirai/console/plugin/Plugin.kt | 19 ++ .../plugin/internal/JarPluginLoaderImpl.kt | 109 +++++++ .../plugin/internal/JvmPluginInternal.kt | 2 +- .../console/plugin/jvm/AbstractJvmPlugin.kt | 5 +- .../console/plugin/jvm/JarPluginLoader.kt | 109 +------ .../mirai/console/plugin/jvm/JvmPlugin.kt | 12 +- .../mamoe/mirai/console/setting/Setting.kt | 71 ++++- .../mirai/console/setting/SettingStorage.kt | 283 +++++++----------- .../net/mamoe/mirai/console/setting/Value.kt | 54 +++- .../internal/Setting.value composite impl.kt | 2 +- .../internal/SettingStorage internal.kt | 190 ++++++++++++ .../console/setting/internal/asKClass.kt | 35 +++ .../mamoe/mirai/console/utils/BotManagers.kt | 9 +- .../mirai/console/utils/JavaFriendlyAPI.kt | 7 +- .../mirai/console/utils/ResourceContainer.kt | 32 +- .../mamoe/mirai/console/TestMiraiConosle.kt | 5 +- .../mirai/console/setting/SettingTest.kt | 6 + frontend/mirai-console-pure/build.gradle.kts | 1 + .../console/pure/MiraiConsoleFrontEndPure.kt | 2 +- ...e.kt => MiraiConsoleImplementationPure.kt} | 23 +- .../console/pure/MiraiConsolePureLoader.kt | 5 +- 28 files changed, 726 insertions(+), 420 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JarPluginLoaderImpl.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingStorage internal.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/asKClass.kt rename frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/{MiraiConsolePure.kt => MiraiConsoleImplementationPure.kt} (78%) diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 3a056e285..d76f2360b 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -42,6 +42,7 @@ kotlin { useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") + useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI") useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index ebb265e47..337799227 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION") +@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "unused") @file:OptIn(ConsoleInternalAPI::class) package net.mamoe.mirai.console @@ -77,32 +77,22 @@ public interface MiraiConsole : CoroutineScope { @ConsoleExperimentalAPI public fun newLogger(identity: String?): MiraiLogger - public companion object INSTANCE : MiraiConsole by MiraiConsoleInternal -} - -public class IllegalMiraiConsoleImplementationError( - override val message: String? -) : Error() - -/** - * 获取 [MiraiConsole] 的 [Job] - */ -public val MiraiConsole.job: Job - get() = this.coroutineContext[Job] ?: error("Internal error: Job not found in MiraiConsole.coroutineContext") - -//// internal - - -internal object MiraiConsoleInitializer { - internal lateinit var instance: IMiraiConsole - - /** 由前端调用 */ - internal fun init(instance: IMiraiConsole) { - this.instance = instance - MiraiConsoleInternal.doStart() + public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge { + /** + * 获取 [MiraiConsole] 的 [Job] + */ // MiraiConsole.INSTANCE.getJob() + public val job: Job + get() = MiraiConsole.coroutineContext[Job] + ?: error("Internal error: Job not found in MiraiConsole.coroutineContext") } } +public class IllegalMiraiConsoleImplementationError @JvmOverloads constructor( + public override val message: String? = null, + public override val cause: Throwable? = null +) : Error() + + internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic val buildDate: Date = Date(1595136353901L) // 2020-07-19 13:25:53 @@ -110,12 +100,13 @@ internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mira } /** - * mirai 控制台实例. + * [MiraiConsole] 公开 API 与前端实现的连接桥. */ -internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConsole { +internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation, + MiraiConsole { override val pluginCenter: PluginCenter get() = CuiPluginCenter - private val instance: IMiraiConsole get() = MiraiConsoleInitializer.instance + private val instance: MiraiConsoleImplementation get() = MiraiConsoleImplementation.instance override val buildDate: Date get() = MiraiConsoleBuildConstants.buildDate override val version: String get() = MiraiConsoleBuildConstants.version override val rootDir: File get() = instance.rootDir @@ -147,7 +138,7 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso if (coroutineContext[Job] == null) { throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") } - job.invokeOnCompletion { + MiraiConsole.job.invokeOnCompletion { Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } } @@ -165,35 +156,6 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso } } - -// 前端使用 -internal interface IMiraiConsole : CoroutineScope { - /** - * Console 运行路径 - */ - val rootDir: File - - /** - * Console 前端接口 - */ - val frontEnd: MiraiConsoleFrontEnd - - /** - * 与前端交互所使用的 Logger - */ - val mainLogger: MiraiLogger - - /** - * 内建加载器列表, 一般需要包含 [JarPluginLoader] - */ - val builtInPluginLoaders: List> - - val consoleCommandSender: ConsoleCommandSender - - val settingStorageForJarPluginLoader: SettingStorage - val settingStorageForBuiltIns: SettingStorage -} - /** * Included in kotlin stdlib 1.4 */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt index 36bb0c741..1703c38df 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt @@ -10,13 +10,17 @@ package net.mamoe.mirai.console import net.mamoe.mirai.Bot +import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiLogger /** * 只需要实现一个这个传入 MiraiConsole 就可以绑定 UI 层与 Console 层 + * * 需要保证线程安全 */ +@ConsoleExperimentalAPI +@ConsoleFrontEndImplementation public interface MiraiConsoleFrontEnd { /** * 名称 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt new file mode 100644 index 000000000..2ff1c6277 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2020 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 + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.console + +import kotlinx.atomicfu.locks.withLock +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.plugin.PluginLoader +import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader +import net.mamoe.mirai.console.setting.SettingStorage +import net.mamoe.mirai.utils.MiraiLogger +import java.io.File +import java.util.concurrent.locks.ReentrantLock +import kotlin.annotation.AnnotationTarget.* + + +/** + * 标记一个仅用于 [MiraiConsole] 前端实现的 API. 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用. + * + * 前端实现时 + */ +@Retention(AnnotationRetention.SOURCE) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) +@MustBeDocumented +public annotation class ConsoleFrontEndImplementation + +/** + * [MiraiConsole] 前端实现, 需低啊用 + */ +@ConsoleFrontEndImplementation +public interface MiraiConsoleImplementation : CoroutineScope { + /** + * Console 运行路径 + */ + public val rootDir: File + + /** + * Console 前端接口 + */ + public val frontEnd: MiraiConsoleFrontEnd + + /** + * 与前端交互所使用的 Logger + */ + public val mainLogger: MiraiLogger + + /** + * 内建加载器列表, 一般需要包含 [JarPluginLoader] + */ + public val builtInPluginLoaders: List> + + public val consoleCommandSender: ConsoleCommandSender + + public val settingStorageForJarPluginLoader: SettingStorage + public val settingStorageForBuiltIns: SettingStorage + + public companion object { + internal lateinit var instance: MiraiConsoleImplementation + private val initLock = ReentrantLock() + + /** 由前端调用, 初始化 [MiraiConsole] 实例, 并 */ + @JvmStatic + public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock { + this@Companion.instance = this + MiraiConsoleImplementationBridge.doStart() + } + } +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index c6a020117..d4eac3ff8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.Bot import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.job import net.mamoe.mirai.console.stacktraceString import net.mamoe.mirai.event.selectMessagesUnit import net.mamoe.mirai.utils.DirectoryLogger diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 9d628fb42..b36e0c99a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -13,7 +13,7 @@ package net.mamoe.mirai.console.command import kotlinx.coroutines.runBlocking import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.MiraiConsoleInternal +import net.mamoe.mirai.console.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI import net.mamoe.mirai.console.utils.JavaFriendlyAPI import net.mamoe.mirai.contact.* @@ -68,7 +68,7 @@ public abstract class ConsoleCommandSender internal constructor() : CommandSende public final override val bot: Nothing? get() = null public companion object { - internal val instance get() = MiraiConsoleInternal.consoleCommandSender + internal val instance get() = MiraiConsoleImplementationBridge.consoleCommandSender } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt index 4338a946e..4df9b9f1d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.console.command.internal import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.job import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.event.Listener diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt index e2eeec1be..a56ef852a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin @@ -29,6 +31,23 @@ public interface Plugin { public val loader: PluginLoader<*, *> } +/** + * 禁用这个插件 + * + * @see PluginLoader.disable + */ +public fun Plugin.disable(): Unit = safeLoader.disable(this) + +/** + * 启用这个插件 + * + * @see PluginLoader.enable + */ +public fun Plugin.enable(): Unit = safeLoader.enable(this) + +/** + * 经过泛型类型转换的 [PluginLoader] + */ @get:JvmSynthetic @Suppress("UNCHECKED_CAST") public inline val

P.safeLoader: PluginLoader diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JarPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JarPluginLoaderImpl.kt new file mode 100644 index 000000000..6a896a22d --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JarPluginLoaderImpl.kt @@ -0,0 +1,109 @@ +package net.mamoe.mirai.console.plugin.internal + + +import kotlinx.coroutines.* +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader +import net.mamoe.mirai.console.plugin.PluginLoadException +import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription +import net.mamoe.mirai.console.setting.SettingStorage +import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.yamlkt.Yaml +import java.io.File +import java.net.URI +import kotlin.coroutines.CoroutineContext +import kotlin.reflect.full.createInstance + +internal object JarPluginLoaderImpl : + AbstractFilePluginLoader(".jar"), + CoroutineScope, + JarPluginLoader { + + private val logger: MiraiLogger = MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!) + + @ConsoleExperimentalAPI + override val settingStorage: SettingStorage + get() = MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader + + override val coroutineContext: CoroutineContext = + MiraiConsole.coroutineContext + + SupervisorJob(MiraiConsole.coroutineContext[Job]) + + CoroutineExceptionHandler { _, throwable -> + logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) + } + + private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) + + init { // delayed + coroutineContext[Job]!!.invokeOnCompletion { + classLoader.clear() + } + } + + @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter + override val JvmPlugin.description: JvmPluginDescription + get() = this.description + + override fun Sequence.mapToDescription(): List { + return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() } + .mapNotNull { (file, url) -> + kotlin.runCatching { + url.readText() + }.fold( + onSuccess = { yaml -> + Yaml.nonStrict.parse(JvmPluginDescription.serializer(), yaml) + }, + onFailure = { + logger.error("Cannot load plugin file ${file.name}", it) + null + } + )?.also { it._file = file } + } + } + + @Suppress("RemoveExplicitTypeArguments") // until Kotlin 1.4 NI + @Throws(PluginLoadException::class) + override fun load(description: JvmPluginDescription): JvmPlugin = + description.runCatching { + ensureActive() + val main = classLoader.loadPluginMainClassByJarFile( + pluginName = name, + mainClass = mainClassName, + jarFile = file + ).kotlin.run { + objectInstance + ?: kotlin.runCatching { createInstance() }.getOrNull() + ?: (java.constructors + java.declaredConstructors) + .firstOrNull { it.parameterCount == 0 } + ?.apply { kotlin.runCatching { isAccessible = true } } + ?.newInstance() + } ?: error("No Kotlin object or public no-arg constructor found") + + check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" } + + if (main is JvmPluginInternal) { + main._description = description + main.internalOnLoad() + } else main.onLoad() + main + }.getOrElse { + throw PluginLoadException("Exception while loading ${description.name}", it) + } + + override fun enable(plugin: JvmPlugin) { + ensureActive() + if (plugin is JvmPluginInternal) { + plugin.internalOnEnable() + } else plugin.onEnable() + } + + override fun disable(plugin: JvmPlugin) { + if (plugin is JvmPluginInternal) { + plugin.internalOnDisable() + } else plugin.onDisable() + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt index add106751..2d066bece 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt @@ -20,7 +20,7 @@ import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription -import net.mamoe.mirai.console.utils.asResourceContainer +import net.mamoe.mirai.console.utils.ResourceContainer.Companion.asResourceContainer import net.mamoe.mirai.utils.MiraiLogger import java.io.File import java.io.InputStream diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index 73ea700a8..f5655dd2a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -12,7 +12,8 @@ package net.mamoe.mirai.console.plugin.jvm import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal -import net.mamoe.mirai.console.setting.Setting +import net.mamoe.mirai.utils.minutesToSeconds +import net.mamoe.mirai.utils.secondsToMillis import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -27,5 +28,5 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor( ) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) { public final override val name: String get() = this.description.name - public override fun getSetting(clazz: Class): T = loader.settingStorage.load(this, clazz) + public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToSeconds } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt index 70387e446..35b2debf4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt @@ -9,120 +9,21 @@ package net.mamoe.mirai.console.plugin.jvm -import kotlinx.coroutines.* -import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.MiraiConsoleInternal -import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader +import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.plugin.FilePluginLoader -import net.mamoe.mirai.console.plugin.PluginLoadException -import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal -import net.mamoe.mirai.console.plugin.internal.PluginsLoader +import net.mamoe.mirai.console.plugin.internal.JarPluginLoaderImpl import net.mamoe.mirai.console.setting.SettingStorage import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.yamlkt.Yaml -import java.io.File -import java.net.URI -import kotlin.coroutines.CoroutineContext -import kotlin.reflect.full.createInstance /** * 内建的 Jar (JVM) 插件加载器 */ public interface JarPluginLoader : CoroutineScope, FilePluginLoader { + /** + * [JvmPlugin.loadSetting] 默认使用的实例 + */ @ConsoleExperimentalAPI public val settingStorage: SettingStorage public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl -} - - -internal object JarPluginLoaderImpl : - AbstractFilePluginLoader(".jar"), - CoroutineScope, - JarPluginLoader { - - private val logger: MiraiLogger = MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!) - - @ConsoleExperimentalAPI - override val settingStorage: SettingStorage - get() = MiraiConsoleInternal.settingStorageForJarPluginLoader - - override val coroutineContext: CoroutineContext = - MiraiConsole.coroutineContext + - SupervisorJob(MiraiConsole.coroutineContext[Job]) + - CoroutineExceptionHandler { _, throwable -> - logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) - } - - private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) - - init { // delayed - coroutineContext[Job]!!.invokeOnCompletion { - classLoader.clear() - } - } - - @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter - override val JvmPlugin.description: JvmPluginDescription - get() = this.description - - override fun Sequence.mapToDescription(): List { - return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() } - .mapNotNull { (file, url) -> - kotlin.runCatching { - url.readText() - }.fold( - onSuccess = { yaml -> - Yaml.nonStrict.parse(JvmPluginDescription.serializer(), yaml) - }, - onFailure = { - logger.error("Cannot load plugin file ${file.name}", it) - null - } - )?.also { it._file = file } - } - } - - @Suppress("RemoveExplicitTypeArguments") // until Kotlin 1.4 NI - @Throws(PluginLoadException::class) - override fun load(description: JvmPluginDescription): JvmPlugin = - description.runCatching { - ensureActive() - val main = classLoader.loadPluginMainClassByJarFile( - pluginName = name, - mainClass = mainClassName, - jarFile = file - ).kotlin.run { - objectInstance - ?: kotlin.runCatching { createInstance() }.getOrNull() - ?: (java.constructors + java.declaredConstructors) - .firstOrNull { it.parameterCount == 0 } - ?.apply { kotlin.runCatching { isAccessible = true } } - ?.newInstance() - } ?: error("No Kotlin object or public no-arg constructor found") - - check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" } - - if (main is JvmPluginInternal) { - main._description = description - main.internalOnLoad() - } else main.onLoad() - main - }.getOrElse { - throw PluginLoadException("Exception while loading ${description.name}", it) - } - - override fun enable(plugin: JvmPlugin) { - ensureActive() - if (plugin is JvmPluginInternal) { - plugin.internalOnEnable() - } else plugin.onEnable() - } - - override fun disable(plugin: JvmPlugin) { - if (plugin is JvmPluginInternal) { - plugin.internalOnDisable() - } else plugin.onDisable() - } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt index 1e5835556..93ace6ee0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt @@ -41,13 +41,15 @@ public interface JvmPlugin : Plugin, CoroutineScope, public val description: JvmPluginDescription /** 所属插件加载器实例 */ - public override val loader: JarPluginLoader get() = JarPluginLoader + @JvmDefault + public override val loader: JarPluginLoader + get() = JarPluginLoader /** * 获取一个 [Setting] 实例 */ - public fun getSetting(clazz: Class): T - + @JvmDefault + public fun loadSetting(clazz: Class): T = loader.settingStorage.load(this, clazz) // TODO: 2020/7/11 document onLoad, onEnable, onDisable @JvmDefault @@ -64,7 +66,7 @@ public interface JvmPlugin : Plugin, CoroutineScope, } @JvmSynthetic -public inline fun JvmPlugin.getSetting(clazz: KClass): T = this.getSetting(clazz.java) +public inline fun JvmPlugin.loadSetting(clazz: KClass): T = this.loadSetting(clazz.java) @JvmSynthetic -public inline fun JvmPlugin.getSetting(): T = this.getSetting(T::class) \ No newline at end of file +public inline fun JvmPlugin.loadSetting(): T = this.loadSetting(T::class) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt index 0dde7d9d5..0bb578b57 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -12,7 +12,11 @@ package net.mamoe.mirai.console.setting import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.plugin.jvm.loadSetting import net.mamoe.mirai.console.setting.internal.* +import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI +import net.mamoe.mirai.console.utils.ConsoleInternalAPI import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KProperty import kotlin.reflect.KType @@ -23,16 +27,31 @@ import kotlin.reflect.KType * * 例: * ``` - * class MySetting : Setting() { - * + * @SerialName("accounts") + * object AccountSettings : Setting by ... { + * @SerialName("info") + * val map: Map by value("a" to "b") * } * ``` + * + * 将被保存为配置 (YAML 作为示例): + * ```yaml + * accounts: + * info: + * a: b + * ``` */ -// TODO: 2020/6/26 document public typealias SerialName = kotlinx.serialization.SerialName -// TODO: 2020/6/26 document +/** + * [Setting] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动. + * + * @see Setting + */ public abstract class AbstractSetting : Setting, SettingImpl() { + /** + * 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪. + */ public final override operator fun SerializerAwareValue.provideDelegate( thisRef: Any?, property: KProperty<*> @@ -42,21 +61,56 @@ public abstract class AbstractSetting : Setting, SettingImpl() { return this } - public final override val updaterSerializer: KSerializer get() = super.updaterSerializer + /** + * 值更新序列化器. 仅供内部使用 + */ + @ConsoleInternalAPI + public final override val updaterSerializer: KSerializer + get() = super.updaterSerializer + + /** + * 当所属于这个 [Setting] 的 [Value] 的 [值][Value.value] 被修改时被调用. + */ + public abstract override fun onValueChanged(value: Value<*>) } -// TODO: 2020/6/26 document +/** + * 一个配置对象. 可包含对多个 [Value] 的值变更的跟踪. + * + * 在 [JvmPlugin] 的实现方式: + * ``` + * object PluginMain : KotlinPlugin() + * + * object AccountSettings : Setting by PluginMain.getSetting() { + * val map: Map by value("a" to "b") + * } + * ``` + * + * @see JvmPlugin.loadSetting 通过 [JvmPlugin] 获取指定 [Setting] 实例. + */ public interface Setting { - // TODO: 2020/6/26 document + /** + * 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪. + */ public operator fun SerializerAwareValue.provideDelegate( thisRef: Any?, property: KProperty<*> ): SerializerAwareValue - // TODO: 2020/6/26 document + /** + * 值更新序列化器. 仅供内部使用 + */ public val updaterSerializer: KSerializer + /** + * 当所属于这个 [Setting] 的 [Value] 的 [值][Value.value] 被修改时被调用. + */ public fun onValueChanged(value: Value<*>) + + /** + * 当这个 [Setting] 被放入一个 [SettingStorage] 时调用 + */ + public fun setStorage(storage: SettingStorage) } //// region Setting_value_primitives CODEGEN //// @@ -101,6 +155,7 @@ public inline fun Setting.value(): SerializerAwareValue = value(T * Creates a [Value] with specified [KType], and set default value. */ @Suppress("UNCHECKED_CAST") +@ConsoleExperimentalAPI public fun Setting.valueFromKType(type: KType, default: T): SerializerAwareValue = (valueFromKTypeImpl(type) as SerializerAwareValue).apply { this.value = default } as SerializerAwareValue diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt index e3019af1a..491758165 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt @@ -1,34 +1,31 @@ -@file:Suppress("NOTHING_TO_INLINE") +@file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST", "unused") package net.mamoe.mirai.console.setting -import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip -import net.mamoe.mirai.console.plugin.internal.updateWhen -import net.mamoe.mirai.console.plugin.jvm.getSetting -import net.mamoe.mirai.console.setting.AutoSaveSettingHolder.AutoSaveSetting -import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI -import net.mamoe.mirai.utils.currentTimeMillis -import net.mamoe.mirai.utils.minutesToSeconds -import net.mamoe.mirai.utils.secondsToMillis -import net.mamoe.yamlkt.Yaml +import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.setting.SettingStorage.Companion.load +import net.mamoe.mirai.console.setting.internal.* import java.io.File import kotlin.reflect.KClass -import kotlin.reflect.KParameter -import kotlin.reflect.full.findAnnotation +import kotlin.reflect.KType +import kotlin.reflect.full.createType /** - * [Setting] 存储容器 + * [Setting] 存储容器. + * + * 此为较低层的 API, 一般插件开发者不会接触. + * + * [JarPluginLoader] 实现一个 [SettingStorage], 用于管理所有 [JvmPlugin] 的 [Setting] 实例. * * @see SettingHolder + * @see JarPluginLoader.settingStorage */ public interface SettingStorage { /** - * 读取一个实例 + * 读取一个实例. 在 [T] 实例创建后 [设置 [SettingStorage]][Setting.setStorage] */ public fun load(holder: SettingHolder, settingClass: Class): T @@ -36,39 +33,82 @@ public interface SettingStorage { * 保存一个实例 */ public fun store(holder: SettingHolder, setting: Setting) -} -// TODO: 2020/7/11 document -public interface MemorySettingStorage : SettingStorage { public companion object { + /** + * 读取一个实例. 在 [T] 实例创建后 [设置 [SettingStorage]][Setting.setStorage] + */ @JvmStatic - @JvmName("create") - public operator fun invoke(): MemorySettingStorage = MemorySettingStorageImpl() + public fun SettingStorage.load(holder: SettingHolder, settingClass: KClass): T = + this.load(holder, settingClass.java) + + /** + * 读取一个实例. 在 [T] 实例创建后 [设置 [SettingStorage]][Setting.setStorage] + */ + @JvmSynthetic + public inline fun SettingStorage.load(holder: SettingHolder): T = + this.load(holder, T::class) } } -// TODO: 2020/7/11 document +/** + * 在内存存储所有 [Setting] 实例的 [SettingStorage]. 在内存数据丢失后相关 [Setting] 实例也会丢失. + */ +public interface MemorySettingStorage : SettingStorage, Map, Setting> { + /** + * 当任一 [Setting] 实例拥有的 [Value] 的值被改变后调用的回调函数. + */ + public /* fun */ interface OnChangedCallback { // TODO: 2020/7/24 make `fun` in 1.4 + public fun onChanged(storage: MemorySettingStorage, value: Value<*>) + + /** + * 无任何操作的 [OnChangedCallback] + * @see OnChangedCallback + */ + public object NoOp : OnChangedCallback { + public override fun onChanged(storage: MemorySettingStorage, value: Value<*>) { + // no-op + } + } + } + + public companion object { + /** + * 创建一个 [MemorySettingStorage] 实例. + * + * @param onChanged 当任一 [Setting] 实例拥有的 [Value] 的值被改变后调用的回调函数. + */ + @JvmStatic + @JvmName("create") + @JvmOverloads + public operator fun invoke(onChanged: OnChangedCallback = OnChangedCallback.NoOp): MemorySettingStorage = + MemorySettingStorageImpl(onChanged) + } +} + +/** + * 在内存存储所有 [Setting] 实例的 [SettingStorage]. + */ public interface MultiFileSettingStorage : SettingStorage { + /** + * 存放 [Setting] 的目录. + */ public val directory: File public companion object { + /** + * 创建一个 [MultiFileSettingStorage] 实例. + * + * @see directory 存放 [Setting] 的目录. + */ @JvmStatic @JvmName("create") public operator fun invoke(directory: File): MultiFileSettingStorage = MultiFileSettingStorageImpl(directory) } } - -// TODO: 2020/7/11 here or companion? -public inline fun SettingStorage.load(holder: SettingHolder, settingClass: KClass): T = - this.load(holder, settingClass.java) - -// TODO: 2020/7/11 here or companion? -public inline fun SettingStorage.load(holder: SettingHolder): T = - this.load(holder, T::class) - /** - * 可以持有相关 [Setting] 的对象. + * 可以持有相关 [Setting] 实例的对象, 作为 [Setting] 实例的拥有者. * * @see SettingStorage.load * @see SettingStorage.store @@ -80,6 +120,28 @@ public interface SettingHolder { * 保存时使用的分类名 */ public val name: String + + /** + * 创建一个 [Setting] 实例. + * + * @see Companion.newSettingInstance + * @see KClass.createType + */ + @JvmDefault + public fun newSettingInstance(type: KType): T = + newSettingInstanceUsingReflection(type) as T + + public companion object { + /** + * 创建一个 [Setting] 实例. + * + * @see SettingHolder.newSettingInstance + */ + @JvmSynthetic + public inline fun SettingHolder.newSettingInstance(): T { + return this.newSettingInstance(typeOf0()) + } + } } /** @@ -94,158 +156,23 @@ public interface AutoSaveSettingHolder : SettingHolder, CoroutineScope { * - 区间的左端点为最小间隔, 一个 [Value] 被修改后, 若此时间段后无其他修改, 将触发自动保存; 若有, 将重新开始计时. * - 区间的右端点为最大间隔, 一个 [Value] 被修改后, 最多不超过这个时间段后就会被保存. * - * 若 [coroutineContext] 含有 [Job], 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. + * 若 [AutoSaveSettingHolder.coroutineContext] 含有 [Job], + * 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. * * @see LongRange Java 用户使用 [LongRange] 的构造器创建 * @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, 如 `3000..50000` */ public val autoSaveIntervalMillis: LongRange - get() = 30.secondsToMillis..10.minutesToSeconds /** - * 链接自动保存的 [Setting]. - * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 - * - * 若 [AutoSaveSettingHolder.coroutineContext] 含有 [Job], 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. - * - * @see getSetting + * 仅支持确切的 [Setting] 类型 */ - public open class AutoSaveSetting(private val owner: AutoSaveSettingHolder, private val storage: SettingStorage) : - AbstractSetting() { - @JvmField - @Volatile - internal var lastAutoSaveJob: Job? = null - - @JvmField - @Volatile - internal var currentFirstStartTime = atomic(0L) - - init { - owner.coroutineContext[Job]?.invokeOnCompletion { doSave() } + @JvmDefault + public override fun newSettingInstance(type: KType): T { + val classifier = type.classifier?.cast>()?.java + require(classifier == Setting::class.java) { + "Cannot create Setting instance. AutoSaveSettingHolder supports only Setting type." } - - private val updaterBlock: suspend CoroutineScope.() -> Unit = { - currentFirstStartTime.updateWhen({ it == 0L }, { currentTimeMillis }) - - delay(owner.autoSaveIntervalMillis.first.coerceAtLeast(1000)) // for safety - - if (lastAutoSaveJob == this.coroutineContext[Job]) { - doSave() - } else { - if (currentFirstStartTime.updateWhen( - { currentTimeMillis - it >= owner.autoSaveIntervalMillis.last }, - { 0 }) - ) doSave() - } - } - - public final override fun onValueChanged(value: Value<*>) { - lastAutoSaveJob = owner.launch(block = updaterBlock) - } - - private fun doSave() = storage.store(owner, this) + return AutoSaveSetting(this) as T // T is always Setting } - -} - -// internal - -internal class MemorySettingStorageImpl : SettingStorage, MemorySettingStorage { - private val list = mutableMapOf, Setting>() - - internal class MemorySettingImpl : AbstractSetting() { - override fun onValueChanged(value: Value<*>) { - // nothing to do - } - } - - @Suppress("UNCHECKED_CAST") - override fun load(holder: SettingHolder, settingClass: Class): T { - return synchronized(list) { - list.getOrPut(settingClass) { - settingClass.kotlin.run { - objectInstance ?: createInstanceOrNull() ?: kotlin.run { - if (settingClass != Setting::class.java) { - throw IllegalArgumentException( - "Cannot create Setting instance. Make sure settingClass is Setting::class.java or a Kotlin's object, " + - "or has a constructor which either has no parameters or all parameters of which are optional" - ) - } - MemorySettingImpl() - } - } - } - } as T - } - - override fun store(holder: SettingHolder, setting: Setting) { - synchronized(list) { - list[setting::class.java] = setting - } - } -} - -public open class MultiFileSettingStorageImpl( - public final override val directory: File -) : SettingStorage, MultiFileSettingStorage { - public override fun load(holder: SettingHolder, settingClass: Class): T = - with(settingClass.kotlin) { - val file = getSettingFile(holder, settingClass::class) - - @Suppress("UNCHECKED_CAST") - val instance = objectInstance ?: this.createInstanceOrNull() ?: kotlin.run { - if (settingClass != Setting::class.java) { - throw IllegalArgumentException( - "Cannot create Setting instance. Make sure settingClass is Setting::class.java or a Kotlin's object, " + - "or has a constructor which either has no parameters or all parameters of which are optional" - ) - } - if (holder is AutoSaveSettingHolder) { - AutoSaveSetting(holder, this@MultiFileSettingStorageImpl) as T? - } else null - } ?: throw IllegalArgumentException( - "Cannot create Setting instance. Make sure 'holder' is a AutoSaveSettingHolder, " + - "or 'setting' is an object or has a constructor which either has no parameters or all parameters of which are optional" - ) - if (file.exists() && file.isFile && file.canRead()) { - Yaml.default.parse(instance.updaterSerializer, file.readText()) - } - instance - } - - protected open fun getSettingFile(holder: SettingHolder, clazz: KClass<*>): File = with(clazz) { - val name = findASerialName() - - val dir = File(directory, holder.name) - if (dir.isFile) { - error("Target directory ${dir.path} for holder $holder is occupied by a file therefore setting $qualifiedNameOrTip can't be saved.") - } - - val file = File(directory, name) - if (file.isDirectory) { - error("Target file $file is occupied by a directory therefore setting $qualifiedNameOrTip can't be saved.") - } - return file - } - - @ConsoleExperimentalAPI - public override fun store(holder: SettingHolder, setting: Setting): Unit = with(setting::class) { - val file = getSettingFile(holder, this) - - if (file.exists() && file.isFile && file.canRead()) { - file.writeText(Yaml.default.stringify(setting.updaterSerializer, Unit)) - } - } -} - -internal fun KClass.createInstanceOrNull(): T? { - val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) } - ?: return null - - return noArgsConstructor.callBy(emptyMap()) -} - -internal fun KClass<*>.findASerialName(): String = - findAnnotation()?.value - ?: qualifiedName - ?: throw IllegalArgumentException("Cannot find a serial name for $this") \ No newline at end of file +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt index 6a6ca5c28..07ff85f8c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt @@ -41,18 +41,22 @@ public class SerializableValue( /** * The serializer used to update and dump [delegate] */ - override val serializer: KSerializer + public override val serializer: KSerializer ) : Value by delegate, SerializerAwareValue { - override fun toString(): String = delegate.toString() -} + public override fun toString(): String = delegate.toString() -public fun Value.serializableValueWith( - serializer: KSerializer -): SerializableValue { - return SerializableValue( - this, - serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) }) - ) + public companion object { + @JvmStatic + @JvmName("create") + public fun Value.serializableValueWith( + serializer: KSerializer + ): SerializableValue { + return SerializableValue( + this, + serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) }) + ) + } + } } /** @@ -60,14 +64,32 @@ public fun Value.serializableValueWith( */ public interface SerializerAwareValue : Value { public val serializer: KSerializer -} -public fun SerializerAwareValue.serialize(format: StringFormat): String { - return format.stringify(this.serializer, Unit) -} + public companion object { + @JvmStatic + @ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization") + public fun SerializerAwareValue.serialize(format: StringFormat): String { + return format.stringify(this.serializer, Unit) + } -public fun SerializerAwareValue.serialize(format: BinaryFormat): ByteArray { - return format.dump(this.serializer, Unit) + @JvmStatic + @ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization") + public fun SerializerAwareValue.serialize(format: BinaryFormat): ByteArray { + return format.dump(this.serializer, Unit) + } + + @JvmStatic + @ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization") + public fun SerializerAwareValue.deserialize(format: StringFormat, value: String) { + format.parse(this.serializer, value) + } + + @JvmStatic + @ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization") + public fun SerializerAwareValue.deserialize(format: BinaryFormat, value: ByteArray) { + format.load(this.serializer, value) + } + } } @JvmSynthetic diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt index 19883207e..a7fc1299a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt @@ -13,9 +13,9 @@ package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.* import kotlinx.serialization.builtins.* +import net.mamoe.mirai.console.setting.SerializableValue.Companion.serializableValueWith import net.mamoe.mirai.console.setting.SerializerAwareValue import net.mamoe.mirai.console.setting.Setting -import net.mamoe.mirai.console.setting.serializableValueWith import net.mamoe.mirai.console.setting.valueFromKType import net.mamoe.yamlkt.YamlDynamicSerializer import net.mamoe.yamlkt.YamlNullableDynamicSerializer diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingStorage internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingStorage internal.kt new file mode 100644 index 000000000..b4c2ec01d --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingStorage internal.kt @@ -0,0 +1,190 @@ +/* + * Copyright 2020 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.console.setting.internal + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip +import net.mamoe.mirai.console.plugin.internal.updateWhen +import net.mamoe.mirai.console.plugin.jvm.loadSetting +import net.mamoe.mirai.console.setting.* +import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI +import net.mamoe.mirai.console.utils.ConsoleInternalAPI +import net.mamoe.mirai.utils.currentTimeMillis +import net.mamoe.yamlkt.Yaml +import java.io.File +import kotlin.reflect.KClass +import kotlin.reflect.KParameter +import kotlin.reflect.full.findAnnotation + + +/** + * 链接自动保存的 [Setting]. + * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 + * + * 若 [AutoSaveSettingHolder.coroutineContext] 含有 [Job], 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. + * + * @see loadSetting + */ +internal open class AutoSaveSetting(private val owner: AutoSaveSettingHolder) : + AbstractSetting() { + private lateinit var storage: SettingStorage + + override fun setStorage(storage: SettingStorage) { + check(!this::storage.isInitialized) { "storage is already initialized" } + this.storage = storage + } + + @JvmField + @Volatile + internal var lastAutoSaveJob: Job? = null + + @JvmField + @Volatile + internal var currentFirstStartTime = atomic(0L) + + init { + owner.coroutineContext[Job]?.invokeOnCompletion { doSave() } + } + + private val updaterBlock: suspend CoroutineScope.() -> Unit = { + currentFirstStartTime.updateWhen({ it == 0L }, { currentTimeMillis }) + + delay(owner.autoSaveIntervalMillis.first.coerceAtLeast(1000)) // for safety + + if (lastAutoSaveJob == this.coroutineContext[Job]) { + doSave() + } else { + if (currentFirstStartTime.updateWhen( + { currentTimeMillis - it >= owner.autoSaveIntervalMillis.last }, + { 0 }) + ) doSave() + } + } + + @Suppress("RedundantVisibilityModifier") + @ConsoleInternalAPI + public final override fun onValueChanged(value: Value<*>) { + lastAutoSaveJob = owner.launch(block = updaterBlock) + } + + private fun doSave() = storage.store(owner, this) +} + +internal class MemorySettingStorageImpl( + private val onChanged: MemorySettingStorage.OnChangedCallback +) : SettingStorage, MemorySettingStorage, + MutableMap, Setting> by mutableMapOf() { + + internal inner class MemorySettingImpl : AbstractSetting() { + @ConsoleInternalAPI + override fun onValueChanged(value: Value<*>) { + onChanged.onChanged(this@MemorySettingStorageImpl, value) + } + + override fun setStorage(storage: SettingStorage) { + check(storage is MemorySettingStorageImpl) { "storage is not MemorySettingStorageImpl" } + } + } + + @Suppress("UNCHECKED_CAST") + override fun load(holder: SettingHolder, settingClass: Class): T = (synchronized(this) { + this.getOrPut(settingClass) { + settingClass.kotlin.run { + objectInstance ?: createInstanceOrNull() ?: kotlin.run { + if (settingClass != Setting::class.java) { + throw IllegalArgumentException( + "Cannot create Setting instance. Make sure settingClass is Setting::class.java or a Kotlin's object, " + + "or has a constructor which either has no parameters or all parameters of which are optional" + ) + } + MemorySettingImpl() + } + } + } + } as T).also { it.setStorage(this) } + + override fun store(holder: SettingHolder, setting: Setting) { + synchronized(this) { + this[setting::class.java] = setting + } + } +} + +@Suppress("RedundantVisibilityModifier") // might be public in the future +internal open class MultiFileSettingStorageImpl( + public final override val directory: File +) : SettingStorage, MultiFileSettingStorage { + public override fun load(holder: SettingHolder, settingClass: Class): T = + with(settingClass.kotlin) { + val file = getSettingFile(holder, settingClass::class) + + @Suppress("UNCHECKED_CAST") + val instance = objectInstance ?: this.createInstanceOrNull() ?: kotlin.run { + if (settingClass != Setting::class.java) { + throw IllegalArgumentException( + "Cannot create Setting instance. Make sure settingClass is Setting::class.java or a Kotlin's object, " + + "or has a constructor which either has no parameters or all parameters of which are optional" + ) + } + if (holder is AutoSaveSettingHolder) { + AutoSaveSetting(holder) as T? + } else null + } ?: throw IllegalArgumentException( + "Cannot create Setting instance. Make sure 'holder' is a AutoSaveSettingHolder, " + + "or 'setting' is an object or has a constructor which either has no parameters or all parameters of which are optional" + ) + if (file.exists() && file.isFile && file.canRead()) { + Yaml.default.parse(instance.updaterSerializer, file.readText()) + } + instance + } + + protected open fun getSettingFile(holder: SettingHolder, clazz: KClass<*>): File = with(clazz) { + val name = findASerialName() + + val dir = File(directory, holder.name) + if (dir.isFile) { + error("Target directory ${dir.path} for holder $holder is occupied by a file therefore setting $qualifiedNameOrTip can't be saved.") + } + + val file = File(directory, name) + if (file.isDirectory) { + error("Target file $file is occupied by a directory therefore setting $qualifiedNameOrTip can't be saved.") + } + return file + } + + @ConsoleExperimentalAPI + public override fun store(holder: SettingHolder, setting: Setting): Unit = with(setting::class) { + val file = getSettingFile(holder, this) + + if (file.exists() && file.isFile && file.canRead()) { + file.writeText(Yaml.default.stringify(setting.updaterSerializer, Unit)) + } + } +} + +@JvmSynthetic +internal fun KClass.createInstanceOrNull(): T? { + val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) } + ?: return null + + return noArgsConstructor.callBy(emptyMap()) +} + +@JvmSynthetic +internal fun KClass<*>.findASerialName(): String = + findAnnotation()?.value + ?: qualifiedName + ?: throw IllegalArgumentException("Cannot find a serial name for $this") \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/asKClass.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/asKClass.kt new file mode 100644 index 000000000..7ae4d8c43 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/asKClass.kt @@ -0,0 +1,35 @@ +package net.mamoe.mirai.console.setting.internal + +import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip +import net.mamoe.mirai.console.setting.Setting +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.full.isSubclassOf + +@Suppress("UNCHECKED_CAST") +internal inline fun KType.asKClass(): KClass { + val clazz = requireNotNull(classifier as? KClass) { "Unsupported classifier: $classifier" } + + val fromClass = arguments[0].type?.classifier as? KClass<*> ?: Any::class + val toClass = T::class + + require(toClass.isSubclassOf(fromClass)) { + "Cannot cast KClass<${fromClass.qualifiedNameOrTip}> to KClass<${toClass.qualifiedNameOrTip}>" + } + + return clazz +} + +internal inline fun newSettingInstanceUsingReflection(type: KType): T { + val classifier = type.asKClass() + + return with(classifier) { + objectInstance + ?: createInstanceOrNull() + ?: throw IllegalArgumentException( + "Cannot create Setting instance. " + + "SettingHolder supports Settings implemented as an object " + + "or the ones with a constructor which either has no parameters or all parameters of which are optional, by default newSettingInstance implementation." + ) + } +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotManagers.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotManagers.kt index 861fc1bc8..4e071e9a9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotManagers.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotManagers.kt @@ -15,9 +15,11 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.MiraiConsoleInternal +import net.mamoe.mirai.console.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.setting.* +import net.mamoe.mirai.console.setting.SettingStorage.Companion.load import net.mamoe.mirai.contact.User +import net.mamoe.mirai.utils.minutesToMillis import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -52,7 +54,10 @@ internal fun CoroutineScope.childScope(context: CoroutineContext = EmptyCoroutin internal object ConsoleBuiltInSettingHolder : AutoSaveSettingHolder, CoroutineScope by MiraiConsole.childScope() { + override val autoSaveIntervalMillis: LongRange + get() = 30.minutesToMillis..60.minutesToMillis override val name: String get() = "ConsoleBuiltIns" } -internal object ConsoleBuiltInSettingStorage : SettingStorage by MiraiConsoleInternal.settingStorageForJarPluginLoader \ No newline at end of file +internal object ConsoleBuiltInSettingStorage : + SettingStorage by MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaFriendlyAPI.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaFriendlyAPI.kt index 10155feb4..84dfe1406 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaFriendlyAPI.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaFriendlyAPI.kt @@ -5,6 +5,7 @@ import kotlin.annotation.AnnotationTarget.* /** * 表明这个 API 是为了让 Java 使用者调用更方便. Kotlin 使用者不应该使用这些 API. */ +@Retention(AnnotationRetention.SOURCE) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(PROPERTY, FUNCTION, TYPE, CLASS) internal annotation class JavaFriendlyAPI @@ -15,7 +16,7 @@ internal annotation class JavaFriendlyAPI * 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 非常不建议在发行版本中使用这些 API. */ -@Retention(AnnotationRetention.SOURCE) +@Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY) @MustBeDocumented @@ -29,10 +30,10 @@ public annotation class ConsoleInternalAPI( * 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 不建议在发行版本中使用这些 API. */ -@Retention(AnnotationRetention.SOURCE) +@Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented public annotation class ConsoleExperimentalAPI( val message: String = "" -) +) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt index 32398402a..1f95b35b7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt @@ -3,12 +3,18 @@ package net.mamoe.mirai.console.utils import net.mamoe.mirai.console.encodeToString +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.utils.ResourceContainer.Companion.asResourceContainer import java.io.InputStream import java.nio.charset.Charset import kotlin.reflect.KClass /** * 资源容器. + * + * 资源容器可能使用 [Class.getResourceAsStream], 也可能使用其他方式, 取决于实现方式. + * + * @see JvmPlugin [JvmPlugin] 实现 [ResourceContainer], 使用 [ResourceContainer.asResourceContainer] */ public interface ResourceContainer { /** @@ -32,27 +38,21 @@ public interface ResourceContainer { public companion object { /** * 使用 [Class.getResourceAsStream] 读取资源文件 - * - * @see asResourceContainer Kotlin 使用 */ @JvmStatic - @JavaFriendlyAPI - public fun byClass(clazz: Class<*>): ResourceContainer = clazz.asResourceContainer() + @JvmName("byClass") + public fun KClass<*>.asResourceContainer(): ResourceContainer = this.java.asResourceContainer() + + /** + * 使用 [Class.getResourceAsStream] 读取资源文件 + */ + @JvmStatic + @JvmName("byClass") + public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this) } } -/** - * 使用 [Class.getResourceAsStream] 读取资源文件 - */ -public fun KClass<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this.java) - -/** - * 使用 [Class.getResourceAsStream] 读取资源文件 - */ -public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this) - - -internal class ClassAsResourceContainer( +private class ClassAsResourceContainer( private val clazz: Class<*> ) : ResourceContainer { override fun getResourceAsStream(name: String): InputStream = clazz.getResourceAsStream(name) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index cc9a02567..6c918fbcd 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout import net.mamoe.mirai.Bot +import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.plugin.DeferredPluginLoader import net.mamoe.mirai.console.plugin.PluginLoader @@ -34,7 +35,7 @@ import kotlin.test.assertNotNull @OptIn(ConsoleInternalAPI::class) fun initTestEnvironment() { - MiraiConsoleInitializer.init(object : IMiraiConsole { + object : MiraiConsoleImplementation { override val rootDir: File = createTempDir() override val frontEnd: MiraiConsoleFrontEnd = object : MiraiConsoleFrontEnd { override val name: String get() = "Test" @@ -52,7 +53,7 @@ fun initTestEnvironment() { override val settingStorageForJarPluginLoader: SettingStorage get() = MemorySettingStorage() override val settingStorageForBuiltIns: SettingStorage get() = MemorySettingStorage() override val coroutineContext: CoroutineContext = SupervisorJob() - }) + }.start() } internal object Testing { diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt index 26fd38504..68892668d 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt @@ -12,10 +12,12 @@ package net.mamoe.mirai.console.setting import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration +import net.mamoe.mirai.console.utils.ConsoleInternalAPI import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertSame +@OptIn(ConsoleInternalAPI::class) internal class SettingTest { class MySetting : AbstractSetting() { @@ -23,9 +25,13 @@ internal class SettingTest { val map by value>() val map2 by value>>() + @ConsoleInternalAPI override fun onValueChanged(value: Value<*>) { } + + override fun setStorage(storage: SettingStorage) { + } } @OptIn(UnstableDefault::class) diff --git a/frontend/mirai-console-pure/build.gradle.kts b/frontend/mirai-console-pure/build.gradle.kts index fcc7850ec..7480b30fd 100644 --- a/frontend/mirai-console-pure/build.gradle.kts +++ b/frontend/mirai-console-pure/build.gradle.kts @@ -17,6 +17,7 @@ kotlin { languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI") + languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt index 5b797db6c..55ac9bbec 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt @@ -48,7 +48,7 @@ internal val LoggerCreator: (identity: String?) -> MiraiLogger = { /** * mirai-console-pure 前端实现 * - * @see MiraiConsolePure 后端实现 + * @see MiraiConsoleImplementationPure 后端实现 * @see MiraiConsolePureLoader CLI 入口点 */ @ConsoleInternalAPI diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt similarity index 78% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePure.kt rename to frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt index 47a5dfe8e..39894ac08 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt @@ -18,16 +18,16 @@ "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING", "EXPOSED_SUPER_CLASS" ) -@file:OptIn(ConsoleInternalAPI::class) +@file:OptIn(ConsoleInternalAPI::class, ConsoleFrontEndImplementation::class) package net.mamoe.mirai.console.pure import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -import net.mamoe.mirai.console.IMiraiConsole +import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleFrontEnd -import net.mamoe.mirai.console.MiraiConsoleInitializer +import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.plugin.DeferredPluginLoader import net.mamoe.mirai.console.plugin.PluginLoader @@ -44,7 +44,8 @@ import java.io.File * @see MiraiConsoleFrontEndPure 前端实现 * @see MiraiConsolePureLoader CLI 入口点 */ -class MiraiConsolePure @JvmOverloads constructor( +class MiraiConsoleImplementationPure +@JvmOverloads constructor( override val rootDir: File = File("."), override val builtInPluginLoaders: List> = listOf(DeferredPluginLoader { JarPluginLoader }), override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure, @@ -52,21 +53,9 @@ class MiraiConsolePure @JvmOverloads constructor( override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl, override val settingStorageForJarPluginLoader: SettingStorage = MultiFileSettingStorage(rootDir), override val settingStorageForBuiltIns: SettingStorage = MultiFileSettingStorage(rootDir) -) : IMiraiConsole, CoroutineScope by CoroutineScope(SupervisorJob()) { +) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) { init { rootDir.mkdir() require(rootDir.isDirectory) { "rootDir ${rootDir.absolutePath} is not a directory" } } - - @JvmField - internal var started: Boolean = false - - companion object { - @JvmStatic - fun MiraiConsolePure.start() = synchronized(this) { - check(!started) { "mirai-console is already started and can't be restarted." } - MiraiConsoleInitializer.init(this) - started = true - } - } } \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index 645e8d048..771df81d6 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -22,9 +22,8 @@ package net.mamoe.mirai.console.pure import kotlinx.coroutines.isActive import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.job -import net.mamoe.mirai.console.pure.MiraiConsolePure.Companion.start import net.mamoe.mirai.console.utils.ConsoleInternalAPI import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.content @@ -46,7 +45,7 @@ object MiraiConsolePureLoader { internal fun startup() { DefaultLogger = { MiraiConsoleFrontEndPure.loggerFor(it) } overrideSTD() - MiraiConsolePure().start() + MiraiConsoleImplementationPure().start() startConsoleThread() }