mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
SettingStorage infrastructure
This commit is contained in:
parent
3d53f7f7bc
commit
f1b0bf7e68
@ -15,6 +15,8 @@ import kotlinx.io.charsets.Charset
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.builtin.JarPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.builtin.JvmPlugin
|
||||
import net.mamoe.mirai.console.setting.SettingStorage
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
@ -41,7 +43,11 @@ object MiraiConsole : CoroutineScope, IMiraiConsole {
|
||||
override val mainLogger: MiraiLogger get() = instance.mainLogger
|
||||
override val coroutineContext: CoroutineContext get() = instance.coroutineContext
|
||||
|
||||
override val builtInPluginLoaders: List<PluginLoader<*, *>> = instance.builtInPluginLoaders
|
||||
override val builtInPluginLoaders: List<PluginLoader<*, *>> get() = instance.builtInPluginLoaders
|
||||
|
||||
@Suppress("CANNOT_WEAKEN_ACCESS_PRIVILEGE")
|
||||
internal override val jvmSettingStorage: SettingStorage
|
||||
get() = instance.jvmSettingStorage
|
||||
|
||||
init {
|
||||
DefaultLogger = { identity -> this.newLogger(identity) }
|
||||
@ -79,6 +85,11 @@ internal interface IMiraiConsole : CoroutineScope {
|
||||
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
|
||||
*/
|
||||
val builtInPluginLoaders: List<PluginLoader<*, *>>
|
||||
|
||||
/**
|
||||
* 内建的供 [JvmPlugin] 使用的 [SettingStorage]
|
||||
*/
|
||||
val jvmSettingStorage: SettingStorage
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,6 +10,7 @@
|
||||
package net.mamoe.mirai.console.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.builtin.JvmPlugin
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 表示一个 mirai-console 插件.
|
||||
@ -22,4 +23,9 @@ interface Plugin {
|
||||
* 所属插件加载器实例
|
||||
*/
|
||||
val loader: PluginLoader<*, *>
|
||||
|
||||
/**
|
||||
* 插件数据目录
|
||||
*/
|
||||
val dataFolder: File
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.builtin.JarPluginLoader
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@ -59,7 +60,7 @@ open class PluginLoadException : RuntimeException {
|
||||
*/
|
||||
interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> {
|
||||
/**
|
||||
* 所支持的插件文件后缀, 不含 '.'. 如 [JarPluginLoader] 为 "jar"
|
||||
* 所支持的插件文件后缀, 含 '.'. 如 [JarPluginLoader] 为 ".jar"
|
||||
*/
|
||||
val fileSuffix: String
|
||||
}
|
||||
@ -70,6 +71,9 @@ abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
|
||||
private fun pluginsFilesSequence(): Sequence<File> =
|
||||
PluginManager.pluginsDir.walk().filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
|
||||
|
||||
/**
|
||||
* 读取扫描到的后缀与 [fileSuffix] 相同的文件中的 [PluginDescription]
|
||||
*/
|
||||
protected abstract fun Sequence<File>.mapToDescription(): List<D>
|
||||
|
||||
final override fun listPlugins(): List<D> = pluginsFilesSequence().mapToDescription()
|
||||
|
@ -24,6 +24,7 @@ inline fun PluginLoader<*, *>.unregister() = PluginManager.unregisterPluginLoade
|
||||
|
||||
object PluginManager {
|
||||
val pluginsDir = File(MiraiConsole.rootDir, "plugins").apply { mkdir() }
|
||||
val pluginsDataFolder = File(MiraiConsole.rootDir, "data").apply { mkdir() }
|
||||
|
||||
private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf()
|
||||
private val loadersLock: ReentrantLock = ReentrantLock()
|
||||
|
@ -14,6 +14,7 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.PluginsLoader
|
||||
import net.mamoe.mirai.console.setting.SettingStorage
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.yamlkt.Yaml
|
||||
import java.io.File
|
||||
@ -24,7 +25,7 @@ import kotlin.reflect.full.createInstance
|
||||
/**
|
||||
* 内建的 Jar (JVM) 插件加载器
|
||||
*/
|
||||
object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>("jar"), CoroutineScope {
|
||||
object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"), CoroutineScope {
|
||||
private val logger: MiraiLogger by lazy {
|
||||
MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
|
||||
}
|
||||
@ -46,6 +47,8 @@ object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescriptio
|
||||
}
|
||||
}
|
||||
|
||||
val settingStorage: SettingStorage = MiraiConsole.jvmSettingStorage
|
||||
|
||||
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description
|
||||
|
||||
override fun Sequence<File>.mapToDescription(): List<JvmPluginDescription> {
|
||||
|
@ -12,18 +12,20 @@
|
||||
package net.mamoe.mirai.console.plugin.builtin
|
||||
|
||||
import kotlinx.atomicfu.locks.withLock
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.setting.*
|
||||
import net.mamoe.mirai.console.utils.JavaPluginScheduler
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
/**
|
||||
@ -40,7 +42,7 @@ interface JvmPlugin : Plugin, CoroutineScope {
|
||||
val description: JvmPluginDescription
|
||||
|
||||
/** 所属插件加载器实例 */
|
||||
override val loader: PluginLoader<*, *> get() = JarPluginLoader
|
||||
override val loader: JarPluginLoader get() = JarPluginLoader
|
||||
|
||||
@JvmDefault
|
||||
fun onLoad() {
|
||||
@ -72,36 +74,46 @@ abstract class JavaPlugin @JvmOverloads constructor(
|
||||
abstract class KotlinPlugin @JvmOverloads constructor(
|
||||
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) {
|
||||
// that's it
|
||||
|
||||
abstract inner class PluginSetting : Setting() {
|
||||
private val track =
|
||||
@Suppress("LeakingThis")
|
||||
loader.settingStorage.trackOn(this)
|
||||
|
||||
init {
|
||||
this@KotlinPlugin.job.invokeOnCompletion {
|
||||
track.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onElementChanged(value: Value<*>) {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val <T> T.job: Job where T : CoroutineScope, T : Plugin get() = this.coroutineContext[Job]!!
|
||||
|
||||
internal sealed class JvmPluginImpl(
|
||||
parentCoroutineContext: CoroutineContext
|
||||
) : JvmPlugin, CoroutineScope {
|
||||
// region JvmPlugin
|
||||
/**
|
||||
* Initialized immediately after construction of [JvmPluginImpl] instance
|
||||
*/
|
||||
@Suppress("PropertyName")
|
||||
internal lateinit var _description: JvmPluginDescription
|
||||
|
||||
// for future use
|
||||
@Suppress("PropertyName")
|
||||
@JvmField
|
||||
internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
|
||||
override val description: JvmPluginDescription get() = _description
|
||||
|
||||
final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) }
|
||||
|
||||
@JvmField
|
||||
internal val coroutineContextInitializer = {
|
||||
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) }
|
||||
.plus(parentCoroutineContext)
|
||||
.plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext
|
||||
}
|
||||
|
||||
private var firstRun = true
|
||||
|
||||
override val dataFolder: File by lazy {
|
||||
File(PluginManager.pluginsDataFolder, description.name).apply { mkdir() }
|
||||
}
|
||||
|
||||
internal fun internalOnDisable() {
|
||||
firstRun = false
|
||||
this.onDisable()
|
||||
@ -116,6 +128,22 @@ internal sealed class JvmPluginImpl(
|
||||
this.onEnable()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region CoroutineScope
|
||||
|
||||
// for future use
|
||||
@Suppress("PropertyName")
|
||||
@JvmField
|
||||
internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
|
||||
@JvmField
|
||||
internal val coroutineContextInitializer = {
|
||||
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) }
|
||||
.plus(parentCoroutineContext)
|
||||
.plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext
|
||||
}
|
||||
|
||||
private fun refreshCoroutineContext(): CoroutineContext {
|
||||
return coroutineContextInitializer().also { _coroutineContext = it }
|
||||
}
|
||||
@ -126,4 +154,5 @@ internal sealed class JvmPluginImpl(
|
||||
get() = _coroutineContext
|
||||
?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() }
|
||||
|
||||
// endregion
|
||||
}
|
@ -13,6 +13,7 @@ package net.mamoe.mirai.console.setting
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import net.mamoe.mirai.console.setting.internal.SettingImpl
|
||||
import net.mamoe.mirai.console.setting.internal.serialNameOrPropertyName
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
@ -33,6 +34,12 @@ typealias Comment = net.mamoe.yamlkt.Comment
|
||||
*/
|
||||
@Suppress("EXPOSED_SUPER_CLASS")
|
||||
abstract class Setting : SettingImpl() {
|
||||
|
||||
data class PropertyInfo(
|
||||
val serialName: String,
|
||||
val annotations: List<Annotation>
|
||||
)
|
||||
|
||||
/**
|
||||
* 这个配置的名称, 仅对于顶层配置有效.
|
||||
*/
|
||||
@ -43,6 +50,16 @@ abstract class Setting : SettingImpl() {
|
||||
?: error("Names should be assigned to anonymous classes manually by overriding serialName")
|
||||
|
||||
|
||||
// for Java only
|
||||
fun <T : Any> addProperty(
|
||||
propertyInfo: PropertyInfo,
|
||||
value: Value<*>
|
||||
): Value<*> {
|
||||
if (built) error("The Setting is already serialized so it's structure is immutable.")
|
||||
valueList.add(value to propertyInfo)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供属性委托, 并添加这个对象的自动保存跟踪.
|
||||
*/
|
||||
@ -52,10 +69,12 @@ abstract class Setting : SettingImpl() {
|
||||
property: KProperty<*>
|
||||
): ReadWriteProperty<Setting, T> {
|
||||
if (built) error("The Setting is already serialized so it's structure is immutable.")
|
||||
valueList.add(this to property)
|
||||
valueList.add(this to PropertyInfo(property.serialNameOrPropertyName, property.annotations))
|
||||
return this
|
||||
}
|
||||
|
||||
abstract override fun onElementChanged(value: Value<*>)
|
||||
|
||||
override fun toString(): String = yamlForToString.stringify(this.serializer, this)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,65 @@
|
||||
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
|
||||
package net.mamoe.mirai.console.setting
|
||||
|
||||
import kotlinx.atomicfu.locks.withLock
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.internal.getChecked
|
||||
import net.mamoe.mirai.console.setting.internal.SettingSerializerMark
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* [Setting] 存储方式
|
||||
*/
|
||||
interface SettingStorage {
|
||||
interface TrackedSetting : Closeable {
|
||||
fun save()
|
||||
fun update()
|
||||
|
||||
override fun close()
|
||||
}
|
||||
|
||||
fun trackOn(setting: Setting): TrackedSetting
|
||||
|
||||
fun saveAll()
|
||||
fun updateAll()
|
||||
}
|
||||
|
||||
class SingleFileSettingStorage(
|
||||
val file: File
|
||||
) : SettingStorage {
|
||||
private val descriptor: MutableList<Setting> = ArrayList()
|
||||
private val updaterSerializer: KSerializer<SettingSerializerMark> = object : KSerializer<SettingSerializerMark> {
|
||||
override val descriptor: SerialDescriptor = SerialDescriptor("SingleFileSettingStorage") {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): SettingSerializerMark {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: SettingSerializerMark) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
require(file.isFile) { "file $file is not a file" }
|
||||
require(file.canRead()) { "file $file is not readable" }
|
||||
}
|
||||
|
||||
override fun trackOn(setting: Setting): SettingStorage.TrackedSetting {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun saveAll() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun updateAll() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -13,13 +13,19 @@ package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.plugin.builtin.KotlinPlugin
|
||||
import net.mamoe.mirai.console.setting.value
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
val plugin: KotlinPlugin = object : KotlinPlugin() {
|
||||
val plugin = MyPlugin()
|
||||
|
||||
class MyPlugin : KotlinPlugin() {
|
||||
|
||||
inner class MySetting : PluginSetting() {
|
||||
val int by value(1)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user