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 [`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 [`PluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[`PluginManager`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt [`PluginManager`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
[`JarPluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt [`JarPluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt

View File

@ -60,8 +60,7 @@ kotlin {
} }
dependencies { dependencies {
compileAndRuntime("net.mamoe:mirai-core:${Versions.core}") implementation("net.mamoe:mirai-core:${Versions.core}")
compileAndRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib))
implementation(kotlinx("serialization-core", Versions.serialization)) implementation(kotlinx("serialization-core", Versions.serialization))
implementation(kotlin("reflect")) implementation(kotlin("reflect"))
@ -92,6 +91,8 @@ ext.apply {
dependencyFilter.exclude { dependencyFilter.exclude {
when ("${it.moduleGroup}:${it.moduleName}") { when ("${it.moduleGroup}:${it.moduleName}") {
"net.mamoe:mirai-core" -> true "net.mamoe:mirai-core" -> true
"org.jetbrains.kotlin:kotlin-stdlib" -> true
"org.jetbrains.kotlin:kotlin-stdlib-jdk8" -> true
"net.mamoe:mirai-core-qqandroid" -> true "net.mamoe:mirai-core-qqandroid" -> true
else -> false else -> false
} }

View File

@ -36,6 +36,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
/** /**
* 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] * 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
*/ */
@ConsoleExperimentalAPI
public override fun <T : SerializerAwareValue<*>> T.track(valueName: String): T = public override fun <T : SerializerAwareValue<*>> T.track(valueName: String): T =
apply { valueNodes.add(ValueNode(valueName, this, this.serializer)) } 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 package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.data.java.JAutoSavePluginConfig
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
@ -27,14 +28,14 @@ import kotlin.annotation.AnnotationTarget.*
* [PluginData] 的示例基础上, 修改对象定义 * [PluginData] 的示例基础上, 修改对象定义
* ``` * ```
* // 原 * // 原
* object MyPluginData : PluginData by PluginMain.loadPluginData() * object MyPluginData : AutoSavePluginData()
* // 修改为 * // 修改为
* object MyPluginConfig : PluginConfig by PluginMain.loadPluginConfig() * object MyPluginConfig : AutoSavePluginConfig()
* ``` * ```
* 即可将一个 [PluginData] 变更为 [PluginConfig]. * 即可将一个 [PluginData] 变更为 [PluginConfig].
* *
* ### Java * ### Java
* [JPluginConfig] * [JAutoSavePluginConfig]
* *
* @see PluginData * @see PluginData
*/ */

View File

@ -18,15 +18,17 @@
package net.mamoe.mirai.console.data package net.mamoe.mirai.console.data
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import net.mamoe.mirai.console.data.java.JAutoSavePluginData
import net.mamoe.mirai.console.internal.data.* import net.mamoe.mirai.console.internal.data.*
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin 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.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import kotlin.internal.LowPriorityInOverloadResolution import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation
/** /**
* 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. * 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪.
@ -47,7 +49,7 @@ import kotlin.reflect.KType
* ``` * ```
* object PluginMain : KotlinPlugin() * 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 list: MutableList<String> by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略
* val custom: Map<Long, CustomData> by value() // 使用 kotlinx-serialization 序列化的类型. (目前还不支持) * val custom: Map<Long, CustomData> by value() // 使用 kotlinx-serialization 序列化的类型. (目前还不支持)
* var long: Long by value(0) // 允许 var * var long: Long by value(0) // 允许 var
@ -69,11 +71,9 @@ import kotlin.reflect.KType
* *
* ### 使用 Java * ### 使用 Java
* *
* 参考 [JPluginData] * 参考 [JAutoSavePluginData]
* *
* **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象. * @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
*
* @see JvmPlugin.loadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
* @see PluginDataStorage [PluginData] 存储仓库 * @see PluginDataStorage [PluginData] 存储仓库
*/ */
public interface PluginData { public interface PluginData {
@ -88,6 +88,18 @@ public interface PluginData {
@ConsoleExperimentalAPI @ConsoleExperimentalAPI
public val valueNodes: MutableList<ValueNode<*>> 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` 初始化的属性节点. * [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点.
*/ */
@ -123,6 +135,7 @@ public interface PluginData {
/** /**
* 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] * 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
*/ */
@ConsoleExperimentalAPI
public fun <T : SerializerAwareValue<*>> T.track( public fun <T : SerializerAwareValue<*>> T.track(
/** /**
* 值名称. * 值名称.
@ -153,7 +166,7 @@ public interface PluginData {
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
*/ */
@ConsoleInternalAPI @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 list: List<String> by value()
* } * }
* *
* val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list) * val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list)
* ``` * ```
*
* @see PluginData
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun <T> PluginData.findBackingFieldValue(property: KProperty<T>): Value<out T>? = 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") * @ValueName("theList")
* val list: List<String> by value() * val list: List<String> by value()
* val int: Int 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 value: Value<List<String>> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称
* val intValue: Value<Int> = MyData.findBackingFieldValue("int") * val intValue: Value<Int> = MyData.findBackingFieldValue("int")
* ``` * ```
*
* @see PluginData
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun <T> PluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? { 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 list: List<String> by value()
* } * }
* *
* val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list) * val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list)
* ``` * ```
*
* @see PluginData
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun <T> PluginData.findBackingFieldValueNode(property: KProperty<T>): PluginData.ValueNode<out T>? { 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.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.console.data.PluginDataStorage.Companion.load import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.internal.data.AutoSavePluginData
/** /**
* 可以持有相关 [PluginData] 实例的对象, 作为 [PluginData] 实例的拥有者. * 可以持有相关 [PluginData] 实例的对象, 作为 [PluginData] 实例的拥有者.
@ -24,6 +23,7 @@ import net.mamoe.mirai.console.internal.data.AutoSavePluginData
* *
* @see AutoSavePluginDataHolder 支持自动保存 * @see AutoSavePluginDataHolder 支持自动保存
*/ */
@ConsoleExperimentalAPI
public interface PluginDataHolder { public interface PluginDataHolder {
/** /**
* 保存时使用的分类名 * 保存时使用的分类名
@ -36,6 +36,7 @@ public interface PluginDataHolder {
* *
* @see net.mamoe.mirai.console.plugin.jvm.JvmPlugin * @see net.mamoe.mirai.console.plugin.jvm.JvmPlugin
*/ */
@ConsoleExperimentalAPI
public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope { public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope {
/** /**
* [AutoSavePluginData] 每次自动保存时间间隔 * [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.internal.data.MultiFilePluginDataStorageImpl
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import kotlin.reflect.KClass
/** /**
* [数据对象][PluginData] 存储仓库. * [数据对象][PluginData] 存储仓库.
* *
* ## 职责 * ## 职责
* [PluginDataStorage] 类似于一个数据库, 它只承担将序列化之后的数据保存到数据库中, 和从数据库取出这个对象的任务. * [PluginDataStorage] 类似于一个数据库, 它只承担将序列化之后的数据保存到数据库中, 和从数据库取出这个对象的任务.
* [PluginDataStorage] 不考虑一个 []
* *
* *
* 此为较低层的 API, 一般插件开发者不会接触. * 此为较低层的 API, 一般插件开发者不会接触.
@ -34,69 +33,77 @@ import kotlin.reflect.KClass
* @see PluginDataHolder * @see PluginDataHolder
* @see JarPluginLoader.dataStorage * @see JarPluginLoader.dataStorage
*/ */
@ConsoleExperimentalAPI
public interface PluginDataStorage { 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], * **实现细节**: 调用 [PluginData.updaterSerializer],
*/ */
public fun store(holder: PluginDataHolder, pluginData: PluginData) public fun store(holder: PluginDataHolder, instance: PluginData)
public companion object { /*
/** 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) * [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] 实例也会丢失. * 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失.
* @see PluginDataStorage * @see PluginDataStorage
*/ */
public interface MemoryPluginDataStorage : PluginDataStorage, Map<Class<out PluginData>, PluginData> { @ConsoleExperimentalAPI
/** public interface MemoryPluginDataStorage : PluginDataStorage {
* 当任一 [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
}
}
}
public companion object { public companion object {
/** /**
* 创建一个 [MemoryPluginDataStorage] 实例. * 创建一个 [MemoryPluginDataStorage] 实例.
*
* @param onChanged 当任一 [PluginData] 实例拥有的 [Value] 的值被改变后调用的回调函数.
*/ */
@JvmStatic @JvmStatic
@JvmName("create") @JvmName("create")
// @JvmOverloads // @JvmOverloads
public operator fun invoke(onChanged: OnChangedCallback = OnChangedCallback.NoOp): MemoryPluginDataStorage = public operator fun invoke(): MemoryPluginDataStorage = MemoryPluginDataStorageImpl()
MemoryPluginDataStorageImpl(onChanged)
} }
} }

View File

@ -7,7 +7,14 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 * ```java
* // 原 * // 原
* public class AccountPluginData extends JPluginData { * public class AccountPluginData extends JAutoSavePluginData {
* // 修改为 * // 修改为
* public class AccountPluginConfig extends JPluginConfig { * public class AccountPluginConfig extends JAutoSavePluginConfig {
* ``` * ```
* 即可将一个 [PluginData] 变更为 [PluginConfig]. * 即可将一个 [PluginData] 变更为 [PluginConfig].
* *
* @see JPluginData * @see JAutoSavePluginData
* @see PluginConfig * @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 * 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.cast
import net.mamoe.mirai.console.internal.data.setValueBySerializer import net.mamoe.mirai.console.internal.data.setValueBySerializer
import net.mamoe.mirai.console.internal.data.valueImpl import net.mamoe.mirai.console.internal.data.valueImpl
@ -27,18 +28,19 @@ import kotlin.reflect.full.createType
* ``` * ```
* // PluginMain.java * // PluginMain.java
* public final class PluginMain extends JavaPlugin { * public final class PluginMain extends JavaPlugin {
* public static PluginMain INSTANCE = null; * public static PluginMain INSTANCE;
* public PluginMain() { * public PluginMain() {
* INSTANCE = this; * INSTANCE = this;
* this.reloadPluginData(MyPluginData.INSTANCE); // 读取文件等
* } * }
* } * }
* *
* // MyPluginData.java * // MyPluginData.java
* public class AccountPluginData extends JPluginData { * public class MyPluginData extends JAutoSavePluginData {
* public static AccountPluginData INSTANCE; * public static final MyPluginData INSTANCE = new MyPluginData();
* *
* public AccountPluginData() { * private MyPluginData() {
* super(PluginMain.INSTANCE.loadPluginData(AccountPluginData.class)); * super(PluginMain.INSTANCE);
* INSTANCE = this; * INSTANCE = this;
* } * }
* *
@ -64,57 +66,57 @@ import kotlin.reflect.full.createType
* theList.set(); * theList.set();
* ``` * ```
* *
* **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象.
*
* @see PluginData * @see PluginData
*/ */
public open class JPluginData( public abstract class JAutoSavePluginData : AutoSavePluginData(), PluginConfig {
private val delegate: PluginData
) : PluginData by delegate {
//// region JPluginData_value_primitives CODEGEN //// //// region JPluginData_value_primitives CODEGEN ////
/** /**
* 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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] * 创建一个 [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 //// //// endregion JPluginData_value_primitives CODEGEN ////
@ -134,7 +136,7 @@ public open class JPluginData(
*/ */
@JvmOverloads @JvmOverloads
public fun <T : Any> typedValue(type: KType, default: T? = null): SerializerAwareValue<T> { 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) if (default != null) value.setValueBySerializer(default)
return value 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 package net.mamoe.mirai.console.internal.data
import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.data.MemoryPluginDataStorage
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.PluginDataHolder
import net.mamoe.mirai.console.data.PluginDataStorage
internal class MemoryPluginDataStorageImpl( internal class MemoryPluginDataStorageImpl : PluginDataStorage, MemoryPluginDataStorage,
private val onChanged: MemoryPluginDataStorage.OnChangedCallback
) : PluginDataStorage, MemoryPluginDataStorage,
MutableMap<Class<out PluginData>, PluginData> by mutableMapOf() { MutableMap<Class<out PluginData>, PluginData> by mutableMapOf() {
internal inner class MemoryPluginDataImpl : AbstractPluginData() { @Suppress("UNCHECKED_CAST")
@ConsoleInternalAPI override fun load(holder: PluginDataHolder, instance: PluginData) {
override fun onValueChanged(value: Value<*>) { instance.onStored(holder, this)
onChanged.onChanged(this@MemoryPluginDataStorageImpl, value)
}
override fun setStorage(storage: PluginDataStorage) {
check(storage is MemoryPluginDataStorageImpl) { "storage is not MemoryPluginDataStorageImpl" }
}
} }
@Suppress("UNCHECKED_CAST") override fun store(holder: PluginDataHolder, instance: PluginData) {
override fun <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): T = (synchronized(this) { // no-op
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
}
} }
} }

View File

@ -9,75 +9,59 @@
package net.mamoe.mirai.console.internal.data 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.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI 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 net.mamoe.yamlkt.Yaml
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import kotlin.reflect.KClass
@Suppress("RedundantVisibilityModifier") // might be public in the future @Suppress("RedundantVisibilityModifier") // might be public in the future
internal open class MultiFilePluginDataStorageImpl( internal open class MultiFilePluginDataStorageImpl(
public final override val directoryPath: Path public final override val directoryPath: Path,
private val logger: MiraiLogger = SilentLogger,
) : PluginDataStorage, MultiFilePluginDataStorage { ) : PluginDataStorage, MultiFilePluginDataStorage {
init { init {
directoryPath.mkdir() directoryPath.mkdir()
} }
public override fun <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): T = public override fun load(holder: PluginDataHolder, instance: PluginData) {
with(dataClass.kotlin) { instance.onStored(holder, this)
@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"
)
val file = getPluginDataFile(holder, this) val text = getPluginDataFile(holder, instance).readText()
file.createNewFile() if (text.isNotBlank()) {
check(file.exists() && file.isFile && file.canRead()) { "${file.absolutePath} cannot be read" } Yaml.default.decodeFromString(instance.updaterSerializer, text)
val text = file.readText() }
if (text.isNotBlank()) { logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" }
Yaml.default.decodeFromString(instance.updaterSerializer, file.readText()) }
}
instance
}.also { it.setStorage(this) }
protected open fun getPluginDataFile(holder: PluginDataHolder, clazz: KClass<*>): File = with(clazz) { protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File {
val name = findValueName() val name = instance.saveName
val dir = directoryPath.resolve(holder.name) val dir = directoryPath.resolve(holder.name)
if (dir.isFile) { 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() dir.mkdir()
val file = directoryPath.resolve(name) val file = dir.resolve(name)
if (file.isDirectory) { 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 @ConsoleExperimentalAPI
public override fun store(holder: PluginDataHolder, pluginData: PluginData) { public override fun store(holder: PluginDataHolder, instance: PluginData) {
val file = getPluginDataFile(holder, instance).writeText(Yaml.default.encodeToString(instance.updaterSerializer, Unit))
getPluginDataFile( logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" }
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))
}
} }
} }

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.cast
import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.plugin.* import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.console.plugin.dsecription.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.dsecription.PluginDescription import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.dsecription.PluginKind import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.info
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock 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.Bot
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.* 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.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.util.BotManager import net.mamoe.mirai.console.util.BotManager
import net.mamoe.mirai.contact.User 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() private val managers: MutableMap<Long, MutableSet<Long>> by value()
internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf) internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf)
@ -61,7 +60,4 @@ internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
} }
internal object ConsoleBuiltInPluginDataStorage : internal object ConsoleBuiltInPluginDataStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns { PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns
inline fun <reified T : PluginData> load(): T = load(ConsoleBuiltInPluginDataHolder)
}

View File

@ -14,7 +14,7 @@ package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable 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.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.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File 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.disable
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.register 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 net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import java.io.File 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.MiraiConsole
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl 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.io.File
import java.nio.file.Path import java.nio.file.Path

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 com.vdurmont.semver4j.Semver
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 com.vdurmont.semver4j.Semver
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

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

View File

@ -7,7 +7,13 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 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.PluginFileExtensions
import net.mamoe.mirai.console.plugin.ResourceContainer import net.mamoe.mirai.console.plugin.ResourceContainer
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import kotlin.reflect.KClass
/** /**
@ -51,16 +56,22 @@ public interface JvmPlugin : Plugin, CoroutineScope,
get() = JarPluginLoader get() = JarPluginLoader
/** /**
* 读取一个 [PluginData] 实例 * 重载 [PluginData]
*
* @see reloadPluginData
*/ */
@JvmDefault @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 @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 @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 @JvmSynthetic
public inline fun <reified T : PluginData> JvmPlugin.loadPluginData(): T = this.loadPluginData(T::class) public inline fun JvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() }
/**
* 读取一个 [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)

View File

@ -14,9 +14,9 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import net.mamoe.mirai.console.internal.data.SemverAsStringSerializerLoose import net.mamoe.mirai.console.internal.data.SemverAsStringSerializerLoose
import net.mamoe.mirai.console.plugin.dsecription.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.dsecription.PluginDescription import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.dsecription.PluginKind import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import java.io.File import java.io.File

View File

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

View File

@ -49,8 +49,7 @@ fun initTestEnvironment() {
override suspend fun sendMessage(message: Message) = println(message) override suspend fun sendMessage(message: Message) = println(message)
} }
override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage() override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage()
override val configStorageForJarPluginLoader: PluginDataStorage override val configStorageForJarPluginLoader: PluginDataStorage get() = TODO("Not yet implemented")
get() = TODO("Not yet implemented")
override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage() override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage()
override val consoleInput: ConsoleInput = object : ConsoleInput { override val consoleInput: ConsoleInput = object : ConsoleInput {
override suspend fun requestInput(hint: String): String { override suspend fun requestInput(hint: String): String {

View File

@ -10,6 +10,8 @@
package net.mamoe.mirai.console.data package net.mamoe.mirai.console.data
import kotlinx.serialization.json.Json 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 net.mamoe.mirai.console.util.ConsoleInternalAPI
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -18,18 +20,12 @@ import kotlin.test.assertSame
@OptIn(ConsoleInternalAPI::class) @OptIn(ConsoleInternalAPI::class)
internal class PluginDataTest { internal class PluginDataTest {
class MyPluginData : AbstractPluginData() { object MyPlugin : KotlinPlugin()
class MyPluginData : PluginData by MyPlugin.createPluginData() {
var int by value(1) var int by value(1)
val map by value<MutableMap<String, String>>() val map: MutableMap<String, String> by value()
val map2 by value<MutableMap<String, MutableMap<String, String>>>() val map2: MutableMap<String, MutableMap<String, String>> by value()
@ConsoleInternalAPI
override fun onValueChanged(value: Value<*>) {
}
override fun setStorage(storage: PluginDataStorage) {
}
} }
private val jsonPrettyPrint = Json { prettyPrint = true } private val jsonPrettyPrint = Json { prettyPrint = true }

View File

@ -18,7 +18,7 @@
object Versions { object Versions {
const val core = "1.2.2" 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 consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0" const val consoleTerminal = "0.1.0"
const val consolePure = console const val consolePure = console

View File

@ -10,6 +10,8 @@
package net.mamoe.mirai.console.pure package net.mamoe.mirai.console.pure
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command.Companion.primaryName 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.console.util.requestInput
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import org.jline.reader.UserInterruptException
import java.util.* import java.util.*
import java.util.concurrent.Executors
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.system.exitProcess
@OptIn(ConsoleInternalAPI::class) @OptIn(ConsoleInternalAPI::class)
internal fun startupConsoleThread() { internal fun startupConsoleThread() {
val service = Executors.newSingleThreadExecutor { code -> val mutex = Mutex()
thread(start = false, isDaemon = false, name = "Console Input", block = code::run)
}
val dispatch = service.asCoroutineDispatcher()
ConsoleUtils.miraiLineReader = { hint -> ConsoleUtils.miraiLineReader = { hint ->
withContext(dispatch) { mutex.withLock {
ConsoleUtils.lineReader.readLine( withContext(Dispatchers.IO) {
if (hint.isNotEmpty()) { println("Requesting input")
ConsoleUtils.lineReader.printAbove( ConsoleUtils.lineReader.readLine(
Ansi.ansi() if (hint.isNotEmpty()) {
.fgCyan().a(MiraiConsoleFrontEndPure.sdf.format(Date())).a(" ") ConsoleUtils.lineReader.printAbove(
.fgMagenta().a(hint) Ansi.ansi()
.reset() .fgCyan().a(sdf.format(Date())).a(" ")
.toString() .fgMagenta().a(hint)
) .reset()
"$hint > " .toString()
} else "> " )
) "$hint > "
} else "> "
)
}
} }
} }
MiraiConsole.launch(dispatch) { val consoleLogger = DefaultLogger("console")
val consoleLogger = DefaultLogger("console")
while (isActive) { val inputThread = thread(start = true, isDaemon = false, name = "Console Input") {
try { try {
val next = MiraiConsole.requestInput("").let { runBlocking {
when { while (true) {
it.startsWith(CommandManager.commandPrefix) -> it try {
it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName val next = MiraiConsole.requestInput("").let {
else -> CommandManager.commandPrefix + it 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)
} }
} } catch (e: InterruptedException) {
}.let { consoleJob -> return@thread
MiraiConsole.job.invokeOnCompletion { } catch (e: CancellationException) {
runCatching { return@thread
consoleJob.cancel() } catch (e: UserInterruptException) {
}.exceptionOrNull()?.printStackTrace() MiraiConsole.cancel()
runCatching { return@thread
service.shutdownNow() } catch (e: Throwable) {
}.exceptionOrNull()?.printStackTrace() consoleLogger.error("Unhandled exception", e)
runCatching {
ConsoleUtils.terminal.close()
}.exceptionOrNull()?.printStackTrace()
} }
} }
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.pure.ConsoleInputImpl.requestInput
import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.DefaultLoginSolver import org.fusesource.jansi.Ansi
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.text.SimpleDateFormat
import java.util.* import java.util.*
/** /**
@ -92,3 +91,21 @@ private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
override val vendor: String get() = "Mamoe Technologies" override val vendor: String get() = "Mamoe Technologies"
override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version 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>) { fun main(args: Array<String>) {
startup() startup()
} }
}
internal fun startup() { @Suppress("MemberVisibilityCanBePrivate")
MiraiConsoleImplementationPure().start() internal fun startup(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {
overrideSTD() instance.start()
startupConsoleThread() overrideSTD()
startupConsoleThread()
}
} }
internal fun overrideSTD() { internal fun overrideSTD() {