Introduce PluginData and PluginConfig

This commit is contained in:
Him188 2020-08-25 22:43:31 +08:00
parent 9cc05f682b
commit ed2ef37304
34 changed files with 446 additions and 527 deletions

View File

@ -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

View File

@ -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
}

View File

@ -36,6 +36,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
/**
* 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
*/
@ConsoleExperimentalAPI
public override fun <T : SerializerAwareValue<*>> T.track(valueName: String): T =
apply { valueNodes.add(ValueNode(valueName, this, this.serializer)) }

View File

@ -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

View File

@ -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)
}

View File

@ -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
*/

View File

@ -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<String> by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略
* val custom: Map<Long, CustomData> 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<ValueNode<*>>
/**
* 这个 [PluginData] 保存时使用的名称. 默认通过 [ValueName] 获取, 否则使用 [类全名][KClass.qualifiedName] ( [Class.getCanonicalName])
*/
@ConsoleExperimentalAPI
public val saveName: String
get() {
val clazz = this::class
return clazz.findAnnotation<ValueName>()?.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 : SerializerAwareValue<*>> 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<String> by value()
* }
*
* val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list)
* ```
*
* @see PluginData
*/
@Suppress("UNCHECKED_CAST")
public fun <T> PluginData.findBackingFieldValue(property: KProperty<T>): Value<out T>? =
@ -177,7 +192,7 @@ public fun <T> PluginData.findBackingFieldValue(property: KProperty<T>): Value<o
*
* , 对于
* ```
* object MyData : PluginData {
* object MyData : AutoSavePluginData(PluginMain) {
* @ValueName("theList")
* val list: List<String> by value()
* val int: Int by value()
@ -186,6 +201,8 @@ public fun <T> PluginData.findBackingFieldValue(property: KProperty<T>): Value<o
* val value: Value<List<String>> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称
* val intValue: Value<Int> = MyData.findBackingFieldValue("int")
* ```
*
* @see PluginData
*/
@Suppress("UNCHECKED_CAST")
public fun <T> PluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? {
@ -198,12 +215,14 @@ public fun <T> PluginData.findBackingFieldValue(propertyValueName: String): Valu
*
* , 对于
* ```
* object MyData : PluginData {
* object MyData : AutoSavePluginData(PluginMain) {
* val list: List<String> by value()
* }
*
* val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list)
* ```
*
* @see PluginData
*/
@Suppress("UNCHECKED_CAST")
public fun <T> PluginData.findBackingFieldValueNode(property: KProperty<T>): PluginData.ValueNode<out T>? {

View File

@ -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] 每次自动保存时间间隔

View File

@ -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 <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): 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 <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: KClass<T>): T =
this.load(holder, dataClass.java)
/*
public companion object {
/**
* 通过反射
* 读取一个实例.
*
* [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage]
*/
@ConsoleExperimentalAPI
@JvmStatic
public fun <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: KClass<T>): 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 <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: Class<T>): T =
this.load(holder, dataClass.java)
/**
* 读取一个实例. [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage]
*/
@JvmSynthetic
public inline fun <reified T : PluginData> PluginDataStorage.load(holder: PluginDataHolder): T =
this.load(holder, T::class)
/**
* 读取一个实例. [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage]
*/
@JvmSynthetic
public inline fun <reified T : PluginData> PluginDataStorage.load(holder: PluginDataHolder): T =
this.load(holder, T::class)
}
*/
}
/**
* 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失.
* @see PluginDataStorage
*/
public interface MemoryPluginDataStorage : PluginDataStorage, Map<Class<out PluginData>, 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()
}
}

View File

@ -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
@ExperimentalPluginConfig
public abstract class JAutoSavePluginConfig : AutoSavePluginConfig(), PluginConfig

View File

@ -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<Byte> = delegate.valueImpl(default)
public fun value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default)
/**
* 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Short): SerializerAwareValue<Short> = delegate.valueImpl(default)
public fun value(default: Short): SerializerAwareValue<Short> = valueImpl(default)
/**
* 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Int): SerializerAwareValue<Int> = delegate.valueImpl(default)
public fun value(default: Int): SerializerAwareValue<Int> = valueImpl(default)
/**
* 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Long): SerializerAwareValue<Long> = delegate.valueImpl(default)
public fun value(default: Long): SerializerAwareValue<Long> = valueImpl(default)
/**
* 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Float): SerializerAwareValue<Float> = delegate.valueImpl(default)
public fun value(default: Float): SerializerAwareValue<Float> = valueImpl(default)
/**
* 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Double): SerializerAwareValue<Double> = delegate.valueImpl(default)
public fun value(default: Double): SerializerAwareValue<Double> = valueImpl(default)
/**
* 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Char): SerializerAwareValue<Char> = delegate.valueImpl(default)
public fun value(default: Char): SerializerAwareValue<Char> = valueImpl(default)
/**
* 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Boolean): SerializerAwareValue<Boolean> = delegate.valueImpl(default)
public fun value(default: Boolean): SerializerAwareValue<Boolean> = valueImpl(default)
/**
* 创建一个 [String] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: String): SerializerAwareValue<String> = delegate.valueImpl(default)
public fun value(default: String): SerializerAwareValue<String> = valueImpl(default)
//// endregion JPluginData_value_primitives CODEGEN ////
@ -134,7 +136,7 @@ public open class JPluginData(
*/
@JvmOverloads
public fun <T : Any> typedValue(type: KType, default: T? = null): SerializerAwareValue<T> {
val value = delegate.valueImpl<T>(type, type.classifier!!.cast())
val value = valueImpl<T>(type, type.classifier!!.cast())
if (default != null) value.setValueBySerializer(default)
return value
}

View File

@ -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<out PluginData>
) : 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)
}

View File

@ -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<Class<out PluginData>, 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 <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): 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
}
}

View File

@ -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 <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): 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)" }
}
}

View File

@ -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

View File

@ -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<Long, MutableSet<Long>> by value()
internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf)
@ -61,7 +60,4 @@ internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
}
internal object ConsoleBuiltInPluginDataStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns {
inline fun <reified T : PluginData> load(): T = load(ConsoleBuiltInPluginDataHolder)
}
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -21,13 +21,13 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
@ConsoleExperimentalAPI("classname might change")
public interface JarPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
/**
* [JvmPlugin.loadPluginData] 默认使用的实例
* [JvmPlugin.reloadPluginData] 默认使用的实例
*/
@ConsoleExperimentalAPI
public val dataStorage: PluginDataStorage
/**
* [JvmPlugin.loadPluginData] 默认使用的实例
* [JvmPlugin.reloadPluginData] 默认使用的实例
*/
@ConsoleExperimentalAPI
public val configStorage: PluginDataStorage

View File

@ -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 <T : PluginData> loadPluginData(clazz: Class<T>): T = loader.dataStorage.load(this, clazz)
@JvmName("reloadPluginData")
public fun <T : PluginData> T.reload(): Unit = loader.dataStorage.load(this@JvmPlugin, this)
/**
* 读取一个 [PluginConfig] 实例
* 重载 [PluginConfig]
*
* @see reloadPluginConfig
*/
@JvmDefault
public fun <T : PluginConfig> loadPluginConfig(clazz: Class<T>): T = loader.configStorage.load(this, clazz)
@JvmName("reloadPluginConfig")
public fun <T : PluginConfig> 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 <T : PluginData> JvmPlugin.loadPluginData(clazz: KClass<T>): 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 <reified T : PluginData> JvmPlugin.loadPluginData(): T = this.loadPluginData(T::class)
/**
* 读取一个 [PluginConfig] 实例
*/
@JvmSynthetic
public inline fun <T : PluginConfig> JvmPlugin.loadPluginConfig(clazz: KClass<T>): T = this.loadPluginConfig(clazz.java)
/**
* 读取一个 [PluginConfig] 实例
*/
@JvmSynthetic
public inline fun <reified T : PluginConfig> JvmPlugin.loadPluginConfig(): T = this.loadPluginConfig(T::class)
public inline fun JvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() }

View File

@ -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

View File

@ -24,7 +24,6 @@ public abstract class KotlinPlugin @JvmOverloads constructor(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
) : JvmPlugin, AbstractJvmPlugin(parentCoroutineContext)
/**
* 在内存动态加载的插件. 此为预览版本 API.
*/

View File

@ -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 {

View File

@ -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<MutableMap<String, String>>()
val map2 by value<MutableMap<String, MutableMap<String, String>>>()
@ConsoleInternalAPI
override fun onValueChanged(value: Value<*>) {
}
override fun setStorage(storage: PluginDataStorage) {
}
val map: MutableMap<String, String> by value()
val map2: MutableMap<String, MutableMap<String, String>> by value()
}
private val jsonPrettyPrint = Json { prettyPrint = true }

View File

@ -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

View File

@ -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()
}
}

View File

@ -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 <T> ThreadLocal<T>.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<Long>) {
}
override fun createLoginSolver(): LoginSolver {
return DefaultLoginSolver(
input = suspend {
requestInput("")
}
)
}
}
*/

View File

@ -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
}
}
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 <T> ThreadLocal<T>.getValue(thisRef: Any?, property: Any): T {
return this.get()
}

View File

@ -36,12 +36,13 @@ object MiraiConsolePureLoader {
fun main(args: Array<String>) {
startup()
}
}
internal fun startup() {
MiraiConsoleImplementationPure().start()
overrideSTD()
startupConsoleThread()
@Suppress("MemberVisibilityCanBePrivate")
internal fun startup(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {
instance.start()
overrideSTD()
startupConsoleThread()
}
}
internal fun overrideSTD() {