From 9c18c04466bc724f528e1c3fa7447e4755b453f2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 23 May 2020 17:44:34 +0800 Subject: [PATCH] Implement PluginLoaders and PluginManager --- .../mirai/console/codegen/ValueImplCodegen.kt | 3 +- backend/mirai-console/build.gradle.kts | 81 +++---- .../net/mamoe/mirai/console/MiraiConsole.kt | 34 ++- .../{utils => }/MiraiConsoleFrontEnd.kt | 2 +- .../description/CommandArgParserBuiltins.kt | 6 +- .../mamoe/mirai/console/command/internal.kt | 95 ++++++++ .../plugins/{JarPlugin.kt => JvmPlugin.kt} | 117 ++++++++-- .../net/mamoe/mirai/console/plugins/Plugin.kt | 95 +++++--- .../mirai/console/plugins/PluginLoader.kt | 83 +++++-- .../mirai/console/plugins/PluginManager.kt | 176 ++++++++++++++- .../console/setting/internal/_ValueImpl.kt | 205 ++++++++++-------- .../mamoe/mirai/console/utils/BotHelper.kt | 7 - .../net/mamoe/mirai/console/utils/Utils.kt | 161 -------------- .../net/mamoe/mirai/console/utils/Value.kt | 50 ----- .../mirai/console/utils/retryCatching.kt | 38 ++++ .../src/test/kotlin/StringFuzzyTest.kt | 4 +- 16 files changed, 708 insertions(+), 449 deletions(-) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/{utils => }/MiraiConsoleFrontEnd.kt (97%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/{JarPlugin.kt => JvmPlugin.kt} (79%) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Value.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/retryCatching.kt diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt index d518c32cd..d09d147c8 100644 --- a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt @@ -224,7 +224,8 @@ fun genCollectionValueImpl( """ internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value { var internalValue: $kotlinTypeName = default - return object : ${miraiValueName}Value(), $kotlinTypeName by dynamic$collectionName({ internalValue }) { + val delegt = dynamic$collectionName { internalValue } + return object : ${miraiValueName}Value(), $kotlinTypeName by delegt { override var value: $kotlinTypeName get() = internalValue set(new) { diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 57f14a79f..4fc131443 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import upload.Bintray import java.util.* @@ -12,19 +11,46 @@ plugins { apply(plugin = "com.github.johnrengelman.shadow") -kotlin { - sourceSets { - all { - languageSettings.enableLanguageFeature("InlineClasses") +version = Versions.Mirai.console +description = "Console backend for mirai" - languageSettings.useExperimentalAnnotation("kotlin.Experimental") - languageSettings.useExperimentalAnnotation("kotlin.OptIn") - languageSettings.progressiveMode = true - languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") - languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") - languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") - languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") - languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" +} + +kotlin { + sourceSets.all { + target.compilations.all { + kotlinOptions { + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=enable" + jvmTarget = "1.8" + } + } + languageSettings.apply { + enableLanguageFeature("InlineClasses") + progressiveMode = true + + useExperimentalAnnotation("kotlin.Experimental") + useExperimentalAnnotation("kotlin.OptIn") + + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") + useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") + useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") + useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") + } + } + + sourceSets { + getByName("test") { + languageSettings.apply { + languageVersion = "1.4" + } } } } @@ -34,36 +60,15 @@ dependencies { compileAndRuntime(kotlin("stdlib")) api("net.mamoe.yamlkt:yamlkt:0.3.1") - api("org.jetbrains:annotations:19.0.0") testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}") - testApi(kotlin("stdlib")) + testApi(kotlin("stdlib-jdk8")) testApi(kotlin("test")) testApi(kotlin("test-junit5")) } -version = Versions.Mirai.console - -description = "Console backend for mirai" - -val compileKotlin: KotlinCompile by tasks -compileKotlin.kotlinOptions { - jvmTarget = "1.8" -} -val compileTestKotlin: KotlinCompile by tasks -compileTestKotlin.kotlinOptions { - jvmTarget = "1.8" -} -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} -tasks.withType(JavaCompile::class.java) { - options.encoding = "UTF8" -} - - +// region PUBLISHING tasks.register("ensureBintrayAvailable") { doLast { @@ -128,4 +133,6 @@ if (Bintray.isBintrayAvailable(project)) { } } } -} else println("bintray isn't available. NO PUBLICATIONS WILL BE SET") \ No newline at end of file +} else println("bintray isn't available. NO PUBLICATIONS WILL BE SET") + +// endregion \ No newline at end of file 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 a69a4eb6a..46cfcef00 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 @@ -13,33 +13,40 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.io.charsets.Charset import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd +import net.mamoe.mirai.console.plugins.JarPluginLoader +import net.mamoe.mirai.console.plugins.PluginLoader import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiLogger import java.io.ByteArrayOutputStream +import java.io.File import java.io.PrintStream import kotlin.coroutines.CoroutineContext // 前端使用 -interface IMiraiConsole : CoroutineScope { +internal interface IMiraiConsole : CoroutineScope { val build: String val version: String /** - * Console运行路径 + * Console 运行路径 */ - val path: String + val rootDir: File /** - * Console前端接口 + * Console 前端接口 */ val frontEnd: MiraiConsoleFrontEnd /** - * 与前端交互所使用的Logger + * 与前端交互所使用的 Logger */ val mainLogger: MiraiLogger + + /** + * 内建加载器列表, 一般需要包含 [JarPluginLoader] + */ + val builtInPluginLoaders: List> } object MiraiConsole : CoroutineScope, IMiraiConsole { @@ -52,19 +59,19 @@ object MiraiConsole : CoroutineScope, IMiraiConsole { override val build: String get() = instance.build override val version: String get() = instance.version - override val path: String get() = instance.path + override val rootDir: File get() = instance.rootDir override val frontEnd: MiraiConsoleFrontEnd get() = instance.frontEnd override val mainLogger: MiraiLogger get() = instance.mainLogger override val coroutineContext: CoroutineContext get() = instance.coroutineContext + override val builtInPluginLoaders: List> = instance.builtInPluginLoaders + init { - DefaultLogger = { - this.newLogger(it) + DefaultLogger = { identity -> + this.newLogger(identity) } this.coroutineContext[Job]!!.invokeOnCompletion { - Bot.botInstances.forEach { - it.close() - } + Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } } } @@ -72,6 +79,9 @@ object MiraiConsole : CoroutineScope, IMiraiConsole { fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity) } +/** + * Included in kotlin stdlib 1.4 + */ internal val Throwable.stacktraceString: String get() = ByteArrayOutputStream().apply { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/MiraiConsoleFrontEnd.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt similarity index 97% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/MiraiConsoleFrontEnd.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt index f34db8ff3..f94a8eaea 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/MiraiConsoleFrontEnd.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.utils +package net.mamoe.mirai.console import net.mamoe.mirai.Bot import net.mamoe.mirai.console.center.CuiPluginCenter diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt index 01f2cdf92..8399965b2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt @@ -10,11 +10,7 @@ package net.mamoe.mirai.console.command.description import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.command.BotAwareCommandSender -import net.mamoe.mirai.console.command.CommandSender -import net.mamoe.mirai.console.command.MemberCommandSender -import net.mamoe.mirai.console.command.UserCommandSender -import net.mamoe.mirai.console.utils.fuzzySearchMember +import net.mamoe.mirai.console.command.* import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt index 1594fd98c..e88f5a486 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt @@ -9,6 +9,8 @@ package net.mamoe.mirai.console.command +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member import java.util.concurrent.locks.ReentrantLock @@ -61,4 +63,97 @@ internal infix fun Array.intersects(other: Array): Boolean { if (this[i] == other[i]) return true } return false +} + + + +internal fun String.fuzzyCompare(target: String): Double { + var step = 0 + if (this == target) { + return 1.0 + } + if (target.length > this.length) { + return 0.0 + } + for (i in this.indices) { + if (target.length == i) { + step-- + } else { + if (this[i] != target[i]) { + break + } + step++ + } + } + + if (step == this.length - 1) { + return 1.0 + } + return step.toDouble() / this.length +} + +/** + * 模糊搜索一个List中index最接近target的东西 + */ +internal inline fun Collection.fuzzySearch( + target: String, + index: (T) -> String +): T? { + if (this.isEmpty()) { + return null + } + var potential: T? = null + var rate = 0.0 + this.forEach { + val thisIndex = index(it) + if (thisIndex == target) { + return it + } + with(thisIndex.fuzzyCompare(target)) { + if (this > rate) { + rate = this + potential = it + } + } + } + return potential +} + +/** + * 模糊搜索一个List中index最接近target的东西 + * 并且确保target是唯一的 + * 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null + */ +internal inline fun Collection.fuzzySearchOnly( + target: String, + index: (T) -> String +): T? { + if (this.isEmpty()) { + return null + } + var potential: T? = null + var rate = 0.0 + var collide = 0 + this.forEach { + with(index(it).fuzzyCompare(target)) { + if (this > rate) { + rate = this + potential = it + } + if (this == 1.0) { + collide++ + } + if (collide > 1) { + return null//collide + } + } + } + return potential +} + + +internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? { + return this.members.fuzzySearchOnly(nameCardTarget) { + it.nameCard + } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JarPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JvmPlugin.kt similarity index 79% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JarPlugin.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JvmPlugin.kt index 83c2cd4e7..6cc2c9552 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JarPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JvmPlugin.kt @@ -6,50 +6,133 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.plugins +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.utils.MiraiLogger +import java.io.File +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext -sealed class JarPlugin : Plugin(), CoroutineScope { - internal lateinit var _description: JarPluginDescription +interface JvmPlugin : Plugin, CoroutineScope { + val logger: MiraiLogger + val description: JvmPluginDescription - final override val description: PluginDescription get() = _description - final override val loader: JarPluginLoader get() = JarPluginLoader + @JvmDefault + fun onLoad() { + } + + @JvmDefault + fun onEnable() { + } + + @JvmDefault + fun onDisable() { + } +} + + +abstract class JavaPlugin @JvmOverloads constructor( + coroutineContext: CoroutineContext = EmptyCoroutineContext +) : JvmPlugin, JvmPluginImpl(coroutineContext) { + // TODO: 2020/5/23 scheduler, event listener(?) +} + +abstract class KotlinPlugin @JvmOverloads constructor( + coroutineContext: CoroutineContext = EmptyCoroutineContext +) : JvmPlugin, JvmPluginImpl(coroutineContext) { + // that's it } @Serializable -internal class JarPluginDescription( +data class JvmPluginDescription internal constructor( // serializer 可以用这个构造器 + override val kind: PluginKind, override val name: String, override val author: String, override val version: String, override val info: String, - override val depends: List -) : PluginDescription + override val loadBefore: List, + override val dependencies: List +) : PluginDescription, FilePluginDescription { + /** + * 在手动实现时使用这个构造器. + */ + @Suppress("unused") + constructor( + kind: PluginKind, + name: String, + author: String, + version: String, + info: String, + loadBefore: List, + depends: List, + file: File + ) : this(kind, name, author, version, info, loadBefore, depends) { + this._file = file + } -abstract class JavaPlugin : JarPlugin() + @Suppress("PropertyName") + @Transient + internal var _file: File? = null -abstract class KotlinPlugin : JarPlugin() + override val file: File + get() = _file ?: error("Internal error: JvmPluginDescription(name=$name)._file == null") +} +internal abstract class JvmPluginImpl( + parentCoroutineContext: CoroutineContext +) : JvmPlugin, CoroutineScope { + /** + * Initialized immediately after construction of [JvmPluginImpl] instance + */ + @Suppress("PropertyName") + internal lateinit var _description: JvmPluginDescription + override val description: JvmPluginDescription get() = _description + + final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) } + + final override val coroutineContext: CoroutineContext by lazy { + SupervisorJob(parentCoroutineContext[Job]) + CoroutineExceptionHandler { _, throwable -> + logger.error(throwable) + } + } +} /** * 内建的 Jar (JVM) 插件加载器 */ -object JarPluginLoader : PluginLoader { - override val list: List - get() = TODO("Not yet implemented") +object JarPluginLoader : AbstractFilePluginLoader("jar") { + override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description - override fun load(plugin: JarPlugin) { - TODO("Not yet implemented") + override fun Sequence.mapToDescription(): List { + TODO( + """ + CHECK IS JAR FILE AND CAN BE READ + READ JAR FILE, EXTRACT PLUGIN DESCRIPTION + SET JvmPluginDescription._file + RETURN PLUGIN + """.trimIndent() + ) } - override fun enable(plugin: JarPlugin) { - TODO("Not yet implemented") + @Throws(PluginLoadException::class) + override fun load(description: JvmPluginDescription): JvmPlugin { + TODO("FIND PLUGIN MAIN, THEN LOAD") + // no need to check dependencies } + + override fun enable(plugin: JvmPlugin) = plugin.onEnable() + override fun disable(plugin: JvmPlugin) = plugin.onDisable() } /* diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/Plugin.kt index f967bff79..f0df37871 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/Plugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/Plugin.kt @@ -9,45 +9,82 @@ package net.mamoe.mirai.console.plugins -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiLogger -import kotlin.coroutines.CoroutineContext +import kotlinx.serialization.Serializable +import java.io.File + +/** 插件类型 */ +enum class PluginKind { + /** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */ + LOADER, + + /** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */ + NORMAL +} /** - * 插件信息 + * 插件描述 */ interface PluginDescription { + val kind: PluginKind + val name: String val author: String val version: String val info: String - val depends: List + + /** 指定此插件需要在这些插件之前加载 */ + val loadBefore: List + + /** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */ + val dependencies: List +} + +/** 插件的一个依赖的信息 */ +@Serializable +data class PluginDependency( + /** 依赖插件名 */ + val name: String, + /** + * 依赖版本号 + * @see versionKind 版本号类型 + */ + val version: String, + /** 版本号类型 */ + val versionKind: VersionKind +) { + enum class VersionKind { + /** 要求依赖精确的版本 */ + EXACT, + + /** 要求依赖最低版本 */ + AT_LEAST, + + /** 要求依赖最高版本 */ + AT_MOST + } + + override fun toString(): String { + return "$name ${versionKind.toEnglishString()}v$version" + } +} + + +internal fun PluginDependency.VersionKind.toEnglishString(): String = when (this) { + PluginDependency.VersionKind.EXACT -> "" + PluginDependency.VersionKind.AT_LEAST -> "at least " + PluginDependency.VersionKind.AT_MOST -> "at most " } /** - * 插件基类. - * - * 内建的插件类型: - * - [JarPlugin] + * 基于文件的插件的描述 */ -abstract class Plugin : CoroutineScope { - abstract val description: PluginDescription - abstract val loader: PluginLoader<*> +interface FilePluginDescription : PluginDescription { + val file: File +} - @OptIn(MiraiExperimentalAPI::class) - val logger: MiraiLogger by lazy { MiraiConsole.newLogger(description.name) } - - override val coroutineContext: CoroutineContext - get() = SupervisorJob(MiraiConsole.coroutineContext[Job]) + CoroutineExceptionHandler { _, throwable -> - logger.error(throwable) - } - - open fun onLoaded() {} - open fun onDisabled() {} - open fun onEnabled() {} -} \ No newline at end of file +/** + * 表示一个 mirai-console 插件. + * + * @see JvmPlugin + */ +interface Plugin \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginLoader.kt index 144b9b21d..acd31a025 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginLoader.kt @@ -1,29 +1,68 @@ +@file:Suppress("unused") + package net.mamoe.mirai.console.plugins +import net.mamoe.mirai.console.MiraiConsole +import java.io.File + /** - * 插件加载器 + * 插件加载器. * + * 插件加载器只实现寻找插件列表, 加载插件, 启用插件, 关闭插件这四个功能. + * + * 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护. + */ +interface PluginLoader

{ + /** + * 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表. 此函数只会被调用一次 + */ + fun listPlugins(): List + + /** + * 获取此插件的描述 + */ + @Throws(PluginLoadException::class) + fun getPluginDescription(plugin: P): D + + /** + * 加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的实例 + * + * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). + */ + @Throws(PluginLoadException::class) + fun load(description: D): P + + fun enable(plugin: P) + fun disable(plugin: P) +} + +open class PluginLoadException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} + +/** + * '/plugins' 目录中的插件的加载器. 每个加载器需绑定一个后缀. + * + * @see AbstractFilePluginLoader * @see JarPluginLoader 内建的 Jar (JVM) 插件加载器. */ -interface PluginLoader

{ - val list: List

- - fun loadAll() = list.forEach(::load) - fun enableAll() = list.forEach(::enable) - fun unloadAll() = list.forEach(::unload) - fun reloadAll() = list.forEach(::reload) - - val isUnloadSupported: Boolean - get() = false - - fun load(plugin: P) - fun enable(plugin: P) - fun unload(plugin: P) { - error("NotImplemented") - } - - fun reload(plugin: P) { - unload(plugin) - load(plugin) - } +interface FilePluginLoader

: PluginLoader { + /** + * 所支持的插件文件后缀, 不含 '.'. 如 [JarPluginLoader] 为 "jar" + */ + val fileSuffix: String } + +abstract class AbstractFilePluginLoader

( + override val fileSuffix: String +) : FilePluginLoader { + private fun pluginsFilesSequence(): Sequence = + PluginManager.pluginsDir.walk().filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) } + + protected abstract fun Sequence.mapToDescription(): List + + final override fun listPlugins(): List = pluginsFilesSequence().mapToDescription() +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt index 5b01f905c..d2386f4d8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt @@ -1,24 +1,180 @@ +@file:Suppress("NOTHING_TO_INLINE") + package net.mamoe.mirai.console.plugins +import kotlinx.atomicfu.locks.withLock +import net.mamoe.mirai.console.MiraiConsole +import java.io.File +import java.util.* +import java.util.concurrent.locks.ReentrantLock +import kotlin.collections.ArrayList + +val Plugin.description: PluginDescription get() = TODO() +val

P.loader: PluginLoader get() = TODO() + +inline fun PluginLoader<*, *>.register() = PluginManager.registerPluginLoader(this) +inline fun PluginLoader<*, *>.unregister() = PluginManager.unregisterPluginLoader(this) object PluginManager { - private val _loaders: MutableSet> = mutableSetOf() + val pluginsDir = File(MiraiConsole.rootDir, "plugins").apply { mkdir() } - val loaders: Set> get() = _loaders + class LoaderNode

( + val loader: PluginLoader, + val loadedPlugins: MutableList

= mutableListOf() + ) - fun registerPluginLoader(loader: PluginLoader<*>) { - _loaders.add(loader) + private val _pluginLoaders: MutableSet> = mutableSetOf() + private val loadersLock: ReentrantLock = ReentrantLock() + + private val resolvedPlugins: LinkedList = LinkedList() + + @JvmStatic + val plugins: List + get() = _pluginLoaders.flatMap { it.loadedPlugins } + + /** + * 内建的插件加载器列表. 由 [MiraiConsole] 初始化 + */ + @JvmStatic + val builtInLoaders: List> + get() = MiraiConsole.builtInPluginLoaders + + /** + * 由插件创建的 [PluginLoader] + */ + val pluginLoaders: List> get() = _pluginLoaders.map { it.loader } + + @JvmStatic + fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean = loadersLock.withLock { + _pluginLoaders.add(LoaderNode(loader)) } - fun unregisterPluginLoader(loader: PluginLoader<*>) { - _loaders.remove(loader) + @JvmStatic + fun unregisterPluginLoader(loader: PluginLoader<*, *>) = loadersLock.withLock { + _pluginLoaders.removeAll { it.loader == loader } } - fun loadPlugins() { - loaders.forEach(PluginLoader<*>::loadAll) + + // region LOADING + + private fun

PluginLoader.loadPluginNoEnable(description: D): P { + return this.load(description).also { resolvedPlugins.add(it) } } - fun enablePlugins() { - loaders.forEach(PluginLoader<*>::enableAll) + private fun

PluginLoader.loadPluginAndEnable(description: D) { + @Suppress("UNCHECKED_CAST") + return this.enable(loadPluginNoEnable(description.unwrap())) } + + /** + * STEPS: + * 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件 + * 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件 + * 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表. + * 4. 解决依赖并排序 + * 5. 依次 [PluginLoader.load] + * 但不 [PluginLoader.enable] + * + * @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable] + */ + @Suppress("UNCHECKED_CAST") + @Throws(PluginMissingDependencyException::class) + internal fun loadEnablePlugins() { + val all = loadAndEnableLoaderProviders() + pluginLoaders.listAllPlugins().flatMap { it.second } + + for ((loader, desc) in all.sortByDependencies()) { + loader.loadPluginAndEnable(desc) + } + } + + /** + * @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable] + */ + @Suppress("UNCHECKED_CAST") + @Throws(PluginMissingDependencyException::class) + private fun loadAndEnableLoaderProviders(): List { + val allDescriptions = + this.builtInLoaders.listAllPlugins() + .asSequence() + .onEach { (loader, descriptions) -> + loader as PluginLoader + + for (it in descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies()) { + loader.loadPluginAndEnable(it) + } + } + .flatMap { it.second.asSequence() } + + return allDescriptions.toList() + } + + private fun List>.listAllPlugins(): List, List>> { + return associateWith { loader -> loader.listPlugins().map { desc -> desc.wrapWith(loader) } }.toList() + } + + @Throws(PluginMissingDependencyException::class) + private fun List.sortByDependencies(): List { + val resolved = ArrayList(this.size) + + fun D.canBeLoad(): Boolean = this.dependencies.all { it in resolved } + + fun List.consumeLoadable(): List { + val (canBeLoad, cannotBeLoad) = this.partition { it.canBeLoad() } + resolved.addAll(canBeLoad) + return cannotBeLoad + } + + fun List.filterIsMissing(): List = this.filterNot { it in resolved } + + tailrec fun List.doSort() { + if (this.isEmpty()) return + + val beforeSize = this.size + this.consumeLoadable().also { resultPlugins -> + check(resultPlugins.size < beforeSize) { + throw PluginMissingDependencyException(resultPlugins.joinToString("\n") { badPlugin -> + "Cannot load plugin ${badPlugin.name}, missing dependencies: ${badPlugin.dependencies.filterIsMissing() + .joinToString()}" + }) + } + }.doSort() + } + + this.doSort() + return resolved + } + + // endregion +} + +internal data class PluginDescriptionWithLoader( + @JvmField val loader: PluginLoader<*, PluginDescription>, // easier type + @JvmField val delegate: PluginDescription +) : PluginDescription by delegate + +@Suppress("UNCHECKED_CAST") +internal fun PluginDescription.unwrap(): D = + if (this is PluginDescriptionWithLoader) this.delegate as D else this as D + +@Suppress("UNCHECKED_CAST") +internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>): PluginDescriptionWithLoader = + PluginDescriptionWithLoader( + loader as PluginLoader<*, PluginDescription>, this + ) + +internal operator fun List.contains(dependency: PluginDependency): Boolean = + any { it.name == dependency.name } + +class PluginMissingDependencyException : Exception { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} + +open class PluginResolutionException : Exception { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt index 983ee212e..d43cfc582 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt @@ -9,10 +9,7 @@ package net.mamoe.mirai.console.setting.internal -import kotlinx.serialization.Decoder -import kotlinx.serialization.Encoder -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialDescriptor +import kotlinx.serialization.* import kotlinx.serialization.builtins.* import net.mamoe.mirai.console.setting.* @@ -415,7 +412,8 @@ internal fun Setting.valueImpl(default: Array): TypedStringArrayValue { internal fun Setting.valueImpl(default: List): IntListValue { var internalValue: List = default - return object : IntListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : IntListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -430,7 +428,8 @@ internal fun Setting.valueImpl(default: List): IntListValue { internal fun Setting.valueImpl(default: List): ShortListValue { var internalValue: List = default - return object : ShortListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : ShortListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -445,7 +444,8 @@ internal fun Setting.valueImpl(default: List): ShortListValue { internal fun Setting.valueImpl(default: List): ByteListValue { var internalValue: List = default - return object : ByteListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : ByteListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -460,7 +460,8 @@ internal fun Setting.valueImpl(default: List): ByteListValue { internal fun Setting.valueImpl(default: List): LongListValue { var internalValue: List = default - return object : LongListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : LongListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -475,7 +476,8 @@ internal fun Setting.valueImpl(default: List): LongListValue { internal fun Setting.valueImpl(default: List): FloatListValue { var internalValue: List = default - return object : FloatListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : FloatListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -490,7 +492,8 @@ internal fun Setting.valueImpl(default: List): FloatListValue { internal fun Setting.valueImpl(default: List): DoubleListValue { var internalValue: List = default - return object : DoubleListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : DoubleListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -505,7 +508,8 @@ internal fun Setting.valueImpl(default: List): DoubleListValue { internal fun Setting.valueImpl(default: List): BooleanListValue { var internalValue: List = default - return object : BooleanListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : BooleanListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -520,7 +524,8 @@ internal fun Setting.valueImpl(default: List): BooleanListValue { internal fun Setting.valueImpl(default: List): CharListValue { var internalValue: List = default - return object : CharListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : CharListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -535,7 +540,8 @@ internal fun Setting.valueImpl(default: List): CharListValue { internal fun Setting.valueImpl(default: List): StringListValue { var internalValue: List = default - return object : StringListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : StringListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -550,7 +556,8 @@ internal fun Setting.valueImpl(default: List): StringListValue { internal fun Setting.valueImpl(default: Set): IntSetValue { var internalValue: Set = default - return object : IntSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : IntSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -565,7 +572,8 @@ internal fun Setting.valueImpl(default: Set): IntSetValue { internal fun Setting.valueImpl(default: Set): ShortSetValue { var internalValue: Set = default - return object : ShortSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : ShortSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -580,7 +588,8 @@ internal fun Setting.valueImpl(default: Set): ShortSetValue { internal fun Setting.valueImpl(default: Set): ByteSetValue { var internalValue: Set = default - return object : ByteSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : ByteSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -595,7 +604,8 @@ internal fun Setting.valueImpl(default: Set): ByteSetValue { internal fun Setting.valueImpl(default: Set): LongSetValue { var internalValue: Set = default - return object : LongSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : LongSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -610,7 +620,8 @@ internal fun Setting.valueImpl(default: Set): LongSetValue { internal fun Setting.valueImpl(default: Set): FloatSetValue { var internalValue: Set = default - return object : FloatSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : FloatSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -625,7 +636,8 @@ internal fun Setting.valueImpl(default: Set): FloatSetValue { internal fun Setting.valueImpl(default: Set): DoubleSetValue { var internalValue: Set = default - return object : DoubleSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : DoubleSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -640,7 +652,8 @@ internal fun Setting.valueImpl(default: Set): DoubleSetValue { internal fun Setting.valueImpl(default: Set): BooleanSetValue { var internalValue: Set = default - return object : BooleanSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : BooleanSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -655,7 +668,8 @@ internal fun Setting.valueImpl(default: Set): BooleanSetValue { internal fun Setting.valueImpl(default: Set): CharSetValue { var internalValue: Set = default - return object : CharSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : CharSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -670,7 +684,8 @@ internal fun Setting.valueImpl(default: Set): CharSetValue { internal fun Setting.valueImpl(default: Set): StringSetValue { var internalValue: Set = default - return object : StringSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : StringSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -690,7 +705,7 @@ internal fun Setting.valueImpl( ): MutableIntListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableIntListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -700,15 +715,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Int.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -726,7 +741,7 @@ internal fun Setting.valueImpl( ): MutableShortListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableShortListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -736,15 +751,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Short.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -762,7 +777,7 @@ internal fun Setting.valueImpl( ): MutableByteListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableByteListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -772,15 +787,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Byte.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -798,7 +813,7 @@ internal fun Setting.valueImpl( ): MutableLongListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableLongListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -808,15 +823,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Long.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -834,7 +849,7 @@ internal fun Setting.valueImpl( ): MutableFloatListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableFloatListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -844,15 +859,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Float.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -870,7 +885,7 @@ internal fun Setting.valueImpl( ): MutableDoubleListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableDoubleListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -880,15 +895,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Double.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -906,7 +921,7 @@ internal fun Setting.valueImpl( ): MutableBooleanListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableBooleanListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -916,15 +931,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Boolean.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -942,7 +957,7 @@ internal fun Setting.valueImpl( ): MutableCharListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableCharListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -952,15 +967,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(Char.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -978,7 +993,7 @@ internal fun Setting.valueImpl( ): MutableStringListValue { var internalValue: MutableList = default - val delegt = dynamicMutableList { internalValue } + val delegt = dynamicMutableList{ internalValue } return object : MutableStringListValue(), MutableList by delegt { override var value: MutableList get() = internalValue @@ -988,15 +1003,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = ListSerializer(String.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableList { - return delegate.deserialize(decoder).toMutableList().observable { + return delegate.deserialize(decoder).toMutableList().observable { onElementChanged(outerThis) } } @@ -1014,7 +1029,7 @@ internal fun Setting.valueImpl( ): MutableIntSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableIntSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1024,15 +1039,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Int.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1050,7 +1065,7 @@ internal fun Setting.valueImpl( ): MutableShortSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableShortSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1060,15 +1075,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Short.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1086,7 +1101,7 @@ internal fun Setting.valueImpl( ): MutableByteSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableByteSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1096,15 +1111,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Byte.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1122,7 +1137,7 @@ internal fun Setting.valueImpl( ): MutableLongSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableLongSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1132,15 +1147,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Long.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1158,7 +1173,7 @@ internal fun Setting.valueImpl( ): MutableFloatSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableFloatSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1168,15 +1183,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Float.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1194,7 +1209,7 @@ internal fun Setting.valueImpl( ): MutableDoubleSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableDoubleSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1204,15 +1219,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Double.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1230,7 +1245,7 @@ internal fun Setting.valueImpl( ): MutableBooleanSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableBooleanSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1240,15 +1255,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Boolean.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1266,7 +1281,7 @@ internal fun Setting.valueImpl( ): MutableCharSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableCharSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1276,15 +1291,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(Char.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1302,7 +1317,7 @@ internal fun Setting.valueImpl( ): MutableStringSetValue { var internalValue: MutableSet = default - val delegt = dynamicMutableSet { internalValue } + val delegt = dynamicMutableSet{ internalValue } return object : MutableStringSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue @@ -1312,15 +1327,15 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - + private val outerThis get() = this - + override val serializer: KSerializer> = object : KSerializer> { private val delegate = SetSerializer(String.serializer()) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MutableSet { - return delegate.deserialize(decoder).toMutableSet().observable { + return delegate.deserialize(decoder).toMutableSet().observable { onElementChanged(outerThis) } } @@ -1344,7 +1359,7 @@ internal fun Setting.valueImpl(default: T): Value { onElementChanged(this) } } - override val serializer = object : KSerializer { + override val serializer = object : KSerializer{ override val descriptor: SerialDescriptor get() = internalValue.updaterSerializer.descriptor diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt index 2746a1672..5bef2c575 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt @@ -22,13 +22,6 @@ import java.io.File val User.isManager: Boolean get() = this.bot.managers.contains(this.id) -@JvmName("addManager") -@JvmSynthetic -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -fun Bot.addManagerDeprecated(long: Long) { - addManager(long) -} - internal fun Bot.addManager(long: Long): Boolean { TODO() return true diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt deleted file mode 100644 index 5562465be..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt +++ /dev/null @@ -1,161 +0,0 @@ -package net.mamoe.mirai.console.utils -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -/** - * 执行N次 builder - * 成功一次就会结束 - * 否则就会throw - */ -@OptIn(ExperimentalContracts::class) -@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") -@kotlin.internal.InlineOnly -inline fun retryCatching(n: Int, block: () -> R): Result { - contract { - callsInPlace(block, InvocationKind.AT_LEAST_ONCE) - } - require(n >= 0) { "param n for retryCatching must not be negative" } - var exception: Throwable? = null - repeat(n){ - try { - return Result.success(block()) - } catch (e: Throwable) { - exception?.addSuppressedMirai(e) - exception = e - } - } - return Result.failure(exception!!) -} - -@OptIn(ExperimentalContracts::class) -inline fun tryNTimes(n: Int = 2, block: () -> T):T { - contract { - callsInPlace(block, InvocationKind.AT_LEAST_ONCE) - } - require(n >= 0) { "param n for tryNTimes must not be negative" } - var last:Exception? = null - repeat(n){ - try { - return block.invoke() - }catch (e:Exception){ - last = e - } - } - - //给我编译 - - throw last!! -} - -@PublishedApi -internal fun Throwable.addSuppressedMirai(e: Throwable) { - if (e === this) { - return - } - kotlin.runCatching { - this.addSuppressed(e) - } -} - - -/** - * 两个字符串的近似值 - * 要求前面完全一致 - * 如 - * XXXXXYYYYY.fuzzyCompare(XXXXXYYY) = 0.8 - * XXXXXYYYYY.fuzzyCompare(XXXXXYYYZZ) = 0.8 - */ - -internal fun String.fuzzyCompare(target: String): Double { - var step = 0 - if (this == target) { - return 1.0 - } - if (target.length > this.length) { - return 0.0 - } - for (i in this.indices) { - if (target.length == i) { - step-- - }else { - if (this[i] != target[i]) { - break - } - step++ - } - } - - if(step == this.length-1){ - return 1.0 - } - return step.toDouble()/this.length -} - -/** - * 模糊搜索一个List中index最接近target的东西 - */ -internal inline fun Collection.fuzzySearch( - target: String, - index: (T) -> String -): T? { - if (this.isEmpty()) { - return null - } - var potential: T? = null - var rate = 0.0 - this.forEach { - val thisIndex = index(it) - if(thisIndex == target){ - return it - } - with(thisIndex.fuzzyCompare(target)) { - if (this > rate) { - rate = this - potential = it - } - } - } - return potential -} - -/** - * 模糊搜索一个List中index最接近target的东西 - * 并且确保target是唯一的 - * 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null - */ -internal inline fun Collection.fuzzySearchOnly( - target: String, - index: (T) -> String -): T? { - if (this.isEmpty()) { - return null - } - var potential: T? = null - var rate = 0.0 - var collide = 0 - this.forEach { - with(index(it).fuzzyCompare(target)) { - if (this > rate) { - rate = this - potential = it - } - if(this == 1.0){ - collide++ - } - if(collide > 1){ - return null//collide - } - } - } - return potential -} - - -internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? { - return this.members.fuzzySearchOnly(nameCardTarget) { - it.nameCard - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Value.kt deleted file mode 100644 index 85a3ba822..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Value.kt +++ /dev/null @@ -1,50 +0,0 @@ -package net.mamoe.mirai.console.utils - -import net.mamoe.mirai.utils.MiraiExperimentalAPI - -/** - * A Value - * the input type of this Value is T while the output is V - */ -@MiraiExperimentalAPI -abstract class Value { - operator fun invoke(): V = get() - - abstract fun get(): V - - abstract fun set(t: T) -} - - -/** - * This value can be used as a Config Value - */ -@MiraiExperimentalAPI -interface ConfigValue - - -/** - * A simple value - * the input type is same as output value - */ - -@MiraiExperimentalAPI -open class SimpleValue( - var value: T -) : Value() { - override fun get() = this.value - - override fun set(t: T) { - this.value = t - } -} - -@MiraiExperimentalAPI -open class NullableSimpleValue( - value: T? = null -) : SimpleValue( - value -) { - fun isNull() = value == null -} - diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/retryCatching.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/retryCatching.kt new file mode 100644 index 000000000..a8c425921 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/retryCatching.kt @@ -0,0 +1,38 @@ +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") + +package net.mamoe.mirai.console.utils + +import org.jetbrains.annotations.Range +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * 执行 [n] 次 [block], 在第一次成功时返回执行结果, 在捕获到异常时返回异常. + */ +@kotlin.internal.InlineOnly +inline fun retryCatching(n: @Range(from = 1, to = Long.MAX_VALUE) Int, block: () -> R): Result { + contract { + callsInPlace(block, InvocationKind.AT_LEAST_ONCE) + } + require(n >= 0) { "param n for retryCatching must not be negative" } + var exception: Throwable? = null + repeat(n) { + try { + return Result.success(block()) + } catch (e: Throwable) { + exception?.addSuppressedMirai(e) + exception = e + } + } + return Result.failure(exception!!) +} + +@PublishedApi +internal fun Throwable.addSuppressedMirai(e: Throwable) { + if (e === this) { + return + } + kotlin.runCatching { + this.addSuppressed(e) + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/StringFuzzyTest.kt b/backend/mirai-console/src/test/kotlin/StringFuzzyTest.kt index 58177caeb..d9259738c 100644 --- a/backend/mirai-console/src/test/kotlin/StringFuzzyTest.kt +++ b/backend/mirai-console/src/test/kotlin/StringFuzzyTest.kt @@ -1,5 +1,5 @@ -import net.mamoe.mirai.console.utils.fuzzySearch -import net.mamoe.mirai.console.utils.fuzzySearchOnly +import net.mamoe.mirai.console.command.fuzzySearch +import net.mamoe.mirai.console.command.fuzzySearchOnly class Him188(val name:String){ override fun toString(): String {