diff --git a/backend/mirai-console/README.md b/backend/mirai-console/README.md index 39a4523ae..bf7b9710b 100644 --- a/backend/mirai-console/README.md +++ b/backend/mirai-console/README.md @@ -116,7 +116,7 @@ dependencies: [`Plugin`]: src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt -[`PluginDescription`]: src/main/kotlin/net/mamoe/mirai/console/plugin/description.kt +[`PluginDescription`]: src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt [`PluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt [`PluginManager`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt [`JarPluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 5f275a7f8..a33be3dc8 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -60,8 +60,7 @@ kotlin { } dependencies { - compileAndRuntime("net.mamoe:mirai-core:${Versions.core}") - compileAndRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib)) + implementation("net.mamoe:mirai-core:${Versions.core}") implementation(kotlinx("serialization-core", Versions.serialization)) implementation(kotlin("reflect")) @@ -92,6 +91,8 @@ ext.apply { dependencyFilter.exclude { when ("${it.moduleGroup}:${it.moduleName}") { "net.mamoe:mirai-core" -> true + "org.jetbrains.kotlin:kotlin-stdlib" -> true + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" -> true "net.mamoe:mirai-core-qqandroid" -> true else -> false } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt index 7c9e71714..3a11cd3cc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt @@ -36,6 +36,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ + @ConsoleExperimentalAPI public override fun > T.track(valueName: String): T = apply { valueNodes.add(ValueNode(valueName, this, this.serializer)) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt new file mode 100644 index 000000000..e58397045 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt @@ -0,0 +1,27 @@ +/* + * 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.data + +import kotlinx.coroutines.Job + +/** + * 链接自动保存的 [PluginConfig]. + * + * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 + * + * 若 [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], 则 [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. + * + * @see PluginConfig + * @see AutoSavePluginData + */ +@ExperimentalPluginConfig +public open class AutoSavePluginConfig : AutoSavePluginData(), PluginConfig \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt new file mode 100644 index 000000000..383bd7164 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused", "PropertyName", "PrivatePropertyName") + +package net.mamoe.mirai.console.data + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.* +import net.mamoe.mirai.console.internal.plugin.updateWhen +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import net.mamoe.mirai.console.util.ConsoleInternalAPI +import net.mamoe.mirai.utils.currentTimeMillis + +/** + * 链接自动保存的 [PluginData]. + * + * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 + * + * 若 [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], 则 [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. + * + * @see PluginData + */ +public open class AutoSavePluginData private constructor( + @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any? +) : AbstractPluginData() { + private lateinit var owner_: AutoSavePluginDataHolder + private val autoSaveIntervalMillis_: LongRange get() = owner_.autoSaveIntervalMillis + private lateinit var storage_: PluginDataStorage + + public constructor() : this(null) + + + override fun onStored(owner: PluginDataHolder, storage: PluginDataStorage) { + check(owner is AutoSavePluginDataHolder) { "owner must be AutoSavePluginDataHolder for AutoSavePluginData" } + check(!this::storage_.isInitialized) { "storage is already initialized" } + this.storage_ = storage + this.owner_ = owner + + owner_.coroutineContext[Job]?.invokeOnCompletion { doSave() } + + if (shouldPerformAutoSaveWheneverChanged()) { + owner_.launch { + while (isActive) { + delay(autoSaveIntervalMillis_.last) // 定时自动保存一次, 用于 kts 序列化的对象 + doSave() + } + } + } + } + + @JvmField + @Volatile + internal var lastAutoSaveJob_: Job? = null + + @JvmField + internal val currentFirstStartTime_ = atomic(0L) + + /** + * @return `true` 时, 一段时间后, 即使无属性改变, 也会进行保存. + */ + @ConsoleExperimentalAPI + protected open fun shouldPerformAutoSaveWheneverChanged(): Boolean { + return true + } + + private val updaterBlock: suspend CoroutineScope.() -> Unit = { + if (::storage_.isInitialized) { + currentFirstStartTime_.updateWhen({ it == 0L }, { currentTimeMillis }) + + delay(autoSaveIntervalMillis_.first.coerceAtLeast(1000)) // for safety + + if (lastAutoSaveJob_ == this.coroutineContext[Job]) { + doSave() + } else { + if (currentFirstStartTime_.updateWhen( + { currentTimeMillis - it >= autoSaveIntervalMillis_.last }, + { 0 }) + ) doSave() + } + } + } + + @Suppress("RedundantVisibilityModifier") + @ConsoleInternalAPI + public final override fun onValueChanged(value: Value<*>) { + if (::owner_.isInitialized) { + lastAutoSaveJob_ = owner_.launch(block = updaterBlock) + } + } + + private fun doSave() = storage_.store(owner_, this) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt index e168cba5f..1699691c9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.data +import net.mamoe.mirai.console.data.java.JAutoSavePluginConfig import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import kotlin.annotation.AnnotationTarget.* @@ -27,14 +28,14 @@ import kotlin.annotation.AnnotationTarget.* * 在 [PluginData] 的示例基础上, 修改对象定义 * ``` * // 原 - * object MyPluginData : PluginData by PluginMain.loadPluginData() + * object MyPluginData : AutoSavePluginData() * // 修改为 - * object MyPluginConfig : PluginConfig by PluginMain.loadPluginConfig() + * object MyPluginConfig : AutoSavePluginConfig() * ``` * 即可将一个 [PluginData] 变更为 [PluginConfig]. * * ### Java - * 见 [JPluginConfig] + * 见 [JAutoSavePluginConfig] * * @see PluginData */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index d6747fcb5..b4e70316d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -18,15 +18,17 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.data.java.JAutoSavePluginData import net.mamoe.mirai.console.internal.data.* import net.mamoe.mirai.console.plugin.jvm.JvmPlugin -import net.mamoe.mirai.console.plugin.jvm.loadPluginData +import net.mamoe.mirai.console.plugin.jvm.reloadPluginData import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KType +import kotlin.reflect.full.findAnnotation /** * 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. @@ -47,7 +49,7 @@ import kotlin.reflect.KType * ``` * object PluginMain : KotlinPlugin() * - * object MyPluginData : PluginData by PluginMain.loadPluginData() { + * object MyPluginData : AutoSavePluginData() { * val list: MutableList by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略 * val custom: Map by value() // 使用 kotlinx-serialization 序列化的类型. (目前还不支持) * var long: Long by value(0) // 允许 var @@ -69,11 +71,9 @@ import kotlin.reflect.KType * * ### 使用 Java * - * 参考 [JPluginData] + * 参考 [JAutoSavePluginData] * - * **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象. - * - * @see JvmPlugin.loadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. + * @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. * @see PluginDataStorage [PluginData] 存储仓库 */ public interface PluginData { @@ -88,6 +88,18 @@ public interface PluginData { @ConsoleExperimentalAPI public val valueNodes: MutableList> + /** + * 这个 [PluginData] 保存时使用的名称. 默认通过 [ValueName] 获取, 否则使用 [类全名][KClass.qualifiedName] (即 [Class.getCanonicalName]) + */ + @ConsoleExperimentalAPI + public val saveName: String + get() { + val clazz = this::class + return clazz.findAnnotation()?.value + ?: clazz.qualifiedName + ?: throw IllegalArgumentException("Cannot find a serial name for ${this::class}") + } + /** * 由 [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点. */ @@ -123,6 +135,7 @@ public interface PluginData { /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ + @ConsoleExperimentalAPI public fun > T.track( /** * 值名称. @@ -153,7 +166,7 @@ public interface PluginData { * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 */ @ConsoleInternalAPI - public fun setStorage(storage: PluginDataStorage) + public fun onStored(owner: PluginDataHolder, storage: PluginDataStorage) } /** @@ -161,12 +174,14 @@ public interface PluginData { * * 如, 对于 * ``` - * object MyData : PluginData { + * object MyData : AutoSavePluginData(PluginMain) { * val list: List by value() * } * * val value: Value> = MyData.findBackingFieldValue(MyData::list) * ``` + * + * @see PluginData */ @Suppress("UNCHECKED_CAST") public fun PluginData.findBackingFieldValue(property: KProperty): Value? = @@ -177,7 +192,7 @@ public fun PluginData.findBackingFieldValue(property: KProperty): Value by value() * val int: Int by value() @@ -186,6 +201,8 @@ public fun PluginData.findBackingFieldValue(property: KProperty): Value> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称 * val intValue: Value = MyData.findBackingFieldValue("int") * ``` + * + * @see PluginData */ @Suppress("UNCHECKED_CAST") public fun PluginData.findBackingFieldValue(propertyValueName: String): Value? { @@ -198,12 +215,14 @@ public fun PluginData.findBackingFieldValue(propertyValueName: String): Valu * * 如, 对于 * ``` - * object MyData : PluginData { + * object MyData : AutoSavePluginData(PluginMain) { * val list: List by value() * } * * val value: PluginData.ValueNode> = MyData.findBackingFieldValueNode(MyData::list) * ``` + * + * @see PluginData */ @Suppress("UNCHECKED_CAST") public fun PluginData.findBackingFieldValueNode(property: KProperty): PluginData.ValueNode? { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt index a9e66d90e..0c9a05bb0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt @@ -13,8 +13,7 @@ package net.mamoe.mirai.console.data import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import net.mamoe.mirai.console.data.PluginDataStorage.Companion.load -import net.mamoe.mirai.console.internal.data.AutoSavePluginData +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI /** * 可以持有相关 [PluginData] 实例的对象, 作为 [PluginData] 实例的拥有者. @@ -24,6 +23,7 @@ import net.mamoe.mirai.console.internal.data.AutoSavePluginData * * @see AutoSavePluginDataHolder 支持自动保存 */ +@ConsoleExperimentalAPI public interface PluginDataHolder { /** * 保存时使用的分类名 @@ -36,6 +36,7 @@ public interface PluginDataHolder { * * @see net.mamoe.mirai.console.plugin.jvm.JvmPlugin */ +@ConsoleExperimentalAPI public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope { /** * [AutoSavePluginData] 每次自动保存时间间隔 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt index 773a915ca..de2343599 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt @@ -15,16 +15,15 @@ import net.mamoe.mirai.console.internal.data.MemoryPluginDataStorageImpl import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import java.io.File import java.nio.file.Path -import kotlin.reflect.KClass /** * [数据对象][PluginData] 存储仓库. * * ## 职责 * [PluginDataStorage] 类似于一个数据库, 它只承担将序列化之后的数据保存到数据库中, 和从数据库取出这个对象的任务. - * [PluginDataStorage] 不考虑一个 [] * * * 此为较低层的 API, 一般插件开发者不会接触. @@ -34,69 +33,77 @@ import kotlin.reflect.KClass * @see PluginDataHolder * @see JarPluginLoader.dataStorage */ +@ConsoleExperimentalAPI public interface PluginDataStorage { /** - * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] + * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.onStored] */ - public fun load(holder: PluginDataHolder, dataClass: Class): T + public fun load(holder: PluginDataHolder, instance: PluginData) /** * 保存一个实例. * * **实现细节**: 调用 [PluginData.updaterSerializer], 将 */ - public fun store(holder: PluginDataHolder, pluginData: PluginData) + public fun store(holder: PluginDataHolder, instance: PluginData) - public companion object { - /** - * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] - */ - @JvmStatic - public fun PluginDataStorage.load(holder: PluginDataHolder, dataClass: KClass): T = - this.load(holder, dataClass.java) + /* +public companion object { + /** + * 通过反射 + * 读取一个实例. + * + * 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] + */ + @ConsoleExperimentalAPI + @JvmStatic + public fun PluginDataStorage.load(holder: PluginDataHolder, dataClass: KClass): T { + @Suppress("UNCHECKED_CAST") + val instance = with(dataClass){ + objectInstance + ?: createInstanceOrNull() + ?: throw IllegalArgumentException( + "Cannot create PluginData instance. Make sure dataClass is PluginData::class.java or a Kotlin's object, " + + "or has a constructor which either has no parameters or all parameters of which are optional" + ) + } + + load(holder, instance) + return instance + } + + /** + * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] + */ + @JvmStatic + public fun PluginDataStorage.load(holder: PluginDataHolder, dataClass: Class): T = + this.load(holder, dataClass.java) + + /** + * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] + */ + @JvmSynthetic + public inline fun PluginDataStorage.load(holder: PluginDataHolder): T = + this.load(holder, T::class) - /** - * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] - */ - @JvmSynthetic - public inline fun PluginDataStorage.load(holder: PluginDataHolder): T = - this.load(holder, T::class) } + */ } /** * 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失. * @see PluginDataStorage */ -public interface MemoryPluginDataStorage : PluginDataStorage, Map, PluginData> { - /** - * 当任一 [PluginData] 实例拥有的 [Value] 的值被改变后调用的回调函数. - */ - public fun interface OnChangedCallback { - public fun onChanged(storage: MemoryPluginDataStorage, value: Value<*>) - - /** - * 无任何操作的 [OnChangedCallback] - * @see OnChangedCallback - */ - public object NoOp : OnChangedCallback { - public override fun onChanged(storage: MemoryPluginDataStorage, value: Value<*>) { - // no-op - } - } - } - +@ConsoleExperimentalAPI +public interface MemoryPluginDataStorage : PluginDataStorage { public companion object { /** * 创建一个 [MemoryPluginDataStorage] 实例. - * - * @param onChanged 当任一 [PluginData] 实例拥有的 [Value] 的值被改变后调用的回调函数. */ @JvmStatic @JvmName("create") // @JvmOverloads - public operator fun invoke(onChanged: OnChangedCallback = OnChangedCallback.NoOp): MemoryPluginDataStorage = - MemoryPluginDataStorageImpl(onChanged) + public operator fun invoke(): MemoryPluginDataStorage = MemoryPluginDataStorageImpl() } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/SerialName.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt similarity index 100% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/SerialName.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt similarity index 57% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginConfig.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt index fcb62fd21..3e1483b66 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt @@ -7,7 +7,14 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.data +@file:Suppress("unused", "EXPOSED_SUPER_CLASS") + +package net.mamoe.mirai.console.data.java + +import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.ExperimentalPluginConfig +import net.mamoe.mirai.console.data.PluginConfig +import net.mamoe.mirai.console.data.PluginData /** * 一个插件的配置数据, 用于和用户交互. @@ -19,16 +26,17 @@ package net.mamoe.mirai.console.data * * ### 实现 * - * 在 [JPluginData] 的示例基础上, 修改类定义 + * 在 [JAutoSavePluginData] 的示例基础上, 修改类定义 * ```java * // 原 - * public class AccountPluginData extends JPluginData { + * public class AccountPluginData extends JAutoSavePluginData { * // 修改为 - * public class AccountPluginConfig extends JPluginConfig { + * public class AccountPluginConfig extends JAutoSavePluginConfig { * ``` * 即可将一个 [PluginData] 变更为 [PluginConfig]. * - * @see JPluginData + * @see JAutoSavePluginData * @see PluginConfig */ -public abstract class JPluginConfig(delegate: PluginData) : JPluginData(delegate), PluginConfig \ No newline at end of file +@ExperimentalPluginConfig +public abstract class JAutoSavePluginConfig : AutoSavePluginConfig(), PluginConfig diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt similarity index 84% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginData.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt index 15b492460..12333c2bd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt @@ -7,10 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("unused") +@file:Suppress("unused", "EXPOSED_SUPER_CLASS") -package net.mamoe.mirai.console.data +package net.mamoe.mirai.console.data.java +import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.setValueBySerializer import net.mamoe.mirai.console.internal.data.valueImpl @@ -27,18 +28,19 @@ import kotlin.reflect.full.createType * ``` * // PluginMain.java * public final class PluginMain extends JavaPlugin { - * public static PluginMain INSTANCE = null; + * public static PluginMain INSTANCE; * public PluginMain() { * INSTANCE = this; + * this.reloadPluginData(MyPluginData.INSTANCE); // 读取文件等 * } * } * * // MyPluginData.java - * public class AccountPluginData extends JPluginData { - * public static AccountPluginData INSTANCE; + * public class MyPluginData extends JAutoSavePluginData { + * public static final MyPluginData INSTANCE = new MyPluginData(); * - * public AccountPluginData() { - * super(PluginMain.INSTANCE.loadPluginData(AccountPluginData.class)); + * private MyPluginData() { + * super(PluginMain.INSTANCE); * INSTANCE = this; * } * @@ -64,57 +66,57 @@ import kotlin.reflect.full.createType * theList.set(); * ``` * + * **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象. + * * @see PluginData */ -public open class JPluginData( - private val delegate: PluginData -) : PluginData by delegate { +public abstract class JAutoSavePluginData : AutoSavePluginData(), PluginConfig { //// region JPluginData_value_primitives CODEGEN //// /** * 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Byte): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Byte): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Short): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Short): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Int): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Int): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Long): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Long): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Float): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Float): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Double): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Double): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Char): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Char): SerializerAwareValue = valueImpl(default) /** * 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: Boolean): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: Boolean): SerializerAwareValue = valueImpl(default) /** * 创建一个 [String] 类型的 [Value], 并设置初始值为 [default] */ - public fun value(default: String): SerializerAwareValue = delegate.valueImpl(default) + public fun value(default: String): SerializerAwareValue = valueImpl(default) //// endregion JPluginData_value_primitives CODEGEN //// @@ -134,7 +136,7 @@ public open class JPluginData( */ @JvmOverloads public fun typedValue(type: KType, default: T? = null): SerializerAwareValue { - val value = delegate.valueImpl(type, type.classifier!!.cast()) + val value = valueImpl(type, type.classifier!!.cast()) if (default != null) value.setValueBySerializer(default) return value } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/AutoSavePluginData.kt deleted file mode 100644 index bb70c4be9..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/AutoSavePluginData.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.internal.data - -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.* -import net.mamoe.mirai.console.data.* -import net.mamoe.mirai.console.internal.plugin.updateWhen -import net.mamoe.mirai.console.util.ConsoleInternalAPI -import net.mamoe.mirai.utils.currentTimeMillis -import kotlin.reflect.KClass - -/** - * 链接自动保存的 [PluginData]. - * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 - * - * 若 [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], 则 [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. - */ -internal open class AutoSavePluginData( - private val owner: AutoSavePluginDataHolder, - internal val originPluginDataClass: KClass -) : AbstractPluginData(), PluginConfig { - - private lateinit var storage: PluginDataStorage - - override fun setStorage(storage: PluginDataStorage) { - check(!this::storage.isInitialized) { "storage is already initialized" } - this.storage = storage - - if (shouldPerformAutoSaveWheneverChanged()) { - owner.launch { - while (isActive) { - delay(owner.autoSaveIntervalMillis.last) // 定时自动保存一次, 用于 kts 序列化的对象 - doSave() - } - } - } - } - - @JvmField - @Volatile - internal var lastAutoSaveJob: Job? = null - - @JvmField - internal val currentFirstStartTime = atomic(0L) - - protected open fun shouldPerformAutoSaveWheneverChanged(): Boolean { - return true - } - - init { - owner.coroutineContext[Job]?.invokeOnCompletion { doSave() } - } - - private val updaterBlock: suspend CoroutineScope.() -> Unit = { - if (::storage.isInitialized) { - currentFirstStartTime.updateWhen({ it == 0L }, { currentTimeMillis }) - - delay(owner.autoSaveIntervalMillis.first.coerceAtLeast(1000)) // for safety - - if (lastAutoSaveJob == this.coroutineContext[Job]) { - doSave() - } else { - if (currentFirstStartTime.updateWhen( - { currentTimeMillis - it >= owner.autoSaveIntervalMillis.last }, - { 0 }) - ) doSave() - } - } - } - - @Suppress("RedundantVisibilityModifier") - @ConsoleInternalAPI - public final override fun onValueChanged(value: Value<*>) { - lastAutoSaveJob = owner.launch(block = updaterBlock) - } - - private fun doSave() = storage.store(owner, this) -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MemoryPluginDataStorageImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MemoryPluginDataStorageImpl.kt index 2b7515aa1..41adca66a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MemoryPluginDataStorageImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MemoryPluginDataStorageImpl.kt @@ -9,45 +9,20 @@ package net.mamoe.mirai.console.internal.data -import net.mamoe.mirai.console.data.* -import net.mamoe.mirai.console.util.ConsoleInternalAPI +import net.mamoe.mirai.console.data.MemoryPluginDataStorage +import net.mamoe.mirai.console.data.PluginData +import net.mamoe.mirai.console.data.PluginDataHolder +import net.mamoe.mirai.console.data.PluginDataStorage -internal class MemoryPluginDataStorageImpl( - private val onChanged: MemoryPluginDataStorage.OnChangedCallback -) : PluginDataStorage, MemoryPluginDataStorage, +internal class MemoryPluginDataStorageImpl : PluginDataStorage, MemoryPluginDataStorage, MutableMap, PluginData> by mutableMapOf() { - internal inner class MemoryPluginDataImpl : AbstractPluginData() { - @ConsoleInternalAPI - override fun onValueChanged(value: Value<*>) { - onChanged.onChanged(this@MemoryPluginDataStorageImpl, value) - } - - override fun setStorage(storage: PluginDataStorage) { - check(storage is MemoryPluginDataStorageImpl) { "storage is not MemoryPluginDataStorageImpl" } - } + @Suppress("UNCHECKED_CAST") + override fun load(holder: PluginDataHolder, instance: PluginData) { + instance.onStored(holder, this) } - @Suppress("UNCHECKED_CAST") - override fun load(holder: PluginDataHolder, dataClass: Class): T = (synchronized(this) { - this.getOrPut(dataClass) { - dataClass.kotlin.run { - objectInstance ?: createInstanceOrNull() ?: kotlin.run { - if (dataClass != PluginData::class.java) { - throw IllegalArgumentException( - "Cannot create PluginData instance. Make sure dataClass is PluginData::class.java or a Kotlin's object, " + - "or has a constructor which either has no parameters or all parameters of which are optional" - ) - } - MemoryPluginDataImpl() - } - } - } - } as T).also { it.setStorage(this) } - - override fun store(holder: PluginDataHolder, pluginData: PluginData) { - synchronized(this) { - this[pluginData::class.java] = pluginData - } + override fun store(holder: PluginDataHolder, instance: PluginData) { + // no-op } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt index 473fbb6d9..068d1ec08 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt @@ -9,75 +9,59 @@ package net.mamoe.mirai.console.internal.data -import net.mamoe.mirai.console.data.* +import net.mamoe.mirai.console.data.MultiFilePluginDataStorage +import net.mamoe.mirai.console.data.PluginData +import net.mamoe.mirai.console.data.PluginDataHolder +import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.SilentLogger +import net.mamoe.mirai.utils.debug import net.mamoe.yamlkt.Yaml import java.io.File import java.nio.file.Path -import kotlin.reflect.KClass @Suppress("RedundantVisibilityModifier") // might be public in the future internal open class MultiFilePluginDataStorageImpl( - public final override val directoryPath: Path + public final override val directoryPath: Path, + private val logger: MiraiLogger = SilentLogger, ) : PluginDataStorage, MultiFilePluginDataStorage { init { directoryPath.mkdir() } - public override fun load(holder: PluginDataHolder, dataClass: Class): T = - with(dataClass.kotlin) { - @Suppress("UNCHECKED_CAST") - val instance = objectInstance ?: this.createInstanceOrNull() ?: kotlin.run { - require(dataClass == PluginData::class.java) { - "Cannot create PluginData instance. Make sure dataClass is PluginData::class.java or a Kotlin's object, " + - "or has a constructor which either has no parameters or all parameters of which are optional" - } - if (holder is AutoSavePluginDataHolder) { - AutoSavePluginData(holder, this) as T? - } else null - } ?: throw IllegalArgumentException( - "Cannot create PluginData instance. Make sure 'holder' is a AutoSavePluginDataHolder, " + - "or 'data' is an object or has a constructor which either has no parameters or all parameters of which are optional" - ) + public override fun load(holder: PluginDataHolder, instance: PluginData) { + instance.onStored(holder, this) - val file = getPluginDataFile(holder, this) - file.createNewFile() - check(file.exists() && file.isFile && file.canRead()) { "${file.absolutePath} cannot be read" } - val text = file.readText() - if (text.isNotBlank()) { - Yaml.default.decodeFromString(instance.updaterSerializer, file.readText()) - } - instance - }.also { it.setStorage(this) } + val text = getPluginDataFile(holder, instance).readText() + if (text.isNotBlank()) { + Yaml.default.decodeFromString(instance.updaterSerializer, text) + } + logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" } + } - protected open fun getPluginDataFile(holder: PluginDataHolder, clazz: KClass<*>): File = with(clazz) { - val name = findValueName() + protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File { + val name = instance.saveName val dir = directoryPath.resolve(holder.name) if (dir.isFile) { - error("Target directory $dir for holder $holder is occupied by a file therefore data $qualifiedNameOrTip can't be saved.") + error("Target directory $dir for holder $holder is occupied by a file therefore data ${instance::class.qualifiedNameOrTip} can't be saved.") } dir.mkdir() - val file = directoryPath.resolve(name) + val file = dir.resolve(name) if (file.isDirectory) { - error("Target file $file is occupied by a directory therefore data $qualifiedNameOrTip can't be saved.") + error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.") } - return file.toFile() + logger.debug { "File allocated for ${instance.saveName}: $file" } + return file.toFile().also { it.createNewFile() } } @ConsoleExperimentalAPI - public override fun store(holder: PluginDataHolder, pluginData: PluginData) { - val file = - getPluginDataFile( - holder, - if (pluginData is AutoSavePluginData) pluginData.originPluginDataClass else pluginData::class - ) - - if (file.exists() && file.isFile && file.canRead()) { - file.writeText(Yaml.default.encodeToString(pluginData.updaterSerializer, Unit)) - } + public override fun store(holder: PluginDataHolder, instance: PluginData) { + getPluginDataFile(holder, instance).writeText(Yaml.default.encodeToString(instance.updaterSerializer, Unit)) + logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index ee3d1fad2..7572025f6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -17,9 +17,9 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.plugin.* -import net.mamoe.mirai.console.plugin.dsecription.PluginDependency -import net.mamoe.mirai.console.plugin.dsecription.PluginDescription -import net.mamoe.mirai.console.plugin.dsecription.PluginKind +import net.mamoe.mirai.console.plugin.description.PluginDependency +import net.mamoe.mirai.console.plugin.description.PluginDescription +import net.mamoe.mirai.console.plugin.description.PluginKind import net.mamoe.mirai.utils.info import java.nio.file.Path import java.util.concurrent.locks.ReentrantLock diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt index cb0f80e90..2e76603bb 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.* -import net.mamoe.mirai.console.data.PluginDataStorage.Companion.load import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.util.BotManager import net.mamoe.mirai.contact.User @@ -43,7 +42,7 @@ internal object BotManagerImpl : BotManager { } } -internal object ManagersConfig : PluginData by ConsoleBuiltInPluginDataStorage.load() { +internal object ManagersConfig : AutoSavePluginData() { private val managers: MutableMap> by value() internal operator fun get(bot: Bot): MutableSet = managers.getOrPut(bot.id, ::mutableSetOf) @@ -61,7 +60,4 @@ internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder, } internal object ConsoleBuiltInPluginDataStorage : - PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns { - - inline fun load(): T = load(ConsoleBuiltInPluginDataHolder) -} \ No newline at end of file + PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt index 31a8c8233..081e4cc05 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable -import net.mamoe.mirai.console.plugin.dsecription.PluginDescription +import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import java.io.File diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt index 669f3810c..a29bacb98 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.register -import net.mamoe.mirai.console.plugin.dsecription.PluginDescription +import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import java.io.File diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt index f829b51da..99445ce12 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt @@ -13,7 +13,7 @@ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl -import net.mamoe.mirai.console.plugin.dsecription.PluginDescription +import net.mamoe.mirai.console.plugin.description.PluginDescription import java.io.File import java.nio.file.Path diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginDependency.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt similarity index 98% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginDependency.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt index 288aed7ca..148e943fc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginDependency.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.plugin.dsecription +package net.mamoe.mirai.console.plugin.description import com.vdurmont.semver4j.Semver import kotlinx.serialization.KSerializer diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt similarity index 96% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginDescription.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt index 263710946..3fdf4937f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.plugin.dsecription +package net.mamoe.mirai.console.plugin.description import com.vdurmont.semver4j.Semver import kotlinx.serialization.Serializable diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginKind.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt similarity index 96% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginKind.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt index e12af1d73..a2fc7e6b7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/dsecription/PluginKind.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.plugin.dsecription +package net.mamoe.mirai.console.plugin.description import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt index 54e9e6468..35b83b8fe 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt @@ -21,13 +21,13 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI @ConsoleExperimentalAPI("classname might change") public interface JarPluginLoader : CoroutineScope, FilePluginLoader { /** - * [JvmPlugin.loadPluginData] 默认使用的实例 + * [JvmPlugin.reloadPluginData] 默认使用的实例 */ @ConsoleExperimentalAPI public val dataStorage: PluginDataStorage /** - * [JvmPlugin.loadPluginData] 默认使用的实例 + * [JvmPlugin.reloadPluginData] 默认使用的实例 */ @ConsoleExperimentalAPI public val configStorage: PluginDataStorage diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt index 37281aedf..784664b19 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt @@ -7,7 +7,13 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE") +@file:Suppress( + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE", + "EXPOSED_SUPER_CLASS", + "NOTHING_TO_INLINE", + "INAPPLICABLE_JVM_NAME" +) package net.mamoe.mirai.console.plugin.jvm @@ -19,7 +25,6 @@ import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginFileExtensions import net.mamoe.mirai.console.plugin.ResourceContainer import net.mamoe.mirai.utils.MiraiLogger -import kotlin.reflect.KClass /** @@ -51,16 +56,22 @@ public interface JvmPlugin : Plugin, CoroutineScope, get() = JarPluginLoader /** - * 读取一个 [PluginData] 实例 + * 重载 [PluginData] + * + * @see reloadPluginData */ @JvmDefault - public fun loadPluginData(clazz: Class): T = loader.dataStorage.load(this, clazz) + @JvmName("reloadPluginData") + public fun T.reload(): Unit = loader.dataStorage.load(this@JvmPlugin, this) /** - * 读取一个 [PluginConfig] 实例 + * 重载 [PluginConfig] + * + * @see reloadPluginConfig */ @JvmDefault - public fun loadPluginConfig(clazz: Class): T = loader.configStorage.load(this, clazz) + @JvmName("reloadPluginConfig") + public fun T.reload(): Unit = loader.configStorage.load(this@JvmPlugin, this) /** * 在插件被加载时调用. 只会被调用一次. @@ -85,25 +96,17 @@ public interface JvmPlugin : Plugin, CoroutineScope, } /** - * 读取一个 [PluginData] 实例 + * 重载一个 [PluginData] + * + * @see JvmPlugin.reload */ @JvmSynthetic -public inline fun JvmPlugin.loadPluginData(clazz: KClass): T = this.loadPluginData(clazz.java) +public inline fun JvmPlugin.reloadPluginData(instance: PluginData): Unit = this.run { instance.reload() } /** - * 读取一个 [PluginData] 实例 + * 重载一个 [PluginConfig] + * + * @see JvmPlugin.reload */ @JvmSynthetic -public inline fun JvmPlugin.loadPluginData(): T = this.loadPluginData(T::class) - -/** - * 读取一个 [PluginConfig] 实例 - */ -@JvmSynthetic -public inline fun JvmPlugin.loadPluginConfig(clazz: KClass): T = this.loadPluginConfig(clazz.java) - -/** - * 读取一个 [PluginConfig] 实例 - */ -@JvmSynthetic -public inline fun JvmPlugin.loadPluginConfig(): T = this.loadPluginConfig(T::class) \ No newline at end of file +public inline fun JvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 677f4e9be..61b830e37 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -14,9 +14,9 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.console.internal.data.SemverAsStringSerializerLoose -import net.mamoe.mirai.console.plugin.dsecription.PluginDependency -import net.mamoe.mirai.console.plugin.dsecription.PluginDescription -import net.mamoe.mirai.console.plugin.dsecription.PluginKind +import net.mamoe.mirai.console.plugin.description.PluginDependency +import net.mamoe.mirai.console.plugin.description.PluginDescription +import net.mamoe.mirai.console.plugin.description.PluginKind import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI import java.io.File diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt index 2647b9a83..9ce496c43 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt @@ -24,7 +24,6 @@ public abstract class KotlinPlugin @JvmOverloads constructor( parentCoroutineContext: CoroutineContext = EmptyCoroutineContext ) : JvmPlugin, AbstractJvmPlugin(parentCoroutineContext) - /** * 在内存动态加载的插件. 此为预览版本 API. */ diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index eb29088f6..ad9cdb1c5 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -49,8 +49,7 @@ fun initTestEnvironment() { override suspend fun sendMessage(message: Message) = println(message) } override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage() - override val configStorageForJarPluginLoader: PluginDataStorage - get() = TODO("Not yet implemented") + override val configStorageForJarPluginLoader: PluginDataStorage get() = TODO("Not yet implemented") override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage() override val consoleInput: ConsoleInput = object : ConsoleInput { override suspend fun requestInput(hint: String): String { diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt index 51350666e..38362b70c 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.json.Json +import net.mamoe.mirai.console.data.AutoSavePluginDataHolder.Companion.createPluginData +import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.util.ConsoleInternalAPI import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -18,18 +20,12 @@ import kotlin.test.assertSame @OptIn(ConsoleInternalAPI::class) internal class PluginDataTest { - class MyPluginData : AbstractPluginData() { + object MyPlugin : KotlinPlugin() + + class MyPluginData : PluginData by MyPlugin.createPluginData() { var int by value(1) - val map by value>() - val map2 by value>>() - - @ConsoleInternalAPI - override fun onValueChanged(value: Value<*>) { - - } - - override fun setStorage(storage: PluginDataStorage) { - } + val map: MutableMap by value() + val map2: MutableMap> by value() } private val jsonPrettyPrint = Json { prettyPrint = true } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 211e95dcd..871ddd533 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -18,7 +18,7 @@ object Versions { const val core = "1.2.2" - const val console = "1.0-M2-1" + const val console = "1.0-M3-dev-1" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" const val consolePure = console diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt index 9a8fda417..443ef22c3 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.console.pure import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.Command.Companion.primaryName @@ -20,81 +22,97 @@ import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.requestInput import net.mamoe.mirai.utils.DefaultLogger import org.fusesource.jansi.Ansi +import org.jline.reader.UserInterruptException import java.util.* -import java.util.concurrent.Executors import kotlin.concurrent.thread +import kotlin.system.exitProcess @OptIn(ConsoleInternalAPI::class) internal fun startupConsoleThread() { - val service = Executors.newSingleThreadExecutor { code -> - thread(start = false, isDaemon = false, name = "Console Input", block = code::run) - } - val dispatch = service.asCoroutineDispatcher() + val mutex = Mutex() ConsoleUtils.miraiLineReader = { hint -> - withContext(dispatch) { - ConsoleUtils.lineReader.readLine( - if (hint.isNotEmpty()) { - ConsoleUtils.lineReader.printAbove( - Ansi.ansi() - .fgCyan().a(MiraiConsoleFrontEndPure.sdf.format(Date())).a(" ") - .fgMagenta().a(hint) - .reset() - .toString() - ) - "$hint > " - } else "> " - ) + mutex.withLock { + withContext(Dispatchers.IO) { + println("Requesting input") + ConsoleUtils.lineReader.readLine( + if (hint.isNotEmpty()) { + ConsoleUtils.lineReader.printAbove( + Ansi.ansi() + .fgCyan().a(sdf.format(Date())).a(" ") + .fgMagenta().a(hint) + .reset() + .toString() + ) + "$hint > " + } else "> " + ) + } } } - MiraiConsole.launch(dispatch) { - val consoleLogger = DefaultLogger("console") - while (isActive) { - try { - val next = MiraiConsole.requestInput("").let { - when { - it.startsWith(CommandManager.commandPrefix) -> it - it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName - else -> CommandManager.commandPrefix + it + val consoleLogger = DefaultLogger("console") + + val inputThread = thread(start = true, isDaemon = false, name = "Console Input") { + try { + runBlocking { + while (true) { + try { + val next = MiraiConsole.requestInput("").let { + when { + it.startsWith(CommandManager.commandPrefix) -> it + it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName + else -> CommandManager.commandPrefix + it + } + } + exitProcess(123456) + if (next.isBlank()) { + continue + } + // consoleLogger.debug("INPUT> $next") + val result = ConsoleCommandSenderImpl.executeCommand(next) + when (result.status) { + CommandExecuteStatus.SUCCESSFUL -> { + } + CommandExecuteStatus.EXECUTION_EXCEPTION -> { + result.exception?.printStackTrace() + } + CommandExecuteStatus.COMMAND_NOT_FOUND -> { + consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助") + } + CommandExecuteStatus.PERMISSION_DENIED -> { + consoleLogger.warning("Permission denied.") + } + } + } catch (e: InterruptedException) { + return@runBlocking + } catch (e: CancellationException) { + return@runBlocking + } catch (e: UserInterruptException) { + MiraiConsole.cancel() + return@runBlocking + } catch (e: Throwable) { + consoleLogger.error("Unhandled exception", e) } } - if (next.isBlank()) { - continue - } - // consoleLogger.debug("INPUT> $next") - val result = ConsoleCommandSenderImpl.executeCommand(next) - when (result.status) { - CommandExecuteStatus.SUCCESSFUL -> { - } - CommandExecuteStatus.EXECUTION_EXCEPTION -> { - result.exception?.printStackTrace() - } - CommandExecuteStatus.COMMAND_NOT_FOUND -> { - consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助") - } - CommandExecuteStatus.PERMISSION_DENIED -> { - consoleLogger.warning("Permission denied.") - } - } - } catch (e: InterruptedException) { - return@launch - } catch (e: CancellationException) { - return@launch - } catch (e: Throwable) { - consoleLogger.error("Unhandled exception", e) } - } - }.let { consoleJob -> - MiraiConsole.job.invokeOnCompletion { - runCatching { - consoleJob.cancel() - }.exceptionOrNull()?.printStackTrace() - runCatching { - service.shutdownNow() - }.exceptionOrNull()?.printStackTrace() - runCatching { - ConsoleUtils.terminal.close() - }.exceptionOrNull()?.printStackTrace() + } catch (e: InterruptedException) { + return@thread + } catch (e: CancellationException) { + return@thread + } catch (e: UserInterruptException) { + MiraiConsole.cancel() + return@thread + } catch (e: Throwable) { + consoleLogger.error("Unhandled exception", e) } } + + MiraiConsole.job.invokeOnCompletion { + runCatching { + inputThread.interrupt() + }.exceptionOrNull()?.printStackTrace() + runCatching { + ConsoleUtils.terminal.close() + }.exceptionOrNull()?.printStackTrace() + } } diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt deleted file mode 100644 index 804b4899c..000000000 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress( - "INVISIBLE_MEMBER", - "INVISIBLE_REFERENCE", - "CANNOT_OVERRIDE_INVISIBLE_MEMBER", - "INVISIBLE_SETTER", - "INVISIBLE_GETTER", - "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", - "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING", - "EXPOSED_SUPER_CLASS" -) - -package net.mamoe.mirai.console.pure - -//import net.mamoe.mirai.console.command.CommandManager -//import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd -import com.vdurmont.semver4j.Semver -import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription -import net.mamoe.mirai.console.util.ConsoleInternalAPI -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.PlatformLogger -import org.fusesource.jansi.Ansi -import java.text.SimpleDateFormat - -private val ANSI_RESET = Ansi().reset().toString() - -internal val LoggerCreator: (identity: String?) -> MiraiLogger = { - PlatformLogger(identity = it, output = { line -> - ConsoleUtils.lineReader.printAbove(line + ANSI_RESET) - }) -} - -/** - * mirai-console-pure 前端实现 - * - * @see MiraiConsoleImplementationPure 后端实现 - * @see MiraiConsolePureLoader CLI 入口点 - */ -@ConsoleInternalAPI -@Suppress("unused") -object MiraiConsoleFrontEndPure : MiraiConsoleFrontEndDescription { - internal val sdf by ThreadLocal.withInitial { - // SimpleDateFormat not thread safe. - SimpleDateFormat("HH:mm:ss") - } - - private operator fun ThreadLocal.getValue(thiz: Any, property: Any): T { - return this.get() - } - - override val name: String get() = "Pure" - override val version: Semver get() = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version - override val vendor: String get() = "Mamoe Technologies" -} - - -/* -class MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { - private var requesting = false - private var requestStr = "" - - @Suppress("unused") - companion object { - // ANSI color codes - const val COLOR_RED = "\u001b[38;5;196m" - const val COLOR_CYAN = "\u001b[38;5;87m" - const val COLOR_GREEN = "\u001b[38;5;82m" - - // use a dark yellow(more like orange) instead of light one to save Solarized-light users - const val COLOR_YELLOW = "\u001b[38;5;220m" - const val COLOR_GREY = "\u001b[38;5;244m" - const val COLOR_BLUE = "\u001b[38;5;27m" - const val COLOR_NAVY = "\u001b[38;5;24m" // navy uniform blue - const val COLOR_PINK = "\u001b[38;5;207m" - const val COLOR_RESET = "\u001b[39;49m" - } - - init { - thread(name = "Mirai Console Input Thread") { - while (true) { - val input = readLine() ?: return@thread - if (requesting) { - requestStr = input - requesting = false - } else { - CommandManager.runCommand(ConsoleCommandSender, input) - } - } - } - } - - val sdf by lazy { - SimpleDateFormat("HH:mm:ss") - } - - override val logger: MiraiLogger = DefaultLogger("Console") // CLI logger from mirai-core - - fun pushLog(identity: Long, message: String) { - println("\u001b[0m " + sdf.format(Date()) + " $message") - } - - override fun prePushBot(identity: Long) { - - } - - override fun pushBot(bot: Bot) { - - } - - override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) { - - } - - override suspend fun requestInput(hint: String): String { - if (hint.isNotEmpty()) { - println("\u001b[0m " + sdf.format(Date()) + COLOR_PINK + " $hint") - } - requesting = true - while (true) { - delay(50) - if (!requesting) { - return requestStr - } - } - } - - override fun pushBotAdminStatus(identity: Long, admins: List) { - - } - - override fun createLoginSolver(): LoginSolver { - return DefaultLoginSolver( - input = suspend { - requestInput("") - } - ) - } - -} - -*/ diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt index 162ebad22..efed8e9eb 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt @@ -38,12 +38,11 @@ import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalAPI -import net.mamoe.mirai.utils.BotConfiguration -import net.mamoe.mirai.utils.DefaultLoginSolver -import net.mamoe.mirai.utils.LoginSolver -import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.* +import org.fusesource.jansi.Ansi import java.nio.file.Path import java.nio.file.Paths +import java.text.SimpleDateFormat import java.util.* /** @@ -91,4 +90,22 @@ private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { override val name: String get() = "Pure" override val vendor: String get() = "Mamoe Technologies" override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version -} \ No newline at end of file +} + +private val ANSI_RESET = Ansi().reset().toString() + +internal val LoggerCreator: (identity: String?) -> MiraiLogger = { + PlatformLogger(identity = it, output = { line -> + ConsoleUtils.lineReader.printAbove(line + ANSI_RESET) + }) +} + +internal val sdf by ThreadLocal.withInitial { + // SimpleDateFormat not thread safe. + SimpleDateFormat("HH:mm:ss") +} + +private operator fun ThreadLocal.getValue(thisRef: Any?, property: Any): T { + return this.get() +} + diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index b4e648cb7..7f502a039 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -36,12 +36,13 @@ object MiraiConsolePureLoader { fun main(args: Array) { startup() } -} -internal fun startup() { - MiraiConsoleImplementationPure().start() - overrideSTD() - startupConsoleThread() + @Suppress("MemberVisibilityCanBePrivate") + internal fun startup(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) { + instance.start() + overrideSTD() + startupConsoleThread() + } } internal fun overrideSTD() {