diff --git a/.github/workflows/cui.yml b/.github/workflows/cui.yml index 0bbcd36eb..eee6c4ecb 100644 --- a/.github/workflows/cui.yml +++ b/.github/workflows/cui.yml @@ -28,7 +28,7 @@ jobs: run: ./gradlew build # if test's failed, don't publish - name: Gradle :mirai-console:cuiCloudUpload run: ./gradlew :mirai-console:cuiCloudUpload -Dcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Pcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Dcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} -Pcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} - - name: Gradle :mirai-console-qqandroid:cuiCloudUpload + - name: Gradle :mirai-console-graphical:cuiCloudUpload run: ./gradlew :mirai-console-graphical:cuiCloudUpload -Dcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Pcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Dcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} -Pcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml index 8dbaf88c1..2277d04cd 100644 --- a/.github/workflows/shadow.yml +++ b/.github/workflows/shadow.yml @@ -28,7 +28,7 @@ jobs: run: ./gradlew build # if test's failed, don't publish - name: Gradle :mirai-console:githubUpload run: ./gradlew :mirai-console:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }} - - name: Gradle :mirai-console-qqandroid:githubUpload + - name: Gradle :mirai-console-graphical:githubUpload run: ./gradlew :mirai-console-graphical:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }} diff --git a/README.md b/README.md index de9b23dd3..db4d71a2c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持 console 由后端和前端一起工作. 使用时必须选择一个前端. -**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构,master 分支将不再维护。可以在 [reborn](https://github.com/mamoe/mirai-console/tree/reborn) 查看进度** +**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构,master 分支将不再维护。** +**`mirai-console` 将在短时间内不可用。` - `mirai-console`: console 的后端, 包含插件管理, 指令系统, 配置系统. 还包含一个轻量命令行的前端 (因此可以独立启动 `mirai-console`). - `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt index 00aa5a915..87af74d6a 100644 --- a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt @@ -21,11 +21,13 @@ fun main() { }.writeText(buildString { appendln(COPYRIGHT) appendln() + appendln(FILE_SUPPRESS) + appendln() appendln(PACKAGE) appendln() - // appendln(IMPORTS) - // appendln() - // appendln() + appendln(IMPORTS) + appendln() + appendln() appendln(DO_NOT_MODIFY) appendln() appendln() @@ -44,8 +46,12 @@ private val PACKAGE = """ package net.mamoe.mirai.console.setting """.trimIndent() +private val FILE_SUPPRESS = """ +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused") +""".trimIndent() private val IMPORTS = """ -import kotlinx.serialization.builtins.* +import net.mamoe.mirai.console.setting.internal.valueImpl +import kotlin.internal.LowPriorityInOverloadResolution """.trimIndent() fun genAllValueUseSite(): String = buildString { @@ -111,6 +117,26 @@ fun genAllValueUseSite(): String = buildString { @JvmName("valueMutable") inline fun Setting.value(default: MutableSet): MutableSettingSetValue = valueImpl(default) + + /** + * 创建一个只引用对象而不跟踪其属性的值. + * + * @param T 类型. 必须拥有 [kotlinx.serialization.Serializable] 注解 (因此编译器会自动生成序列化器) + */ + @DangerousReferenceOnlyValue + @JvmName("valueDynamic") + @LowPriorityInOverloadResolution + inline fun Setting.value(default: T): Value = valueImpl(default) + + @RequiresOptIn( + ""${'"'} + 这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎. + 对值的改变不会触发自动保存, 也不会同步到 UI 中. 在 UI 中只能编辑序列化之后的值. + ""${'"'}, level = RequiresOptIn.Level.WARNING + ) + @Retention(AnnotationRetention.BINARY) + @Target(AnnotationTarget.FUNCTION) + annotation class DangerousReferenceOnlyValue """ ) } 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 0401e5093..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 @@ -16,7 +16,7 @@ import java.io.File fun main() { println(File("").absolutePath) // default project base dir - File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt").apply { + File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt").apply { createNewFile() }.writeText(buildString { appendln(COPYRIGHT) @@ -41,12 +41,13 @@ private val DO_NOT_MODIFY = """ """.trimIndent() private val PACKAGE = """ -package net.mamoe.mirai.console.setting +package net.mamoe.mirai.console.setting.internal """.trimIndent() private val IMPORTS = """ import kotlinx.serialization.* import kotlinx.serialization.builtins.* +import net.mamoe.mirai.console.setting.* """.trimIndent() fun genAllValueImpl(): String = buildString { @@ -56,20 +57,20 @@ fun genAllValueImpl(): String = buildString { // PRIMITIVE for (number in NUMBERS + OTHER_PRIMITIVES) { - appendln(genValueImpl(number, number, "$number.serializer()", false)) + appendln(genPrimitiveValueImpl(number, number, "$number.serializer()", false)) appendln() } // PRIMITIVE ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) { - appendln(genValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true)) + appendln(genPrimitiveValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true)) appendln() } // TYPED ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( - genValueImpl( + genPrimitiveValueImpl( "Array<${number}>", "Typed${number}Array", "ArraySerializer(${number}.serializer())", @@ -83,7 +84,8 @@ fun genAllValueImpl(): String = buildString { for (collectionName in listOf("List", "Set")) { for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( - genValueImpl( + genCollectionValueImpl( + collectionName, "${collectionName}<${number}>", "${number}${collectionName}", "${collectionName}Serializer(${number}.serializer())", @@ -108,7 +110,8 @@ fun genAllValueImpl(): String = buildString { ): Mutable${number}${collectionName}Value { var internalValue: Mutable${collectionName}<${number}> = default - return object : Mutable${number}${collectionName}Value(), Mutable${collectionName}<${number}> by dynamicMutable${collectionName}({ internalValue }) { + val delegt = dynamicMutable${collectionName}{ internalValue } + return object : Mutable${number}${collectionName}Value(), Mutable${collectionName}<${number}> by delegt { override var value: Mutable${collectionName}<${number}> get() = internalValue set(new) { @@ -118,7 +121,7 @@ fun genAllValueImpl(): String = buildString { } } - private inline val `this` get() = this + private val outerThis get() = this override val serializer: KSerializer> = object : KSerializer> { private val delegate = ${collectionName}Serializer(${number}.serializer()) @@ -126,7 +129,7 @@ fun genAllValueImpl(): String = buildString { override fun deserialize(decoder: Decoder): Mutable${collectionName}<${number}> { return delegate.deserialize(decoder).toMutable${collectionName}().observable { - onElementChanged(`this`) + onElementChanged(outerThis) } } @@ -177,7 +180,12 @@ fun genAllValueImpl(): String = buildString { ) } -fun genValueImpl(kotlinTypeName: String, miraiValueName: String, serializer: String, isArray: Boolean): String = +fun genPrimitiveValueImpl( + kotlinTypeName: String, + miraiValueName: String, + serializer: String, + isArray: Boolean +): String = """ internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value { return object : ${miraiValueName}Value() { @@ -205,3 +213,39 @@ fun genValueImpl(kotlinTypeName: String, miraiValueName: String, serializer: Str } """.trimIndent() + "\n" + +fun genCollectionValueImpl( + collectionName: String, + kotlinTypeName: String, + miraiValueName: String, + serializer: String, + isArray: Boolean +): String = + """ + internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value { + var internalValue: $kotlinTypeName = default + val delegt = dynamic$collectionName { internalValue } + return object : ${miraiValueName}Value(), $kotlinTypeName by delegt { + override var value: $kotlinTypeName + get() = internalValue + set(new) { + ${ + if (isArray) """ + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + """.trim() + else """ + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + """.trim() + } + } + override val serializer = $serializer + } + } + """.trimIndent() + "\n" + diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt index 3b3d095c9..018f7242e 100644 --- a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt @@ -74,7 +74,7 @@ fun genPublicApi() = buildString { appendln( """ /** - * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt + * !!! This file is auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt * !!! for better performance * !!! DO NOT MODIFY THIS FILE MANUALLY */ @@ -157,13 +157,14 @@ sealed class Value : ReadWriteProperty { appendln() for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) { - val template = """ + appendln( + """ abstract class ${number}ArrayValue internal constructor() : PrimitiveArrayValue<${number}Array>(), Iterable<${number}> { override fun iterator(): Iterator<${number}> = this.value.iterator() } """ - - appendln(template) + ) + appendln() } appendln() @@ -180,11 +181,11 @@ sealed class Value : ReadWriteProperty { appendln() for (number in (NUMBERS + OTHER_PRIMITIVES)) { - val template = """ + appendln( + """ abstract class Typed${number}ArrayValue internal constructor() : TypedPrimitiveArrayValue<${number}>() """ - - appendln(template) + ) } appendln() @@ -194,9 +195,7 @@ sealed class Value : ReadWriteProperty { appendln( """ - sealed class ${collectionName}Value : Value<${collectionName}>(), Iterable{ - override fun iterator() = this.value.iterator() - } + sealed class ${collectionName}Value : Value<${collectionName}>(), ${collectionName} """ ) @@ -265,7 +264,7 @@ sealed class Value : ReadWriteProperty { * 只引用这个对象, 而不跟踪其成员. * 仅适用于基础类型, 用于 mutable list/map 等情况; 或标注了 [Serializable] 的类. */ - abstract class DynamicReferenceValue internal constructor() : Value() + abstract class DynamicReferenceValue : Value() """ ) } \ No newline at end of file diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 60572b5fd..b2e0e655f 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,18 +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("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" + } } } } @@ -32,39 +59,17 @@ dependencies { compileAndRuntime("net.mamoe:mirai-core:${Versions.Mirai.core}") compileAndRuntime(kotlin("stdlib")) - api("net.mamoe.yamlkt:yamlkt:0.2.0") - - api("org.jsoup:jsoup:1.12.1") - + api("net.mamoe.yamlkt:yamlkt:0.3.1") api("org.jetbrains:annotations:19.0.0") + api(kotlinx("coroutines-jdk8", Versions.Kotlin.coroutines)) 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 { @@ -129,4 +134,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/java/net/mamoe/mirai/console/event/EventListener.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/event/EventListener.java deleted file mode 100644 index fbc6be9cb..000000000 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/event/EventListener.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.mamoe.mirai.console.event; - -import net.mamoe.mirai.console.plugins.PluginBase; -import net.mamoe.mirai.event.Event; -import net.mamoe.mirai.event.Listener; -import net.mamoe.mirai.event.ListeningStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Consumer; -import java.util.function.Function; - -public class EventListener { - - PluginBase base; - - public EventListener( - PluginBase base - ){ - this.base = base; - } - - /** - * 监听一个事件, 当 {@code onEvent} 返回 {@link ListeningStatus#STOPPED} 时停止监听. - * 机器人离线后不会停止监听. - * - * @param eventClass 事件类 - * @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听. - * @param 事件类型 - * @return 事件监听器. 可调用 {@link Listener#complete()} 或 {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止. - */ - @NotNull - public Listener subscribe(@NotNull Class eventClass, @NotNull Function onEvent) { - return EventsImplKt.subscribeEventForJaptOnly(eventClass, base, onEvent); - } - - - /** - * 监听一个事件, 直到手动停止. - * 机器人离线后不会停止监听. - * - * @param eventClass 事件类 - * @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听. - * @param 事件类型 - * @return 事件监听器. 可调用 {@link Listener#complete()} 或 {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止. - */ - @NotNull - public Listener subscribeAlways(@NotNull Class eventClass, @NotNull Consumer onEvent) { - return EventsImplKt.subscribeEventForJaptOnly(eventClass, base, onEvent); - } - -} diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/event/Events.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/event/Events.java deleted file mode 100644 index 6dcdd0e2a..000000000 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/event/Events.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.event; - -import net.mamoe.mirai.event.Event; -import org.jetbrains.annotations.NotNull; - -/** - * 事件处理 - */ -public final class Events { - /** - * 阻塞地广播一个事件. - * - * @param event 事件 - * @param 事件类型 - * @return {@code event} 本身 - */ - @NotNull - public static E broadcast(@NotNull E event) { - return EventsImplKt.broadcast(event); - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/scheduler/SchedulerTaskManager.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/scheduler/SchedulerTaskManager.java deleted file mode 100644 index 5c49b524a..000000000 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/scheduler/SchedulerTaskManager.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.mamoe.mirai.console.scheduler; - -/** - * Java开发者的SchedulerTask - * 使用kt实现, java的API - */ - -/** - * PluginScheduler.RepeatTaskReceipt repeatTaskReceipt = this.getScheduler().repeat(() -> { - * getLogger().info("I repeat"); - * },100); - * - * - * this.getScheduler().delay(() -> { - * repeatTaskReceipt.setCancelled(true); - * },10000); - * - * - * Future future = this.getScheduler().async(() -> { - * //do some task - * return "success"; - * }); - * - * try { - * getLogger().info(future.get()); - * } catch (InterruptedException | ExecutionException e) { - * e.printStackTrace(); - * } - */ - -public class SchedulerTaskManager { - public static SchedulerTaskManagerInstance getInstance(){ - return SchedulerTaskManagerInstance.INSTANCE; - } -} - diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/BotManager.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/BotManager.java index 080ca51bb..c30422724 100644 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/BotManager.java +++ b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/BotManager.java @@ -1,3 +1,12 @@ +/* + * 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.utils; import net.mamoe.mirai.Bot; @@ -5,8 +14,8 @@ import net.mamoe.mirai.Bot; import java.util.List; /** - * 获取Bot Manager - * Java友好API + * 获取 Bot Manager + * Java 友好 API */ public class BotManager { @@ -15,15 +24,15 @@ public class BotManager { return getManagers(bot); } - public static List getManagers(Bot bot){ + public static List getManagers(Bot bot) { return BotHelperKt.getBotManagers(bot); } - public static boolean isManager(Bot bot, long target){ + public static boolean isManager(Bot bot, long target) { return getManagers(bot).contains(target); } - public static boolean isManager(long botAccount, long target){ + public static boolean isManager(long botAccount, long target) { return getManagers(botAccount).contains(target); } } diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/Utils.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/Utils.java deleted file mode 100644 index 6f2c654db..000000000 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/utils/Utils.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.mamoe.mirai.console.utils; - -import org.jetbrains.annotations.Range; - -import java.util.concurrent.Callable; - -public final class Utils { - - /** - * 执行N次 callable - * 成功一次就会结束 - * 否则就会throw - */ - public static T tryNTimes(@Range(from = 1, to = Integer.MAX_VALUE) int n, - Callable callable) throws Exception { - Exception last = null; - - while (n-- > 0) { - try { - return callable.call(); - } catch (Exception e) { - if (last == null) { - last = e; - } else { - try { - last.addSuppressed(e); - } catch (Throwable ignored) { - } - } - } - } - - throw last; - } -} 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..82b299f82 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,35 +13,19 @@ 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.PluginLoader +import net.mamoe.mirai.console.plugins.builtin.JarPluginLoader 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 { - val build: String - val version: String - - /** - * Console运行路径 - */ - val path: String - - /** - * Console前端接口 - */ - val frontEnd: MiraiConsoleFrontEnd - - /** - * 与前端交互所使用的Logger - */ - val mainLogger: MiraiLogger -} - +/** + * mirai 控制台实例. + */ object MiraiConsole : CoroutineScope, IMiraiConsole { private lateinit var instance: IMiraiConsole @@ -52,19 +36,17 @@ 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 +54,36 @@ object MiraiConsole : CoroutineScope, IMiraiConsole { fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity) } + +// 前端使用 +internal interface IMiraiConsole : CoroutineScope { + val build: String + val version: String + + /** + * Console 运行路径 + */ + val rootDir: File + + /** + * Console 前端接口 + */ + val frontEnd: MiraiConsoleFrontEnd + + /** + * 与前端交互所使用的 Logger + */ + val mainLogger: MiraiLogger + + /** + * 内建加载器列表, 一般需要包含 [JarPluginLoader] + */ + val builtInPluginLoaders: List> +} + +/** + * 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/center/CuiPluginCenter.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt index 60b041c17..2d911108d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt @@ -1,89 +1,82 @@ +/* + * 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:OptIn(MiraiExperimentalAPI::class) + package net.mamoe.mirai.console.center import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.request.get import io.ktor.util.KtorExperimentalAPI -import kotlinx.serialization.json.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.UnstableDefault +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration import net.mamoe.mirai.console.utils.retryCatching +import net.mamoe.mirai.utils.MiraiExperimentalAPI import java.io.File +@OptIn(UnstableDefault::class) +internal val json = Json(JsonConfiguration(isLenient = true, ignoreUnknownKeys = true)) + +@OptIn(KtorExperimentalAPI::class) +internal val Http = HttpClient(CIO) + internal object CuiPluginCenter : PluginCenter { - var plugins: JsonArray? = null + var plugins: List? = null /** - * 一页10个吧,pageMinNum=1 + * 一页 10 个 pageMinNum=1 */ override suspend fun fetchPlugin(page: Int): Map { check(page > 0) val startIndex = (page - 1) * 10 val endIndex = startIndex + 9 val map = mutableMapOf() - (startIndex until endIndex).forEach { - if (plugins == null) { + (startIndex until endIndex).forEach { index -> + val plugins = plugins ?: kotlin.run { refresh() - } - if (it >= plugins!!.size) { + plugins + } ?: return mapOf() + + if (index >= plugins.size) { return@forEach } - val info = plugins!![it] - with(info.jsonObject) { - map[this["name"]!!.toString()] = PluginCenter.PluginInsight( - this["name"]?.primitive?.content ?: "", - this["version"]?.primitive?.content ?: "", - this["core"]?.primitive?.content ?: "", - this["console"]?.primitive?.content ?: "", - this["author"]?.primitive?.content ?: "", - this["description"]?.primitive?.content ?: "", - this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(), - this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf() - ) - } + + map[name] = plugins[index] } return map } - @OptIn(KtorExperimentalAPI::class) - private val Http = HttpClient(CIO) - override suspend fun findPlugin(name: String): PluginCenter.PluginInfo? { val result = retryCatching(3) { Http.get("https://miraiapi.jasonczc.cn/getPluginDetailedInfo?name=$name") - }.recover { - return null - }.getOrNull() ?: return null - - if (result == "err:not found") { - return null - } - - return result.asJson().run { - PluginCenter.PluginInfo( - this["name"]?.primitive?.content ?: "", - this["version"]?.primitive?.content ?: "", - this["core"]?.primitive?.content ?: "", - this["console"]?.primitive?.content ?: "", - this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(), - this["author"]?.primitive?.content ?: "", - this["contact"]?.primitive?.content ?: "", - this["description"]?.primitive?.content ?: "", - this["usage"]?.primitive?.content ?: "", - this["vsc"]?.primitive?.content ?: "", - this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(), - this["changeLog"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf() - ) - } + }.getOrElse { return null } + if (result == "err:not found") return null + return json.parse(PluginCenter.PluginInfo.serializer(), result) } override suspend fun refresh() { - val results = Http.get("https://miraiapi.jasonczc.cn/getPluginList").asJson() - if (!(results.containsKey("success") && results["success"]?.boolean == true)) { - error("Failed to fetch plugin list from Cui Cloud") - } - plugins = results["result"]?.jsonArray//先不解析 + @Serializable + data class Result( + val success: Boolean, + val result: List + ) + + val result = json.parse(Result.serializer(), Http.get("https://miraiapi.jasonczc.cn/getPluginList")) + + check(result.success) { "Failed to fetch plugin list from Cui Cloud" } + plugins = result.result } override suspend fun T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File { @@ -112,15 +105,6 @@ internal object CuiPluginCenter : PluginCenter { */ } - override val name: String - get() = "崔云" - - - private val json = Json(JsonConfiguration.Stable) - - private fun String.asJson(): JsonObject { - return json.parseJson(this).jsonObject - } - + override val name: String get() = "崔云" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/PluginCenter.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/PluginCenter.kt index 4456b7f34..deffd06dd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/PluginCenter.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/PluginCenter.kt @@ -1,13 +1,29 @@ +/* + * 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.center +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import net.mamoe.mirai.utils.MiraiExperimentalAPI import java.io.File +@MiraiExperimentalAPI interface PluginCenter { + @Serializable data class PluginInsight( val name: String, val version: String, + @SerialName("core") val coreVersion: String, + @SerialName("console") val consoleVersion: String, val author: String, val description: String, @@ -15,10 +31,13 @@ interface PluginCenter { val commands: List ) + @Serializable data class PluginInfo( val name: String, val version: String, + @SerialName("core") val coreVersion: String, + @SerialName("console") val consoleVersion: String, val tags: List, val author: String, @@ -32,7 +51,7 @@ interface PluginCenter { /** * 获取一些中心的插件基本信息, - * 能获取到多少由实际的PluginCenter决定 + * 能获取到多少由实际的 [PluginCenter] 决定 * 返回 插件名->Insight */ suspend fun fetchPlugin(page: Int): Map @@ -41,17 +60,18 @@ interface PluginCenter { * 尝试获取到某个插件 by 全名, case sensitive * null 则没有 */ - suspend fun findPlugin(name:String):PluginInfo? + suspend fun findPlugin(name: String): PluginInfo? - suspend fun T.downloadPlugin(name:String, progressListener:T.(Float) -> Unit): File + suspend fun T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File - suspend fun downloadPlugin(name:String, progressListener:PluginCenter.(Float) -> Unit): File = downloadPlugin(name,progressListener) + suspend fun downloadPlugin(name: String, progressListener: PluginCenter.(Float) -> Unit): File = + downloadPlugin(name, progressListener) /** * 刷新 */ suspend fun refresh() - val name:String + val name: String } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 49d664c51..21b66b750 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -13,7 +13,9 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.description.* import net.mamoe.mirai.console.command.description.CommandParam -import net.mamoe.mirai.console.command.hasAnnotation +import net.mamoe.mirai.console.command.description.CommandParserContext +import net.mamoe.mirai.console.command.description.EmptyCommandParserContext +import net.mamoe.mirai.console.command.description.plus import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage import java.lang.Exception diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 15d6dd4dc..cd3a229f2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -1,10 +1,19 @@ +/* + * 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("NOTHING_TO_INLINE", "unused") @file:JvmName("CommandManager") package net.mamoe.mirai.console.command import kotlinx.atomicfu.locks.withLock -import net.mamoe.mirai.console.plugins.PluginBase +import net.mamoe.mirai.console.plugins.Plugin import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.SingleMessage @@ -13,7 +22,7 @@ sealed class CommandOwner object TestCommandOwner : CommandOwner() -abstract class PluginCommandOwner(val plugin: PluginBase) : CommandOwner() +abstract class PluginCommandOwner(val plugin: Plugin) : CommandOwner() // 由前端实现 internal abstract class ConsoleCommandOwner : CommandOwner() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt index e8f52b4b4..2023953fa 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt @@ -117,7 +117,6 @@ inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean = inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() } - internal class OrCommandPermission( private val first: CommandPermission, private val second: CommandPermission @@ -127,6 +126,7 @@ internal class OrCommandPermission( } } + internal class AndCommandPermission( private val first: CommandPermission, private val second: CommandPermission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParser.kt index 740f6d2e1..94acd0519 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParser.kt @@ -1,3 +1,12 @@ +/* + * 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("NOTHING_TO_INLINE") package net.mamoe.mirai.console.command.description 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/description/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt index b20dad891..2ac138bc5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt @@ -1,3 +1,12 @@ +/* + * 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.command.description 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/event/CommandExecutionEvent.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/CommandExecutionEvent.kt index 02dadb887..6a048337f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/CommandExecutionEvent.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/CommandExecutionEvent.kt @@ -1,3 +1,12 @@ +/* + * 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.event import net.mamoe.mirai.console.command.Command diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/ConsoleEvent.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/ConsoleEvent.kt index 9a0504837..50b2aeff6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/ConsoleEvent.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/ConsoleEvent.kt @@ -1,3 +1,12 @@ +/* + * 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.event import net.mamoe.mirai.event.Event diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/EventsImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/EventsImpl.kt deleted file mode 100644 index 738088acb..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/event/EventsImpl.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - -package net.mamoe.mirai.console.event - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking -import net.mamoe.mirai.event.Event -import net.mamoe.mirai.event.Listener -import net.mamoe.mirai.event.ListeningStatus -import net.mamoe.mirai.event.broadcast -import net.mamoe.mirai.event.internal._subscribeEventForJaptOnly -import java.util.function.Consumer -import java.util.function.Function - -internal fun broadcast(e: E): E = runBlocking { e.broadcast() } - -internal fun Class.subscribeEventForJaptOnly( - scope: CoroutineScope, - onEvent: Function -): Listener = _subscribeEventForJaptOnly(scope, onEvent) - -internal fun Class.subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Consumer): Listener = - _subscribeEventForJaptOnly(scope, onEvent) \ 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/JarPlugin.kt deleted file mode 100644 index 83c2cd4e7..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JarPlugin.kt +++ /dev/null @@ -1,426 +0,0 @@ -/* - * 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("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - - -package net.mamoe.mirai.console.plugins - -import kotlinx.coroutines.CoroutineScope -import kotlinx.serialization.Serializable - - -sealed class JarPlugin : Plugin(), CoroutineScope { - internal lateinit var _description: JarPluginDescription - - final override val description: PluginDescription get() = _description - final override val loader: JarPluginLoader get() = JarPluginLoader -} - -@Serializable -internal class JarPluginDescription( - override val name: String, - override val author: String, - override val version: String, - override val info: String, - override val depends: List -) : PluginDescription - -abstract class JavaPlugin : JarPlugin() - -abstract class KotlinPlugin : JarPlugin() - - -/** - * 内建的 Jar (JVM) 插件加载器 - */ -object JarPluginLoader : PluginLoader { - override val list: List - get() = TODO("Not yet implemented") - - override fun load(plugin: JarPlugin) { - TODO("Not yet implemented") - } - - override fun enable(plugin: JarPlugin) { - TODO("Not yet implemented") - } -} - -/* -object PluginManagerOld { - /** - * 通过插件获取介绍 - * @see description - */ - fun getPluginDescription(base: PluginBase): PluginDescription { - nameToPluginBaseMap.forEach { (s, pluginBase) -> - if (pluginBase == base) { - return pluginDescriptions[s]!! - } - } - error("can not find plugin description") - } - - /** - * 获取所有插件摘要 - */ - fun getAllPluginDescriptions(): Collection { - return pluginDescriptions.values - } - - /** - * 关闭所有插件 - */ - @JvmOverloads - fun disablePlugins(throwable: CancellationException? = null) { - pluginsSequence.forEach { plugin -> - plugin.unregisterAllCommands() - plugin.disable(throwable) - } - nameToPluginBaseMap.clear() - pluginDescriptions.clear() - pluginsLoader.clear() - pluginsSequence.clear() - } - - /** - * 重载所有插件 - */ - fun reloadPlugins() { - pluginsSequence.forEach { - it.disable() - } - loadPlugins(false) - } - - /** - * 尝试加载全部插件 - */ - fun loadPlugins(clear: Boolean = true) = loadPluginsImpl(clear) - - - ////////////////// - //// internal //// - ////////////////// - - internal val pluginsPath = (MiraiConsole.path + "/plugins/").replace("//", "/").also { - File(it).mkdirs() - } - - private val logger = MiraiConsole.newLogger("Plugin Manager") - - /** - * 加载成功的插件, 名字->插件 - */ - internal val nameToPluginBaseMap: MutableMap = mutableMapOf() - - /** - * 加载成功的插件, 名字->插件摘要 - */ - private val pluginDescriptions: MutableMap = mutableMapOf() - - /** - * 加载插件的PluginsLoader - */ - private val pluginsLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) - - /** - * 插件优先级队列 - * 任何操作应该按这个Sequence顺序进行 - * 他的优先级取决于依赖, - * 在这个队列中, 被依赖的插件会在依赖的插件之前 - */ - private val pluginsSequence: LockFreeLinkedList = LockFreeLinkedList() - - - /** - * 广播Command方法 - */ - internal fun onCommand(command: Command, sender: CommandSender, args: List) { - pluginsSequence.forEach { - try { - it.onCommand(command, sender, args) - } catch (e: Throwable) { - logger.info(e) - } - } - } - - - @Volatile - internal var lastPluginName: String = "" - - /** - * 判断文件名/插件名是否已加载 - */ - private fun isPluginLoaded(file: File, name: String): Boolean { - pluginDescriptions.forEach { - if (it.key == name || it.value.file == file) { - return true - } - } - return false - } - - /** - * 寻找所有安装的插件(在文件夹), 并将它读取, 记录位置 - * 这个不等同于加载的插件, 可以理解为还没有加载的插件 - */ - internal data class FindPluginsResult( - val pluginsLocation: MutableMap, - val pluginsFound: MutableMap - ) - - internal fun findPlugins(): FindPluginsResult { - val pluginsLocation: MutableMap = mutableMapOf() - val pluginsFound: MutableMap = mutableMapOf() - - File(pluginsPath).listFiles()?.forEach { file -> - if (file != null && file.extension == "jar") { - val jar = JarFile(file) - val pluginYml = - jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull() - - if (pluginYml == null) { - logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin") - } else { - try { - val description = PluginDescription.readFromContent( - URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().let { - val res = it.inputStream.use { input -> - input.readBytes().encodeToString() - } - - // 关闭jarFile,解决热更新插件问题 - (it as JarURLConnection).jarFile.close() - res - }, file - ) - if (!isPluginLoaded(file, description.name)) { - pluginsFound[description.name] = description - pluginsLocation[description.name] = file - } - } catch (e: Exception) { - logger.info(e) - } - } - } - } - return FindPluginsResult(pluginsLocation, pluginsFound) - } - - internal fun loadPluginsImpl(clear: Boolean = true) { - logger.info("""开始加载${pluginsPath}下的插件""") - val findPluginsResult = findPlugins() - val pluginsFound = findPluginsResult.pluginsFound - val pluginsLocation = findPluginsResult.pluginsLocation - - //不仅要解决A->B->C->A, 还要解决A->B->A->A - fun checkNoCircularDepends( - target: PluginDescription, - needDepends: List, - existDepends: MutableList - ) { - - if (!target.noCircularDepend) { - return - } - - existDepends.add(target.name) - - if (needDepends.any { existDepends.contains(it) }) { - target.noCircularDepend = false - } - - existDepends.addAll(needDepends) - - needDepends.forEach { - if (pluginsFound.containsKey(it)) { - checkNoCircularDepends(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends) - } - } - } - - pluginsFound.values.forEach { - checkNoCircularDepends(it, it.depends, mutableListOf()) - } - - //load plugin individually - fun loadPlugin(description: PluginDescription): Boolean { - if (!description.noCircularDepend) { - logger.error("Failed to load plugin " + description.name + " because it has circular dependency") - return false - } - - if (description.loaded || nameToPluginBaseMap.containsKey(description.name)) { - return true - } - - description.depends.forEach { dependent -> - if (!pluginsFound.containsKey(dependent)) { - logger.error("Failed to load plugin " + description.name + " because it need " + dependent + " as dependency") - return false - } - val depend = pluginsFound[dependent]!! - - if (!loadPlugin(depend)) {//先加载depend - logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load") - return false - } - } - - logger.info("loading plugin " + description.name) - - val jarFile = pluginsLocation[description.name]!! - val pluginClass = try { - pluginsLoader.loadPluginMainClassByJarFile(description.name, description.basePath, jarFile) - } catch (e: ClassNotFoundException) { - pluginsLoader.loadPluginMainClassByJarFile(description.name, "${description.basePath}Kt", jarFile) - } - - val subClass = pluginClass.asSubclass(PluginBase::class.java) - - lastPluginName = description.name - val plugin: PluginBase = - subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply { - kotlin.runCatching { - this.isAccessible = true - } - }.newInstance() - plugin.dataFolder // initialize right now - - description.loaded = true - logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author) - logger.info(description.info) - - nameToPluginBaseMap[description.name] = plugin - pluginDescriptions[description.name] = description - plugin.pluginName = description.name - pluginsSequence.addLast(plugin)//按照实际加载顺序加入队列 - return true - } - - - if (clear) { - //清掉优先级队列, 来重新填充 - pluginsSequence.clear() - } - - pluginsFound.values.forEach { - try { - // 尝试加载插件 - loadPlugin(it) - } catch (e: Throwable) { - pluginsLoader.remove(it.name) - when (e) { - is ClassCastException -> logger.error( - "failed to load plugin " + it.name + " , Main class does not extends PluginBase", - e - ) - is ClassNotFoundException -> logger.error( - "failed to load plugin " + it.name + " , Main class not found under " + it.basePath, - e - ) - is NoClassDefFoundError -> logger.error( - "failed to load plugin " + it.name + " , dependent class not found.", - e - ) - else -> logger.error("failed to load plugin " + it.name, e) - } - } - } - - - pluginsSequence.forEach { - try { - it.load() - } catch (ignored: Throwable) { - logger.info(ignored) - logger.info(it.pluginName + " failed to load, disabling it") - logger.info(it.pluginName + " 推荐立即删除/替换并重启") - if (ignored is CancellationException) { - disablePlugin(it, ignored) - } else { - disablePlugin(it) - } - } - } - - pluginsSequence.forEach { - try { - it.enable() - } catch (ignored: Throwable) { - logger.info(ignored) - logger.info(it.pluginName + " failed to enable, disabling it") - logger.info(it.pluginName + " 推荐立即删除/替换并重启") - if (ignored is CancellationException) { - disablePlugin(it, ignored) - } else { - disablePlugin(it) - } - } - } - - logger.info("""加载了${nameToPluginBaseMap.size}个插件""") - } - - private fun disablePlugin( - plugin: PluginBase, - exception: CancellationException? = null - ) { - plugin.unregisterAllCommands() - plugin.disable(exception) - nameToPluginBaseMap.remove(plugin.pluginName) - pluginDescriptions.remove(plugin.pluginName) - pluginsLoader.remove(plugin.pluginName) - pluginsSequence.remove(plugin) - } - - - /** - * 根据插件名字找Jar的文件 - * null => 没找到 - * 这里的url的jarFile没关,热更新插件可能出事 - */ - internal fun getJarFileByName(pluginName: String): File? { - File(pluginsPath).listFiles()?.forEach { file -> - if (file != null && file.extension == "jar") { - val jar = JarFile(file) - val pluginYml = - jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull() - if (pluginYml != null) { - val description = - PluginDescription.readFromContent( - URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use { - it.readBytes().encodeToString() - }, file - ) - if (description.name.toLowerCase() == pluginName.toLowerCase()) { - return file - } - } - } - } - return null - } - - - /** - * 根据插件名字找Jar中的文件 - * null => 没找到 - * 这里的url的jarFile没关,热更新插件可能出事 - */ - internal fun getFileInJarByName(pluginName: String, toFind: String): InputStream? { - val jarFile = getJarFileByName(pluginName) ?: return null - val jar = JarFile(jarFile) - val toFindFile = - jar.entries().asSequence().filter { it.name == toFind }.firstOrNull() ?: return null - return URL("jar:file:" + jarFile.absoluteFile + "!/" + toFindFile.name).openConnection().inputStream - } -}*/ \ No newline at end of file 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..d9ecf24e4 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,17 @@ 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 net.mamoe.mirai.console.plugins.builtin.JvmPlugin /** - * 插件信息 - */ -interface PluginDescription { - val name: String - val author: String - val version: String - val info: String - val depends: List -} - -/** - * 插件基类. + * 表示一个 mirai-console 插件. * - * 内建的插件类型: - * - [JarPlugin] + * @see JvmPlugin + * @see PluginDescription 插件描述 */ -abstract class Plugin : CoroutineScope { - abstract val description: PluginDescription - abstract val loader: PluginLoader<*> - - @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() {} +interface Plugin { + /** + * 所属插件加载器实例 + */ + val loader: PluginLoader<*, *> } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt deleted file mode 100644 index d83a35559..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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("MemberVisibilityCanBePrivate", "unused") - -package net.mamoe.mirai.console.plugins - -import kotlinx.coroutines.* -import net.mamoe.mirai.console.command.Command -import net.mamoe.mirai.console.command.CommandSender -import net.mamoe.mirai.console.event.EventListener -import net.mamoe.mirai.console.scheduler.PluginScheduler -import net.mamoe.mirai.utils.MiraiLogger -import java.io.File -import java.io.InputStream -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -/** - * 所有插件的基类 - */ -abstract class PluginBase -@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope { - final override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob() - - /** - * 插件被分配的数据目录。数据目录会与插件名称同名。 - */ - val dataFolder: File by lazy { - TODO() - /* - File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName) - .also { it.mkdir() }*/ - } - - /** - * 当一个插件被加载时调用 - */ - open fun onLoad() { - - } - - /** - * 当插件被启用时调用. - * 此时所有其他插件都已经被调用了 [onLoad] - */ - open fun onEnable() { - - } - - /** - * 当插件关闭前被调用 - */ - open fun onDisable() { - - } - - /** - * 当任意指令被使用时调用. - * - * 指令调用将优先触发 [Command.onCommand], 若该函数返回 `false`, 则不会调用 [PluginBase.onCommand] - */ - open fun onCommand(command: Command, sender: CommandSender, args: List) { - - } - - /** - * 插件的日志 - */ - val logger: MiraiLogger by lazy { - TODO() - /* - SimpleLogger("Plugin $pluginName") { priority, message, e -> - val identityString = "[${pluginName}]" - MiraiConsole.logger(priority, identityString, 0, message) - if (e != null) { - MiraiConsole.logger(priority, identityString, 0, e) - } - }*/ - } - - /** - * 加载 resources 中的文件 - */ - fun getResources(fileName: String): InputStream? { - return try { - this.javaClass.classLoader.getResourceAsStream(fileName) - } catch (e: Exception) { - TODO() - - /* - PluginManager.getFileInJarByName( - this.pluginName, - fileName - )*/ - } - } - - - /** - * Java API Scheduler - */ - val scheduler: PluginScheduler? = PluginScheduler(this.coroutineContext) - - /** - * Java API EventListener - */ - val eventListener: EventListener = EventListener(@Suppress("LeakingThis") this) - - - // internal - - private var loaded = false - private var enabled = false - - internal fun load() { - if (!loaded) { - onLoad() - loaded = true - } - } - - internal fun enable() { - if (!enabled) { - onEnable() - enabled = true - } - } - - internal fun disable(throwable: CancellationException? = null) { - if (enabled) { - this.coroutineContext[Job]!!.cancelChildren(throwable) - try { - onDisable() - } catch (e: Exception) { - logger.error(e) - } - enabled = false - } - } - - internal var pluginName: String = "" -} 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..43af25148 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,76 @@ +/* + * 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.plugins +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..09724d3ee 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,194 @@ +/* + * 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("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.concurrent.locks.ReentrantLock + +val Plugin.description: PluginDescription + get() = PluginManager.resolvedPlugins.firstOrNull { it == this }?.description ?: error("Plugin is unloaded") + +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 + private val _pluginLoaders: MutableList> = mutableListOf() + private val loadersLock: ReentrantLock = ReentrantLock() - fun registerPluginLoader(loader: PluginLoader<*>) { - _loaders.add(loader) + @JvmField + internal val resolvedPlugins: MutableList = mutableListOf() + + /** + * 已加载的插件列表 + */ + @JvmStatic + val plugins: List + get() = resolvedPlugins.toList() + + /** + * 内建的插件加载器列表. 由 [MiraiConsole] 初始化 + */ + @JvmStatic + val builtInLoaders: List> + get() = MiraiConsole.builtInPluginLoaders + + /** + * 由插件创建的 [PluginLoader] + */ + @JvmStatic + val pluginLoaders: List> + get() = _pluginLoaders.toList() + + @JvmStatic + fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean = loadersLock.withLock { + if (_pluginLoaders.any { it::class == loader }) { + return false + } + _pluginLoaders.add(loader) } - fun unregisterPluginLoader(loader: PluginLoader<*>) { - _loaders.remove(loader) + @JvmStatic + fun unregisterPluginLoader(loader: PluginLoader<*, *>) = loadersLock.withLock { + _pluginLoaders.remove(loader) } - fun loadPlugins() { - loaders.forEach(PluginLoader<*>::loadAll) + + // region LOADING + + private fun

PluginLoader.loadPluginNoEnable(description: D): P { + // TODO: 2020/5/23 HANDLE INITIALIZATION EXCEPTION + 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())) } -} \ No newline at end of file + + /** + * 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.isOptional || 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.isOptional || 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 +} + +class PluginMissingDependencyException : PluginResolutionException { + 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) +} + + +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 } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManagerOld.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManagerOld.kt deleted file mode 100644 index ade4a15c6..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManagerOld.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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", "unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - -package net.mamoe.mirai.console.plugins - -val PluginBase.description: PluginDescription get() = TODO() - diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt new file mode 100644 index 000000000..476a863af --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt @@ -0,0 +1,105 @@ +/* + * 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.plugins.builtin + +import kotlinx.coroutines.* +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.plugins.AbstractFilePluginLoader +import net.mamoe.mirai.console.plugins.PluginLoadException +import net.mamoe.mirai.console.plugins.PluginsLoader +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.error +import net.mamoe.yamlkt.Yaml +import java.io.File +import java.net.URL +import kotlin.coroutines.CoroutineContext +import kotlin.reflect.full.createInstance + +/** + * 内建的 Jar (JVM) 插件加载器 + */ +object JarPluginLoader : AbstractFilePluginLoader("jar"), CoroutineScope { + private val logger: MiraiLogger by lazy { + MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!) + } + + override val coroutineContext: CoroutineContext by lazy { + MiraiConsole.coroutineContext + SupervisorJob( + MiraiConsole.coroutineContext[Job] + ) + CoroutineExceptionHandler { _, throwable -> + logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) + } + } + private val supervisor: Job = coroutineContext[Job]!! + + private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) + + init { + supervisor.invokeOnCompletion { + classLoader.clear() + } + } + + override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description + + override fun Sequence.mapToDescription(): List { + return this.associateWith { URL("jar:${it.absolutePath}!/plugin.yml") }.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(name, mainClassName, 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 JvmPluginImpl) { + 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 JvmPluginImpl) { + plugin.internalOnEnable() + } else plugin.onEnable() + } + + override fun disable(plugin: JvmPlugin) { + if (plugin is JvmPluginImpl) { + plugin.internalOnDisable() + } else plugin.onDisable() + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPlugin.kt new file mode 100644 index 000000000..970f0b8ae --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPlugin.kt @@ -0,0 +1,129 @@ +/* + * 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("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS") + +package net.mamoe.mirai.console.plugins.builtin + +import kotlinx.atomicfu.locks.withLock +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.console.plugins.Plugin +import net.mamoe.mirai.console.plugins.PluginLoader +import net.mamoe.mirai.console.utils.JavaPluginScheduler +import net.mamoe.mirai.utils.MiraiLogger +import java.util.concurrent.locks.ReentrantLock +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + + +/** + * Java 或 Kotlin Jar 插件 + * + * @see JavaPlugin Java 插件 + * @see KotlinPlugin Kotlin 插件 + */ +interface JvmPlugin : Plugin, CoroutineScope { + /** 日志 */ + val logger: MiraiLogger + + /** 插件描述 */ + val description: JvmPluginDescription + + /** 所属插件加载器实例 */ + override val loader: PluginLoader<*, *> get() = JarPluginLoader + + @JvmDefault + fun onLoad() { + } + + @JvmDefault + fun onEnable() { + } + + @JvmDefault + fun onDisable() { + } +} + +/** + * Java 插件的父类 + */ +abstract class JavaPlugin @JvmOverloads constructor( + parentCoroutineContext: CoroutineContext = EmptyCoroutineContext +) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) { + + /** + * Java API Scheduler + */ + val scheduler: JavaPluginScheduler = + JavaPluginScheduler(this.coroutineContext) +} + +abstract class KotlinPlugin @JvmOverloads constructor( + parentCoroutineContext: CoroutineContext = EmptyCoroutineContext +) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) { + // that's it +} + +internal sealed class JvmPluginImpl( + parentCoroutineContext: CoroutineContext +) : JvmPlugin, CoroutineScope { + /** + * Initialized immediately after construction of [JvmPluginImpl] instance + */ + @Suppress("PropertyName") + internal lateinit var _description: JvmPluginDescription + + // for future use + @Suppress("PropertyName") + @JvmField + internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext + + override val description: JvmPluginDescription get() = _description + + final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) } + + @JvmField + internal val coroutineContextInitializer = { + CoroutineExceptionHandler { _, throwable -> logger.error(throwable) } + .plus(parentCoroutineContext) + .plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext + } + + private var firstRun = true + + internal fun internalOnDisable() { + firstRun = false + this.onDisable() + } + + internal fun internalOnLoad() { + this.onLoad() + } + + internal fun internalOnEnable() { + if (!firstRun) refreshCoroutineContext() + this.onEnable() + } + + private fun refreshCoroutineContext(): CoroutineContext { + return coroutineContextInitializer().also { _coroutineContext = it } + } + + private val contextUpdateLock: ReentrantLock = ReentrantLock() + private var _coroutineContext: CoroutineContext? = null + final override val coroutineContext: CoroutineContext + get() = _coroutineContext + ?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() } + +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPluginDescription.kt new file mode 100644 index 000000000..562ad58f2 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPluginDescription.kt @@ -0,0 +1,58 @@ +/* + * 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.plugins.builtin + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.mamoe.mirai.console.plugins.FilePluginDescription +import net.mamoe.mirai.console.plugins.PluginDependency +import net.mamoe.mirai.console.plugins.PluginDescription +import net.mamoe.mirai.console.plugins.PluginKind +import java.io.File + +@Serializable +class JvmPluginDescription internal constructor( + override val kind: PluginKind = PluginKind.NORMAL, + override val name: String, + @SerialName("main") + val mainClassName: String, + override val author: String = "", + override val version: String, + override val info: String = "", + @SerialName("depends") + override val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> = listOf() +) : PluginDescription, FilePluginDescription { + + /** + * 在手动实现时使用这个构造器. + */ + @Suppress("unused") + constructor( + kind: PluginKind, name: String, mainClassName: String, author: String, + version: String, info: String, depends: List, + file: File + ) : this(kind, name, mainClassName, author, version, info, depends) { + this._file = file + } + + override val file: File + get() = _file ?: error("Internal error: JvmPluginDescription(name=$name)._file == null") + + + @Suppress("PropertyName") + @Transient + @JvmField + internal var _file: File? = null + + override fun toString(): String { + return "JvmPluginDescription(kind=$kind, name='$name', mainClassName='$mainClassName', author='$author', version='$version', info='$info', dependencies=$dependencies, _file=$_file)" + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/description.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/description.kt new file mode 100644 index 000000000..4d23c90e4 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/description.kt @@ -0,0 +1,125 @@ +/* + * 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.plugins + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.setting.internal.map +import net.mamoe.yamlkt.Yaml +import net.mamoe.yamlkt.YamlDynamicSerializer +import java.io.File + + +/** 插件类型 */ +@Serializable(with = PluginKind.AsStringSerializer::class) +enum class PluginKind { + /** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */ + LOADER, + + /** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */ + NORMAL; + + object AsStringSerializer : KSerializer by String.serializer().map( + serializer = { it.name }, + deserializer = { str -> + values().firstOrNull { + it.name.equals(str, ignoreCase = true) + } ?: NORMAL + } + ) +} + +/** + * 插件描述 + */ +interface PluginDescription { + val kind: PluginKind + + val name: String + val author: String + val version: String + val info: String + + /** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */ + val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> +} + +/** 插件的一个依赖的信息 */ +@Serializable +data class PluginDependency( + /** 依赖插件名 */ + val name: String, + /** + * 依赖版本号. 为 null 时则为不限制版本. + * @see versionKind 版本号类型 + */ + val version: String? = null, + /** 版本号类型 */ + val versionKind: VersionKind = VersionKind.AT_LEAST, + /** + * 若为 `false`, 插件在找不到此依赖时也能正常加载. + */ + val isOptional: Boolean = false +) { + /** 版本号类型 */ + @Serializable(with = VersionKind.AsStringSerializer::class) + enum class VersionKind( + private vararg val serialNames: String + ) { + /** 要求依赖精确的版本 */ + EXACT("exact"), + + /** 要求依赖最低版本 */ + AT_LEAST("at_least", "AtLeast", "least", "lowest", "+"), + + /** 要求依赖最高版本 */ + AT_MOST("at_most", "AtMost", "most", "highest", "-"); + + object AsStringSerializer : KSerializer by String.serializer().map( + serializer = { it.serialNames.first() }, + deserializer = { str -> + values().firstOrNull { + it.serialNames.any { name -> name.equals(str, ignoreCase = true) } + } ?: AT_LEAST + } + ) + } + + override fun toString(): String { + return "$name ${versionKind.toEnglishString()}v$version" + } + + + /** + * 可支持解析 [String] 作为 [PluginDependency.version] 或单个 [PluginDependency] + */ + object SmartSerializer : KSerializer by YamlDynamicSerializer.map( + serializer = { it }, + deserializer = { any -> + when (any) { + is Map<*, *> -> Yaml.nonStrict.parse(serializer(), Yaml.nonStrict.stringify(any)) + else -> PluginDependency(any.toString()) + } + } + ) +} + +/** + * 基于文件的插件的描述 + */ +interface FilePluginDescription : PluginDescription { + val file: File +} + +internal fun PluginDependency.VersionKind.toEnglishString(): String = when (this) { + PluginDependency.VersionKind.EXACT -> "" + PluginDependency.VersionKind.AT_LEAST -> "at least " + PluginDependency.VersionKind.AT_MOST -> "at most " +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/scheduler/SchedulerTask.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/scheduler/SchedulerTask.kt deleted file mode 100644 index c6a1e996f..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/scheduler/SchedulerTask.kt +++ /dev/null @@ -1,149 +0,0 @@ -package net.mamoe.mirai.console.scheduler - -import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import net.mamoe.mirai.console.plugins.PluginBase -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException -import java.util.function.Supplier -import kotlin.coroutines.CoroutineContext - - -/** - * 作为Java插件开发者, 你应该使用PluginScheduler - * 他们使用kotlin更高效的协程实现,并在API上对java有很高的亲和度 - * 且可以保证在PluginBase关闭的时候结束所有任务 - * - * 你应该使用SchedulerTaskManager获取PluginScheduler, 或直接通过PluginBase获取 - */ - -class PluginScheduler(_coroutineContext: CoroutineContext) : CoroutineScope { - override val coroutineContext: CoroutineContext = _coroutineContext + SupervisorJob(_coroutineContext[Job]) - - - class RepeatTaskReceipt(@Volatile var cancelled: Boolean = false) - - /** - * 新增一个 Repeat Task (定时任务) - * - * 这个 Runnable 会被每 [intervalMs] 调用一次(不包含 [runnable] 执行时间) - * - * 使用返回的 [RepeatTaskReceipt], 可以取消这个定时任务 - */ - fun repeat(runnable: Runnable, intervalMs: Long): RepeatTaskReceipt { - val receipt = RepeatTaskReceipt() - - this.launch { - while (isActive && (!receipt.cancelled)) { - withContext(Dispatchers.IO) { - runnable.run() - } - delay(intervalMs) - } - } - - return receipt - } - - /** - * 新增一个 Delay Task (延迟任务) - * - * 在延迟 [delayMs] 后执行 [runnable] - * - * 作为 Java 使用者, 你要注意可见性, 原子性 - */ - fun delay(runnable: Runnable, delayMs: Long) { - this.launch { - delay(delayMs) - withContext(Dispatchers.IO) { - runnable.run() - } - } - } - - /** - * 异步执行一个任务, 最终返回 [Future], 与 Java 使用方法无异, 但效率更高且可以在插件关闭时停止 - */ - fun async(supplier: Supplier): Future { - return AsyncResult( - this.async { - withContext(Dispatchers.IO) { - supplier.get() - } - } - ) - } - - /** - * 异步执行一个任务, 没有返回 - */ - fun async(runnable: Runnable) { - this.launch { - withContext(Dispatchers.IO) { - runnable.run() - } - } - } - -} - - -/** - * 这个类作为 Java 与 Kotlin 的桥接 - * 用 Java 的 interface 进行了 Kotlin 的实现 - * 使得 Java 开发者可以使用 Kotlin 的协程 [CoroutineScope.async] - * 具体使用方法与 Java 的 [Future] 没有区别 - */ -class AsyncResult( - private val deferred: Deferred -) : Future { - - override fun isDone(): Boolean { - return deferred.isCompleted - } - - override fun get(): T { - return runBlocking { - deferred.await() - } - } - - @OptIn(ExperimentalCoroutinesApi::class) - override fun get(p0: Long, p1: TimeUnit): T { - return runBlocking { - withTimeoutOrNull(p1.toMillis(p0)) { - deferred.await() - } ?: throw TimeoutException() - } - } - - override fun cancel(p0: Boolean): Boolean { - deferred.cancel() - return true - } - - override fun isCancelled(): Boolean { - return deferred.isCancelled - } -} - - -internal object SchedulerTaskManagerInstance { - private val schedulerTaskManagerInstance = mutableMapOf() - - private val mutex = Mutex() - - fun getPluginScheduler(pluginBase: PluginBase): PluginScheduler { - runBlocking { - mutex.withLock { - if (!schedulerTaskManagerInstance.containsKey(pluginBase)) { - schedulerTaskManagerInstance[pluginBase] = PluginScheduler(pluginBase.coroutineContext) - } - } - } - return schedulerTaskManagerInstance[pluginBase]!! - } -} - 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 ed4373690..0c42bc83d 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,23 +12,40 @@ package net.mamoe.mirai.console.setting import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.setting.internal.SettingImpl +import net.mamoe.mirai.utils.MiraiExperimentalAPI import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlin.reflect.full.findAnnotation +/** + * 在配置文件和图像界面中保存的名称. + */ typealias SerialName = kotlinx.serialization.SerialName +/** + * 在配置文件和图像界面中显示的说明. + */ +typealias Comment = net.mamoe.yamlkt.Comment + /** * 配置的基类. 所有配置必须拥有一个无参构造器, 以用于在 [MutableList] 与 [MutableMap] 中动态识别类型 */ @Suppress("EXPOSED_SUPER_CLASS") -abstract class Setting : AbstractSetting() { +abstract class Setting : SettingImpl() { + /** + * 这个配置的名称, 仅对于顶层配置有效. + */ + @MiraiExperimentalAPI open val serialName: String get() = this::class.findAnnotation()?.value ?: this::class.qualifiedName ?: error("Names should be assigned to anonymous classes manually by overriding serialName") + /** + * 提供属性委托, 并添加这个对象的自动保存跟踪. + */ @JvmSynthetic operator fun Value.provideDelegate( thisRef: Setting, @@ -39,9 +56,12 @@ abstract class Setting : AbstractSetting() { return this } - override fun toString(): String = yaml.stringify(this.serializer, this) + override fun toString(): String = yamlForToString.stringify(this.serializer, this) } +/** + * 用于更新或保存这个 [Value] 的序列化器. + */ @Suppress("UNCHECKED_CAST") val T.serializer: KSerializer get() = kotlinSerializer as KSerializer 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 deleted file mode 100644 index 989ece08b..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 - -object ValueSerializerMark - -/* - * More generic ones - */ 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 2f4b80a48..1fd951369 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 @@ -7,9 +7,14 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused") package net.mamoe.mirai.console.setting +import net.mamoe.mirai.console.setting.internal.valueImpl +import kotlin.internal.LowPriorityInOverloadResolution + + /** * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.SettingValueUseSiteCodegen.kt * !!! DO NOT MODIFY THIS FILE MANUALLY @@ -136,3 +141,23 @@ inline fun Setting.value(default: Set): SettingSetValue @JvmName("valueMutable") inline fun Setting.value(default: MutableSet): MutableSettingSetValue = valueImpl(default) +/** + * 创建一个只引用对象而不跟踪其属性的值. + * + * @param T 类型. 必须拥有 [kotlinx.serialization.Serializable] 注解 (因此编译器会自动生成序列化器) + */ +@DangerousReferenceOnlyValue +@JvmName("valueDynamic") +@LowPriorityInOverloadResolution +inline fun Setting.value(default: T): Value = valueImpl(default) + +@RequiresOptIn( + """ + 这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎. + 对值的改变不会触发自动保存, 也不会同步到 UI 中. 在 UI 中只能编辑序列化之后的值. +""", level = RequiresOptIn.Level.WARNING +) +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION) +annotation class DangerousReferenceOnlyValue + 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 1aa6600b5..6dc6f62be 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 @@ -14,7 +14,7 @@ import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** - * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt + * !!! This file is auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt * !!! for better performance * !!! DO NOT MODIFY THIS FILE MANUALLY */ @@ -62,28 +62,36 @@ sealed class PrimitiveArrayValue : ArrayValue() abstract class IntArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class ShortArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class ByteArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class LongArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class FloatArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class DoubleArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class BooleanArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + abstract class CharArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { override fun iterator(): Iterator = this.value.iterator() } + sealed class TypedPrimitiveArrayValue : ArrayValue>(), Iterable { override fun iterator() = this.value.iterator() } @@ -98,10 +106,7 @@ abstract class TypedBooleanArrayValue internal constructor() : TypedPrimitiveArr abstract class TypedCharArrayValue internal constructor() : TypedPrimitiveArrayValue() abstract class TypedStringArrayValue internal constructor() : TypedPrimitiveArrayValue() -sealed class ListValue : Value>(), Iterable { - override fun iterator() = this.value.iterator() -} - +sealed class ListValue : Value>(), List abstract class IntListValue internal constructor() : ListValue() abstract class ShortListValue internal constructor() : ListValue() abstract class ByteListValue internal constructor() : ListValue() @@ -114,10 +119,7 @@ abstract class StringListValue internal constructor() : ListValue() abstract class SettingListValue internal constructor() : Value>(), List -sealed class SetValue : Value>(), Iterable { - override fun iterator() = this.value.iterator() -} - +sealed class SetValue : Value>(), Set abstract class IntSetValue internal constructor() : SetValue() abstract class ShortSetValue internal constructor() : SetValue() abstract class ByteSetValue internal constructor() : SetValue() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt similarity index 86% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt index 17ce4184b..d43cfc582 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt @@ -7,14 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +package net.mamoe.mirai.console.setting.internal -package net.mamoe.mirai.console.setting - -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.* /** @@ -414,8 +411,9 @@ internal fun Setting.valueImpl(default: Array): TypedStringArrayValue { } internal fun Setting.valueImpl(default: List): IntListValue { - return object : IntListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : IntListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -429,8 +427,9 @@ internal fun Setting.valueImpl(default: List): IntListValue { } internal fun Setting.valueImpl(default: List): ShortListValue { - return object : ShortListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : ShortListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -444,8 +443,9 @@ internal fun Setting.valueImpl(default: List): ShortListValue { } internal fun Setting.valueImpl(default: List): ByteListValue { - return object : ByteListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : ByteListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -459,8 +459,9 @@ internal fun Setting.valueImpl(default: List): ByteListValue { } internal fun Setting.valueImpl(default: List): LongListValue { - return object : LongListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : LongListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -474,8 +475,9 @@ internal fun Setting.valueImpl(default: List): LongListValue { } internal fun Setting.valueImpl(default: List): FloatListValue { - return object : FloatListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : FloatListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -489,8 +491,9 @@ internal fun Setting.valueImpl(default: List): FloatListValue { } internal fun Setting.valueImpl(default: List): DoubleListValue { - return object : DoubleListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : DoubleListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -504,8 +507,9 @@ internal fun Setting.valueImpl(default: List): DoubleListValue { } internal fun Setting.valueImpl(default: List): BooleanListValue { - return object : BooleanListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : BooleanListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -519,8 +523,9 @@ internal fun Setting.valueImpl(default: List): BooleanListValue { } internal fun Setting.valueImpl(default: List): CharListValue { - return object : CharListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : CharListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -534,8 +539,9 @@ internal fun Setting.valueImpl(default: List): CharListValue { } internal fun Setting.valueImpl(default: List): StringListValue { - return object : StringListValue() { - private var internalValue: List = default + var internalValue: List = default + val delegt = dynamicList { internalValue } + return object : StringListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -549,8 +555,9 @@ internal fun Setting.valueImpl(default: List): StringListValue { } internal fun Setting.valueImpl(default: Set): IntSetValue { - return object : IntSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : IntSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -564,8 +571,9 @@ internal fun Setting.valueImpl(default: Set): IntSetValue { } internal fun Setting.valueImpl(default: Set): ShortSetValue { - return object : ShortSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : ShortSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -579,8 +587,9 @@ internal fun Setting.valueImpl(default: Set): ShortSetValue { } internal fun Setting.valueImpl(default: Set): ByteSetValue { - return object : ByteSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : ByteSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -594,8 +603,9 @@ internal fun Setting.valueImpl(default: Set): ByteSetValue { } internal fun Setting.valueImpl(default: Set): LongSetValue { - return object : LongSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : LongSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -609,8 +619,9 @@ internal fun Setting.valueImpl(default: Set): LongSetValue { } internal fun Setting.valueImpl(default: Set): FloatSetValue { - return object : FloatSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : FloatSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -624,8 +635,9 @@ internal fun Setting.valueImpl(default: Set): FloatSetValue { } internal fun Setting.valueImpl(default: Set): DoubleSetValue { - return object : DoubleSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : DoubleSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -639,8 +651,9 @@ internal fun Setting.valueImpl(default: Set): DoubleSetValue { } internal fun Setting.valueImpl(default: Set): BooleanSetValue { - return object : BooleanSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : BooleanSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -654,8 +667,9 @@ internal fun Setting.valueImpl(default: Set): BooleanSetValue { } internal fun Setting.valueImpl(default: Set): CharSetValue { - return object : CharSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : CharSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -669,8 +683,9 @@ internal fun Setting.valueImpl(default: Set): CharSetValue { } internal fun Setting.valueImpl(default: Set): StringSetValue { - return object : StringSetValue() { - private var internalValue: Set = default + var internalValue: Set = default + val delegt = dynamicSet { internalValue } + return object : StringSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -690,7 +705,8 @@ internal fun Setting.valueImpl( ): MutableIntListValue { var internalValue: MutableList = default - return object : MutableIntListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableIntListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -699,16 +715,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -725,7 +741,8 @@ internal fun Setting.valueImpl( ): MutableShortListValue { var internalValue: MutableList = default - return object : MutableShortListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableShortListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -734,16 +751,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -760,7 +777,8 @@ internal fun Setting.valueImpl( ): MutableByteListValue { var internalValue: MutableList = default - return object : MutableByteListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableByteListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -769,16 +787,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -795,7 +813,8 @@ internal fun Setting.valueImpl( ): MutableLongListValue { var internalValue: MutableList = default - return object : MutableLongListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableLongListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -804,16 +823,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -830,7 +849,8 @@ internal fun Setting.valueImpl( ): MutableFloatListValue { var internalValue: MutableList = default - return object : MutableFloatListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableFloatListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -839,16 +859,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -865,7 +885,8 @@ internal fun Setting.valueImpl( ): MutableDoubleListValue { var internalValue: MutableList = default - return object : MutableDoubleListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableDoubleListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -874,16 +895,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -900,7 +921,8 @@ internal fun Setting.valueImpl( ): MutableBooleanListValue { var internalValue: MutableList = default - return object : MutableBooleanListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableBooleanListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -909,16 +931,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -935,7 +957,8 @@ internal fun Setting.valueImpl( ): MutableCharListValue { var internalValue: MutableList = default - return object : MutableCharListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableCharListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -944,16 +967,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -970,7 +993,8 @@ internal fun Setting.valueImpl( ): MutableStringListValue { var internalValue: MutableList = default - return object : MutableStringListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList{ internalValue } + return object : MutableStringListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -979,16 +1003,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableList().observable { + onElementChanged(outerThis) } } @@ -1005,7 +1029,8 @@ internal fun Setting.valueImpl( ): MutableIntSetValue { var internalValue: MutableSet = default - return object : MutableIntSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableIntSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1014,16 +1039,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1040,7 +1065,8 @@ internal fun Setting.valueImpl( ): MutableShortSetValue { var internalValue: MutableSet = default - return object : MutableShortSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableShortSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1049,16 +1075,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1075,7 +1101,8 @@ internal fun Setting.valueImpl( ): MutableByteSetValue { var internalValue: MutableSet = default - return object : MutableByteSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableByteSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1084,16 +1111,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1110,7 +1137,8 @@ internal fun Setting.valueImpl( ): MutableLongSetValue { var internalValue: MutableSet = default - return object : MutableLongSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableLongSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1119,16 +1147,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1145,7 +1173,8 @@ internal fun Setting.valueImpl( ): MutableFloatSetValue { var internalValue: MutableSet = default - return object : MutableFloatSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableFloatSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1154,16 +1183,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1180,7 +1209,8 @@ internal fun Setting.valueImpl( ): MutableDoubleSetValue { var internalValue: MutableSet = default - return object : MutableDoubleSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableDoubleSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1189,16 +1219,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1215,7 +1245,8 @@ internal fun Setting.valueImpl( ): MutableBooleanSetValue { var internalValue: MutableSet = default - return object : MutableBooleanSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableBooleanSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1224,16 +1255,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1250,7 +1281,8 @@ internal fun Setting.valueImpl( ): MutableCharSetValue { var internalValue: MutableSet = default - return object : MutableCharSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableCharSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1259,16 +1291,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1285,7 +1317,8 @@ internal fun Setting.valueImpl( ): MutableStringSetValue { var internalValue: MutableSet = default - return object : MutableStringSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet{ internalValue } + return object : MutableStringSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -1294,16 +1327,16 @@ internal fun Setting.valueImpl( onElementChanged(this) } } - - private inline val `this` get() = 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 { - onElementChanged(`this`) + return delegate.deserialize(decoder).toMutableSet().observable { + onElementChanged(outerThis) } } @@ -1326,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/setting/utils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt similarity index 99% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/utils.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt index 0f069db89..c686d2fdb 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/utils.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.setting +package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.serializer diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internal.kt similarity index 56% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Internal.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internal.kt index 46a7b73ef..ee3e4418b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internal.kt @@ -7,15 +7,21 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.setting +package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.* +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.setting.SerialName +import net.mamoe.mirai.console.setting.Setting +import net.mamoe.mirai.console.setting.Value +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.YamlConfiguration import kotlin.reflect.KProperty import kotlin.reflect.full.findAnnotation -internal abstract class AbstractSetting { +internal abstract class SettingImpl { @JvmField internal var valueList: MutableList, KProperty<*>>> = mutableListOf() @@ -31,15 +37,18 @@ internal abstract class AbstractSetting { internal val kotlinSerializer: KSerializer by lazy { object : KSerializer { override val descriptor: SerialDescriptor - get() = this@AbstractSetting.updaterSerializer.descriptor + get() = this@SettingImpl.updaterSerializer.descriptor override fun deserialize(decoder: Decoder): Setting { - this@AbstractSetting.updaterSerializer.deserialize(decoder) - return this@AbstractSetting as Setting + this@SettingImpl.updaterSerializer.deserialize(decoder) + return this@SettingImpl as Setting } override fun serialize(encoder: Encoder, value: Setting) { - this@AbstractSetting.updaterSerializer.serialize(encoder, SettingSerializerMark) + this@SettingImpl.updaterSerializer.serialize( + encoder, + SettingSerializerMark + ) } } } @@ -50,7 +59,7 @@ internal abstract class AbstractSetting { companion object { @JvmStatic - internal val yaml = + internal val yamlForToString = Yaml( configuration = YamlConfiguration( nonStrictNullability = true, @@ -67,6 +76,7 @@ internal class SettingUpdaterSerializer( private val instance: Setting ) : KSerializer { override val descriptor: SerialDescriptor by lazy { + @OptIn(MiraiExperimentalAPI::class) SerialDescriptor(instance.serialName) { for ((value, property) in instance.valueList) { element(property.serialNameOrPropertyName, value.serializer.descriptor, annotations, true) @@ -89,9 +99,8 @@ internal class SettingUpdaterSerializer( while (true) { val index = this.decodeElementIndex(descriptor) if (index == CompositeDecoder.READ_DONE) return@decodeStructure SettingSerializerMark - val value = instance.valueList[index].first - - this.decodeSerializableElement( + val value = instance.valueList[index].first as Value + value.value = this.decodeSerializableElement( descriptor, index, value.serializer @@ -101,20 +110,43 @@ internal class SettingUpdaterSerializer( SettingSerializerMark } - override fun serialize(encoder: Encoder, value: SettingSerializerMark) = encoder.encodeStructure(descriptor) { - instance.valueList.forEachIndexed { index, (value, _) -> - @Suppress("UNCHECKED_CAST") // erased, no problem. - this.encodeSerializableElement( - descriptor, - index, - value.serializer as KSerializer, - value.value - ) + private val emptyList = emptyList() + private val emptyListSerializer = ListSerializer(String.serializer()) + + override fun serialize(encoder: Encoder, value: SettingSerializerMark) { + if (instance.valueList.isEmpty()) { + emptyListSerializer.serialize(encoder, emptyList) + } else encoder.encodeStructure(descriptor) { + instance.valueList.forEachIndexed { index, (value, _) -> + @Suppress("UNCHECKED_CAST") // erased, no problem. + this.encodeElementSmart(descriptor, index, value) + } } } } +// until https://github.com/Him188/yamlkt/issues/2 fixed +internal fun CompositeEncoder.encodeElementSmart( + descriptor: SerialDescriptor, + index: Int, + value: Value +) { + when (value.value::class) { + String::class -> this.encodeStringElement(descriptor, index, value.value as String) + Int::class -> this.encodeIntElement(descriptor, index, value.value as Int) + Byte::class -> this.encodeByteElement(descriptor, index, value.value as Byte) + Char::class -> this.encodeCharElement(descriptor, index, value.value as Char) + Long::class -> this.encodeLongElement(descriptor, index, value.value as Long) + Float::class -> this.encodeFloatElement(descriptor, index, value.value as Float) + Double::class -> this.encodeDoubleElement(descriptor, index, value.value as Double) + Boolean::class -> this.encodeBooleanElement(descriptor, index, value.value as Boolean) + else -> + @Suppress("UNCHECKED_CAST") + this.encodeSerializableElement(descriptor, index, value.serializer as KSerializer, value.value) + } +} + internal object SettingSerializerMark internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation()?.value ?: this.name @@ -132,3 +164,14 @@ internal inline fun KSerializer.bind( this@bind.serialize(encoder, getter()) } } + +internal inline fun KSerializer.map( + crossinline serializer: (R) -> E, + crossinline deserializer: (E) -> R +): KSerializer { + return object : KSerializer { + override val descriptor: SerialDescriptor get() = this@map.descriptor + override fun deserialize(decoder: Decoder): R = this@map.deserialize(decoder).let(deserializer) + override fun serialize(encoder: Encoder, value: R) = this@map.serialize(encoder, value.let(serializer)) + } +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/ValueImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internalValueImpl.kt similarity index 88% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/ValueImpl.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internalValueImpl.kt index 0331ad96e..0942f9fdd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/ValueImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internalValueImpl.kt @@ -6,13 +6,16 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") -package net.mamoe.mirai.console.setting +package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.* import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.SetSerializer +import net.mamoe.mirai.console.setting.* import net.mamoe.yamlkt.YamlDynamicSerializer +import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.createInstance @@ -53,7 +56,10 @@ internal fun Setting.valueImpl( internalValue.shadowMap(transform = { it.value }, transformBack = { valueMapper(it) }) var shadowed: MutableList = updateShadow() - return object : MutableListValue(), MutableList by dynamicMutableList({ shadowed }) { + + + val delegt = dynamicMutableList { shadowed } + return object : MutableListValue(), MutableList by delegt { override var value: MutableList> get() = internalValue set(new) { @@ -86,7 +92,8 @@ internal fun Setting.valueImpl( ): MutableSettingListValue { var internalValue: MutableList = default - return object : MutableSettingListValue(), MutableList by dynamicMutableList({ internalValue }) { + val delegt = dynamicMutableList { internalValue } + return object : MutableSettingListValue(), MutableList by delegt { override var value: MutableList get() = internalValue set(new) { @@ -118,7 +125,8 @@ internal fun Setting.valueImpl( ): SettingListValue { var internalValue: List = default - return object : SettingListValue(), List by dynamicList({ internalValue }) { + val delegt = dynamicList { internalValue } + return object : SettingListValue(), List by delegt { override var value: List get() = internalValue set(new) { @@ -150,7 +158,8 @@ internal fun Setting.valueImpl( ): SettingSetValue { var internalValue: Set = default - return object : SettingSetValue(), Set by dynamicSet({ internalValue }) { + val delegt = dynamicSet { internalValue } + return object : SettingSetValue(), Set by delegt { override var value: Set get() = internalValue set(new) { @@ -182,7 +191,8 @@ internal fun Setting.valueImpl( ): MutableSettingSetValue { var internalValue: MutableSet = default - return object : MutableSettingSetValue(), MutableSet by dynamicMutableSet({ internalValue }) { + val delegt = dynamicMutableSet { internalValue } + return object : MutableSettingSetValue(), MutableSet by delegt { override var value: MutableSet get() = internalValue set(new) { @@ -247,7 +257,9 @@ internal fun Setting.valueImpl( internalValue.shadowMap(transform = { it.value }, transformBack = { valueMapper(it) }) var shadowed: MutableSet = updateShadow() - return object : MutableSetValue(), MutableSet by dynamicMutableSet({ shadowed }) { + + val delegt = dynamicMutableSet { shadowed } + return object : MutableSetValue(), MutableSet by delegt { override var value: MutableSet> get() = internalValue set(new) { @@ -280,6 +292,7 @@ internal fun Setting.valueImpl( * For primitives and serializable only */ @PublishedApi +@LowPriorityInOverloadResolution internal inline fun Setting.valueImpl(default: T): Value = valueImpl(default, T::class) @@ -293,20 +306,15 @@ internal fun Setting.valueImpl(default: T, clazz: KClass): Valu } return object : DynamicReferenceValue() { override var value: T = default - override val serializer: KSerializer - get() = object : KSerializer { - override val descriptor: SerialDescriptor - get() = YamlDynamicSerializer.descriptor + override val serializer: KSerializer = object : KSerializer { + override val descriptor: SerialDescriptor + get() = YamlDynamicSerializer.descriptor - override fun deserialize(decoder: Decoder): T { - return YamlDynamicSerializer.deserialize(decoder).smartCastPrimitive(clazz) - } + override fun deserialize(decoder: Decoder): T = + YamlDynamicSerializer.deserialize(decoder).smartCastPrimitive(clazz) - @OptIn(ImplicitReflectionSerializer::class) - override fun serialize(encoder: Encoder, value: T) { - YamlDynamicSerializer.serialize(encoder, value) - } - } + override fun serialize(encoder: Encoder, value: T) = YamlDynamicSerializer.serialize(encoder, value) + } } } 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/ConsoleInput.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ConsoleInput.kt index 8295c7012..75f66a8b0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ConsoleInput.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ConsoleInput.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.console.utils import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.plugins.PluginBase import java.util.concurrent.Executors @Suppress("unused") @@ -25,30 +24,28 @@ object ConsoleInput { * 如弹出框,或一行字 */ suspend fun requestInput( - hint:String - ):String{ + hint: String + ): String { return withContext(inputDispatcher) { MiraiConsole.frontEnd.requestInput(hint) } } - fun requestInputBlocking(hint:String):String = runBlocking { requestInput(hint) } + fun requestInputBlocking(hint: String): String = runBlocking { requestInput(hint) } /** * asnyc获取 */ fun requestInputAsync( - pluginBase: PluginBase, + scope: CoroutineScope, hint: String - ):Deferred{ - return pluginBase.async { + ): Deferred { + return scope.async { requestInput(hint) } } - suspend fun MiraiConsole.requestInput(hint:String):String = requestInput(hint) - - suspend fun PluginBase.requestInputAsync(hint:String):Deferred = requestInputAsync(hint) + suspend fun MiraiConsole.requestInput(hint: String): String = requestInput(hint) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaPluginScheduler.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaPluginScheduler.kt new file mode 100644 index 000000000..ab334872c --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/JavaPluginScheduler.kt @@ -0,0 +1,95 @@ +/* + * 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.utils + +import kotlinx.coroutines.* +import kotlinx.coroutines.future.future +import net.mamoe.mirai.console.plugins.builtin.JavaPlugin +import java.util.concurrent.Callable +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Future +import kotlin.coroutines.CoroutineContext + + +/** + * 拥有生命周期管理的 Java 线程池. + * + * 在插件被 [卸载][JavaPlugin.onDisable] 时将会自动停止. + * + * @see JavaPlugin.scheduler 获取实例 + */ +class JavaPluginScheduler internal constructor(parentCoroutineContext: CoroutineContext) : CoroutineScope { + override val coroutineContext: CoroutineContext = + parentCoroutineContext + SupervisorJob(parentCoroutineContext[Job]) + + /** + * 新增一个 Repeating Task (定时任务) + * + * 这个 Runnable 会被每 [intervalMs] 调用一次(不包含 [runnable] 执行时间) + * + * @see Future.cancel 取消这个任务 + */ + fun repeating(intervalMs: Long, runnable: Runnable): Future { + return this.future { + while (isActive) { + withContext(Dispatchers.IO) { runnable.run() } + delay(intervalMs) + } + null + } + } + + /** + * 新增一个 Delayed Task (延迟任务) + * + * 在延迟 [delayMillis] 后执行 [runnable] + */ + fun delayed(delayMillis: Long, runnable: Runnable): CompletableFuture { + return future { + delay(delayMillis) + withContext(Dispatchers.IO) { + runnable.run() + } + null + } + } + + /** + * 新增一个 Delayed Task (延迟任务) + * + * 在延迟 [delayMillis] 后执行 [runnable] + */ + fun delayed(delayMillis: Long, runnable: Callable): CompletableFuture { + return future { + delay(delayMillis) + withContext(Dispatchers.IO) { runnable.call() } + null + } + } + + /** + * 异步执行一个任务, 最终返回 [Future], 与 Java 使用方法无异, 但效率更高且可以在插件关闭时停止 + */ + fun async(supplier: Callable): Future { + return future { + withContext(Dispatchers.IO) { supplier.call() } + } + } + + /** + * 异步执行一个任务, 没有返回 + */ + fun async(runnable: Runnable): Future { + return future { + withContext(Dispatchers.IO) { runnable.run() } + null + } + } +} \ No newline at end of file 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..2cdb0b423 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/retryCatching.kt @@ -0,0 +1,47 @@ +/* + * 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("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 { diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommands.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommands.kt index 5ff9aca1e..11350c771 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommands.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommands.kt @@ -12,13 +12,13 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.plugins.PluginBase +import net.mamoe.mirai.console.plugins.builtin.KotlinPlugin import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.Test import kotlin.test.assertEquals -val plugin: PluginBase = object : PluginBase() { +val plugin: KotlinPlugin = object : KotlinPlugin() { } diff --git a/buildSrc/src/main/kotlin/versions.kt b/buildSrc/src/main/kotlin/Versions.kt similarity index 55% rename from buildSrc/src/main/kotlin/versions.kt rename to buildSrc/src/main/kotlin/Versions.kt index 2e1176452..6647de79e 100644 --- a/buildSrc/src/main/kotlin/versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,6 +1,3 @@ -import org.gradle.api.artifacts.dsl.DependencyHandler -import org.gradle.kotlin.dsl.DependencyHandlerScope - /* * Copyright 2020 Mamoe Technologies and contributors. * @@ -12,7 +9,7 @@ import org.gradle.kotlin.dsl.DependencyHandlerScope object Versions { object Mirai { - const val core = "1.0-RC2-1" + const val core = "1.0.0" const val console = "0.5.1" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" @@ -21,20 +18,8 @@ object Versions { object Kotlin { const val stdlib = "1.3.72" - const val coroutines = "1.3.5" + const val coroutines = "1.3.7" const val serialization = "0.20.0" const val ktor = "1.3.2" } -} - -@Suppress("unused") -fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" - -@Suppress("unused") -fun DependencyHandlerScope.ktor(id: String, version: String = Versions.Kotlin.ktor) = "io.ktor:ktor-$id:$version" - -@Suppress("unused") -fun DependencyHandler.compileAndRuntime(any: Any) { - add("compileOnly", any) - add("runtimeOnly", any) -} +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/dependencyExtensions.kt b/buildSrc/src/main/kotlin/dependencyExtensions.kt new file mode 100644 index 000000000..03bee26e5 --- /dev/null +++ b/buildSrc/src/main/kotlin/dependencyExtensions.kt @@ -0,0 +1,14 @@ +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.kotlin.dsl.DependencyHandlerScope + +@Suppress("unused") +fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" + +@Suppress("unused") +fun DependencyHandlerScope.ktor(id: String, version: String = Versions.Kotlin.ktor) = "io.ktor:ktor-$id:$version" + +@Suppress("unused") +fun DependencyHandler.compileAndRuntime(any: Any) { + add("compileOnly", any) + add("runtimeOnly", any) +}