mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
API stabilization: rearrange implementations
This commit is contained in:
parent
ead891e223
commit
4d7826f3d9
@ -42,6 +42,7 @@ kotlin {
|
|||||||
|
|
||||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||||
|
useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||||
useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
|
useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
|
||||||
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||||
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION")
|
@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "unused")
|
||||||
@file:OptIn(ConsoleInternalAPI::class)
|
@file:OptIn(ConsoleInternalAPI::class)
|
||||||
|
|
||||||
package net.mamoe.mirai.console
|
package net.mamoe.mirai.console
|
||||||
@ -77,32 +77,22 @@ public interface MiraiConsole : CoroutineScope {
|
|||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public fun newLogger(identity: String?): MiraiLogger
|
public fun newLogger(identity: String?): MiraiLogger
|
||||||
|
|
||||||
public companion object INSTANCE : MiraiConsole by MiraiConsoleInternal
|
public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge {
|
||||||
}
|
|
||||||
|
|
||||||
public class IllegalMiraiConsoleImplementationError(
|
|
||||||
override val message: String?
|
|
||||||
) : Error()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 [MiraiConsole] 的 [Job]
|
* 获取 [MiraiConsole] 的 [Job]
|
||||||
*/
|
*/ // MiraiConsole.INSTANCE.getJob()
|
||||||
public val MiraiConsole.job: Job
|
public val job: Job
|
||||||
get() = this.coroutineContext[Job] ?: error("Internal error: Job not found in MiraiConsole.coroutineContext")
|
get() = MiraiConsole.coroutineContext[Job]
|
||||||
|
?: error("Internal error: Job not found in MiraiConsole.coroutineContext")
|
||||||
//// internal
|
|
||||||
|
|
||||||
|
|
||||||
internal object MiraiConsoleInitializer {
|
|
||||||
internal lateinit var instance: IMiraiConsole
|
|
||||||
|
|
||||||
/** 由前端调用 */
|
|
||||||
internal fun init(instance: IMiraiConsole) {
|
|
||||||
this.instance = instance
|
|
||||||
MiraiConsoleInternal.doStart()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class IllegalMiraiConsoleImplementationError @JvmOverloads constructor(
|
||||||
|
public override val message: String? = null,
|
||||||
|
public override val cause: Throwable? = null
|
||||||
|
) : Error()
|
||||||
|
|
||||||
|
|
||||||
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
|
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val buildDate: Date = Date(1595136353901L) // 2020-07-19 13:25:53
|
val buildDate: Date = Date(1595136353901L) // 2020-07-19 13:25:53
|
||||||
@ -110,12 +100,13 @@ internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mira
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mirai 控制台实例.
|
* [MiraiConsole] 公开 API 与前端实现的连接桥.
|
||||||
*/
|
*/
|
||||||
internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConsole {
|
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
|
||||||
|
MiraiConsole {
|
||||||
override val pluginCenter: PluginCenter get() = CuiPluginCenter
|
override val pluginCenter: PluginCenter get() = CuiPluginCenter
|
||||||
|
|
||||||
private val instance: IMiraiConsole get() = MiraiConsoleInitializer.instance
|
private val instance: MiraiConsoleImplementation get() = MiraiConsoleImplementation.instance
|
||||||
override val buildDate: Date get() = MiraiConsoleBuildConstants.buildDate
|
override val buildDate: Date get() = MiraiConsoleBuildConstants.buildDate
|
||||||
override val version: String get() = MiraiConsoleBuildConstants.version
|
override val version: String get() = MiraiConsoleBuildConstants.version
|
||||||
override val rootDir: File get() = instance.rootDir
|
override val rootDir: File get() = instance.rootDir
|
||||||
@ -147,7 +138,7 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso
|
|||||||
if (coroutineContext[Job] == null) {
|
if (coroutineContext[Job] == null) {
|
||||||
throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
|
throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
|
||||||
}
|
}
|
||||||
job.invokeOnCompletion {
|
MiraiConsole.job.invokeOnCompletion {
|
||||||
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
|
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,35 +156,6 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 前端使用
|
|
||||||
internal interface IMiraiConsole : CoroutineScope {
|
|
||||||
/**
|
|
||||||
* Console 运行路径
|
|
||||||
*/
|
|
||||||
val rootDir: File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Console 前端接口
|
|
||||||
*/
|
|
||||||
val frontEnd: MiraiConsoleFrontEnd
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 与前端交互所使用的 Logger
|
|
||||||
*/
|
|
||||||
val mainLogger: MiraiLogger
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
|
|
||||||
*/
|
|
||||||
val builtInPluginLoaders: List<PluginLoader<*, *>>
|
|
||||||
|
|
||||||
val consoleCommandSender: ConsoleCommandSender
|
|
||||||
|
|
||||||
val settingStorageForJarPluginLoader: SettingStorage
|
|
||||||
val settingStorageForBuiltIns: SettingStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Included in kotlin stdlib 1.4
|
* Included in kotlin stdlib 1.4
|
||||||
*/
|
*/
|
||||||
|
@ -10,13 +10,17 @@
|
|||||||
package net.mamoe.mirai.console
|
package net.mamoe.mirai.console
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.utils.LoginSolver
|
import net.mamoe.mirai.utils.LoginSolver
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 只需要实现一个这个传入 MiraiConsole 就可以绑定 UI 层与 Console 层
|
* 只需要实现一个这个传入 MiraiConsole 就可以绑定 UI 层与 Console 层
|
||||||
|
*
|
||||||
* 需要保证线程安全
|
* 需要保证线程安全
|
||||||
*/
|
*/
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
|
@ConsoleFrontEndImplementation
|
||||||
public interface MiraiConsoleFrontEnd {
|
public interface MiraiConsoleFrontEnd {
|
||||||
/**
|
/**
|
||||||
* 名称
|
* 名称
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import kotlinx.atomicfu.locks.withLock
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||||
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||||
|
import net.mamoe.mirai.console.setting.SettingStorage
|
||||||
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.annotation.AnnotationTarget.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记一个仅用于 [MiraiConsole] 前端实现的 API. 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用.
|
||||||
|
*
|
||||||
|
* 前端实现时
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||||
|
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||||
|
@MustBeDocumented
|
||||||
|
public annotation class ConsoleFrontEndImplementation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MiraiConsole] 前端实现, 需低啊用
|
||||||
|
*/
|
||||||
|
@ConsoleFrontEndImplementation
|
||||||
|
public interface MiraiConsoleImplementation : CoroutineScope {
|
||||||
|
/**
|
||||||
|
* Console 运行路径
|
||||||
|
*/
|
||||||
|
public val rootDir: File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console 前端接口
|
||||||
|
*/
|
||||||
|
public val frontEnd: MiraiConsoleFrontEnd
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与前端交互所使用的 Logger
|
||||||
|
*/
|
||||||
|
public val mainLogger: MiraiLogger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
|
||||||
|
*/
|
||||||
|
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
||||||
|
|
||||||
|
public val consoleCommandSender: ConsoleCommandSender
|
||||||
|
|
||||||
|
public val settingStorageForJarPluginLoader: SettingStorage
|
||||||
|
public val settingStorageForBuiltIns: SettingStorage
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
internal lateinit var instance: MiraiConsoleImplementation
|
||||||
|
private val initLock = ReentrantLock()
|
||||||
|
|
||||||
|
/** 由前端调用, 初始化 [MiraiConsole] 实例, 并 */
|
||||||
|
@JvmStatic
|
||||||
|
public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock {
|
||||||
|
this@Companion.instance = this
|
||||||
|
MiraiConsoleImplementationBridge.doStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,6 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.alsoLogin
|
import net.mamoe.mirai.alsoLogin
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.job
|
|
||||||
import net.mamoe.mirai.console.stacktraceString
|
import net.mamoe.mirai.console.stacktraceString
|
||||||
import net.mamoe.mirai.event.selectMessagesUnit
|
import net.mamoe.mirai.event.selectMessagesUnit
|
||||||
import net.mamoe.mirai.utils.DirectoryLogger
|
import net.mamoe.mirai.utils.DirectoryLogger
|
||||||
|
@ -13,7 +13,7 @@ package net.mamoe.mirai.console.command
|
|||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.console.MiraiConsoleInternal
|
import net.mamoe.mirai.console.MiraiConsoleImplementationBridge
|
||||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.console.utils.JavaFriendlyAPI
|
import net.mamoe.mirai.console.utils.JavaFriendlyAPI
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
@ -68,7 +68,7 @@ public abstract class ConsoleCommandSender internal constructor() : CommandSende
|
|||||||
public final override val bot: Nothing? get() = null
|
public final override val bot: Nothing? get() = null
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
internal val instance get() = MiraiConsoleInternal.consoleCommandSender
|
internal val instance get() = MiraiConsoleImplementationBridge.consoleCommandSender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ package net.mamoe.mirai.console.command.internal
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.*
|
||||||
import net.mamoe.mirai.console.job
|
|
||||||
import net.mamoe.mirai.contact.Group
|
import net.mamoe.mirai.contact.Group
|
||||||
import net.mamoe.mirai.contact.Member
|
import net.mamoe.mirai.contact.Member
|
||||||
import net.mamoe.mirai.event.Listener
|
import net.mamoe.mirai.event.Listener
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.plugin
|
package net.mamoe.mirai.console.plugin
|
||||||
|
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
@ -29,6 +31,23 @@ public interface Plugin {
|
|||||||
public val loader: PluginLoader<*, *>
|
public val loader: PluginLoader<*, *>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用这个插件
|
||||||
|
*
|
||||||
|
* @see PluginLoader.disable
|
||||||
|
*/
|
||||||
|
public fun Plugin.disable(): Unit = safeLoader.disable(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用这个插件
|
||||||
|
*
|
||||||
|
* @see PluginLoader.enable
|
||||||
|
*/
|
||||||
|
public fun Plugin.enable(): Unit = safeLoader.enable(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经过泛型类型转换的 [PluginLoader]
|
||||||
|
*/
|
||||||
@get:JvmSynthetic
|
@get:JvmSynthetic
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
|
public inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
package net.mamoe.mirai.console.plugin.internal
|
||||||
|
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
|
import net.mamoe.mirai.console.MiraiConsoleImplementationBridge
|
||||||
|
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
|
||||||
|
import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||||
|
import net.mamoe.mirai.console.setting.SettingStorage
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
|
import net.mamoe.yamlkt.Yaml
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.reflect.full.createInstance
|
||||||
|
|
||||||
|
internal object JarPluginLoaderImpl :
|
||||||
|
AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
|
||||||
|
CoroutineScope,
|
||||||
|
JarPluginLoader {
|
||||||
|
|
||||||
|
private val logger: MiraiLogger = MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
|
||||||
|
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
|
override val settingStorage: SettingStorage
|
||||||
|
get() = MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext =
|
||||||
|
MiraiConsole.coroutineContext +
|
||||||
|
SupervisorJob(MiraiConsole.coroutineContext[Job]) +
|
||||||
|
CoroutineExceptionHandler { _, throwable ->
|
||||||
|
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
|
||||||
|
|
||||||
|
init { // delayed
|
||||||
|
coroutineContext[Job]!!.invokeOnCompletion {
|
||||||
|
classLoader.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
|
||||||
|
override val JvmPlugin.description: JvmPluginDescription
|
||||||
|
get() = this.description
|
||||||
|
|
||||||
|
override fun Sequence<File>.mapToDescription(): List<JvmPluginDescription> {
|
||||||
|
return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() }
|
||||||
|
.mapNotNull { (file, url) ->
|
||||||
|
kotlin.runCatching {
|
||||||
|
url.readText()
|
||||||
|
}.fold(
|
||||||
|
onSuccess = { yaml ->
|
||||||
|
Yaml.nonStrict.parse(JvmPluginDescription.serializer(), yaml)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logger.error("Cannot load plugin file ${file.name}", it)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)?.also { it._file = file }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("RemoveExplicitTypeArguments") // until Kotlin 1.4 NI
|
||||||
|
@Throws(PluginLoadException::class)
|
||||||
|
override fun load(description: JvmPluginDescription): JvmPlugin =
|
||||||
|
description.runCatching<JvmPluginDescription, JvmPlugin> {
|
||||||
|
ensureActive()
|
||||||
|
val main = classLoader.loadPluginMainClassByJarFile(
|
||||||
|
pluginName = name,
|
||||||
|
mainClass = mainClassName,
|
||||||
|
jarFile = file
|
||||||
|
).kotlin.run {
|
||||||
|
objectInstance
|
||||||
|
?: kotlin.runCatching { createInstance() }.getOrNull()
|
||||||
|
?: (java.constructors + java.declaredConstructors)
|
||||||
|
.firstOrNull { it.parameterCount == 0 }
|
||||||
|
?.apply { kotlin.runCatching { isAccessible = true } }
|
||||||
|
?.newInstance()
|
||||||
|
} ?: error("No Kotlin object or public no-arg constructor found")
|
||||||
|
|
||||||
|
check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" }
|
||||||
|
|
||||||
|
if (main is JvmPluginInternal) {
|
||||||
|
main._description = description
|
||||||
|
main.internalOnLoad()
|
||||||
|
} else main.onLoad()
|
||||||
|
main
|
||||||
|
}.getOrElse<JvmPlugin, JvmPlugin> {
|
||||||
|
throw PluginLoadException("Exception while loading ${description.name}", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enable(plugin: JvmPlugin) {
|
||||||
|
ensureActive()
|
||||||
|
if (plugin is JvmPluginInternal) {
|
||||||
|
plugin.internalOnEnable()
|
||||||
|
} else plugin.onEnable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disable(plugin: JvmPlugin) {
|
||||||
|
if (plugin is JvmPluginInternal) {
|
||||||
|
plugin.internalOnDisable()
|
||||||
|
} else plugin.onDisable()
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ import net.mamoe.mirai.console.plugin.Plugin
|
|||||||
import net.mamoe.mirai.console.plugin.PluginManager
|
import net.mamoe.mirai.console.plugin.PluginManager
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||||
import net.mamoe.mirai.console.utils.asResourceContainer
|
import net.mamoe.mirai.console.utils.ResourceContainer.Companion.asResourceContainer
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
package net.mamoe.mirai.console.plugin.jvm
|
package net.mamoe.mirai.console.plugin.jvm
|
||||||
|
|
||||||
import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
|
import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
|
||||||
import net.mamoe.mirai.console.setting.Setting
|
import net.mamoe.mirai.utils.minutesToSeconds
|
||||||
|
import net.mamoe.mirai.utils.secondsToMillis
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
@ -27,5 +28,5 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor(
|
|||||||
) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) {
|
) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) {
|
||||||
public final override val name: String get() = this.description.name
|
public final override val name: String get() = this.description.name
|
||||||
|
|
||||||
public override fun <T : Setting> getSetting(clazz: Class<T>): T = loader.settingStorage.load(this, clazz)
|
public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToSeconds
|
||||||
}
|
}
|
@ -9,120 +9,21 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.plugin.jvm
|
package net.mamoe.mirai.console.plugin.jvm
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
|
||||||
import net.mamoe.mirai.console.MiraiConsoleInternal
|
|
||||||
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
|
|
||||||
import net.mamoe.mirai.console.plugin.FilePluginLoader
|
import net.mamoe.mirai.console.plugin.FilePluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoadException
|
import net.mamoe.mirai.console.plugin.internal.JarPluginLoaderImpl
|
||||||
import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
|
|
||||||
import net.mamoe.mirai.console.plugin.internal.PluginsLoader
|
|
||||||
import net.mamoe.mirai.console.setting.SettingStorage
|
import net.mamoe.mirai.console.setting.SettingStorage
|
||||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
|
||||||
import net.mamoe.yamlkt.Yaml
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URI
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.reflect.full.createInstance
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内建的 Jar (JVM) 插件加载器
|
* 内建的 Jar (JVM) 插件加载器
|
||||||
*/
|
*/
|
||||||
public interface JarPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
|
public interface JarPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
|
||||||
|
/**
|
||||||
|
* [JvmPlugin.loadSetting] 默认使用的实例
|
||||||
|
*/
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public val settingStorage: SettingStorage
|
public val settingStorage: SettingStorage
|
||||||
|
|
||||||
public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl
|
public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal object JarPluginLoaderImpl :
|
|
||||||
AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
|
|
||||||
CoroutineScope,
|
|
||||||
JarPluginLoader {
|
|
||||||
|
|
||||||
private val logger: MiraiLogger = MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
|
|
||||||
|
|
||||||
@ConsoleExperimentalAPI
|
|
||||||
override val settingStorage: SettingStorage
|
|
||||||
get() = MiraiConsoleInternal.settingStorageForJarPluginLoader
|
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext =
|
|
||||||
MiraiConsole.coroutineContext +
|
|
||||||
SupervisorJob(MiraiConsole.coroutineContext[Job]) +
|
|
||||||
CoroutineExceptionHandler { _, throwable ->
|
|
||||||
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
|
|
||||||
|
|
||||||
init { // delayed
|
|
||||||
coroutineContext[Job]!!.invokeOnCompletion {
|
|
||||||
classLoader.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
|
|
||||||
override val JvmPlugin.description: JvmPluginDescription
|
|
||||||
get() = this.description
|
|
||||||
|
|
||||||
override fun Sequence<File>.mapToDescription(): List<JvmPluginDescription> {
|
|
||||||
return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() }
|
|
||||||
.mapNotNull { (file, url) ->
|
|
||||||
kotlin.runCatching {
|
|
||||||
url.readText()
|
|
||||||
}.fold(
|
|
||||||
onSuccess = { yaml ->
|
|
||||||
Yaml.nonStrict.parse(JvmPluginDescription.serializer(), yaml)
|
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
logger.error("Cannot load plugin file ${file.name}", it)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
)?.also { it._file = file }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("RemoveExplicitTypeArguments") // until Kotlin 1.4 NI
|
|
||||||
@Throws(PluginLoadException::class)
|
|
||||||
override fun load(description: JvmPluginDescription): JvmPlugin =
|
|
||||||
description.runCatching<JvmPluginDescription, JvmPlugin> {
|
|
||||||
ensureActive()
|
|
||||||
val main = classLoader.loadPluginMainClassByJarFile(
|
|
||||||
pluginName = name,
|
|
||||||
mainClass = mainClassName,
|
|
||||||
jarFile = file
|
|
||||||
).kotlin.run {
|
|
||||||
objectInstance
|
|
||||||
?: kotlin.runCatching { createInstance() }.getOrNull()
|
|
||||||
?: (java.constructors + java.declaredConstructors)
|
|
||||||
.firstOrNull { it.parameterCount == 0 }
|
|
||||||
?.apply { kotlin.runCatching { isAccessible = true } }
|
|
||||||
?.newInstance()
|
|
||||||
} ?: error("No Kotlin object or public no-arg constructor found")
|
|
||||||
|
|
||||||
check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" }
|
|
||||||
|
|
||||||
if (main is JvmPluginInternal) {
|
|
||||||
main._description = description
|
|
||||||
main.internalOnLoad()
|
|
||||||
} else main.onLoad()
|
|
||||||
main
|
|
||||||
}.getOrElse<JvmPlugin, JvmPlugin> {
|
|
||||||
throw PluginLoadException("Exception while loading ${description.name}", it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun enable(plugin: JvmPlugin) {
|
|
||||||
ensureActive()
|
|
||||||
if (plugin is JvmPluginInternal) {
|
|
||||||
plugin.internalOnEnable()
|
|
||||||
} else plugin.onEnable()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun disable(plugin: JvmPlugin) {
|
|
||||||
if (plugin is JvmPluginInternal) {
|
|
||||||
plugin.internalOnDisable()
|
|
||||||
} else plugin.onDisable()
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,13 +41,15 @@ public interface JvmPlugin : Plugin, CoroutineScope,
|
|||||||
public val description: JvmPluginDescription
|
public val description: JvmPluginDescription
|
||||||
|
|
||||||
/** 所属插件加载器实例 */
|
/** 所属插件加载器实例 */
|
||||||
public override val loader: JarPluginLoader get() = JarPluginLoader
|
@JvmDefault
|
||||||
|
public override val loader: JarPluginLoader
|
||||||
|
get() = JarPluginLoader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取一个 [Setting] 实例
|
* 获取一个 [Setting] 实例
|
||||||
*/
|
*/
|
||||||
public fun <T : Setting> getSetting(clazz: Class<T>): T
|
@JvmDefault
|
||||||
|
public fun <T : Setting> loadSetting(clazz: Class<T>): T = loader.settingStorage.load(this, clazz)
|
||||||
|
|
||||||
// TODO: 2020/7/11 document onLoad, onEnable, onDisable
|
// TODO: 2020/7/11 document onLoad, onEnable, onDisable
|
||||||
@JvmDefault
|
@JvmDefault
|
||||||
@ -64,7 +66,7 @@ public interface JvmPlugin : Plugin, CoroutineScope,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
public inline fun <T : Setting> JvmPlugin.getSetting(clazz: KClass<T>): T = this.getSetting(clazz.java)
|
public inline fun <T : Setting> JvmPlugin.loadSetting(clazz: KClass<T>): T = this.loadSetting(clazz.java)
|
||||||
|
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
public inline fun <reified T : Setting> JvmPlugin.getSetting(): T = this.getSetting(T::class)
|
public inline fun <reified T : Setting> JvmPlugin.loadSetting(): T = this.loadSetting(T::class)
|
@ -12,7 +12,11 @@
|
|||||||
package net.mamoe.mirai.console.setting
|
package net.mamoe.mirai.console.setting
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.loadSetting
|
||||||
import net.mamoe.mirai.console.setting.internal.*
|
import net.mamoe.mirai.console.setting.internal.*
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
||||||
import kotlin.internal.LowPriorityInOverloadResolution
|
import kotlin.internal.LowPriorityInOverloadResolution
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
@ -23,16 +27,31 @@ import kotlin.reflect.KType
|
|||||||
*
|
*
|
||||||
* 例:
|
* 例:
|
||||||
* ```
|
* ```
|
||||||
* class MySetting : Setting() {
|
* @SerialName("accounts")
|
||||||
*
|
* object AccountSettings : Setting by ... {
|
||||||
|
* @SerialName("info")
|
||||||
|
* val map: Map<String, String> by value("a" to "b")
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
|
*
|
||||||
|
* 将被保存为配置 (YAML 作为示例):
|
||||||
|
* ```yaml
|
||||||
|
* accounts:
|
||||||
|
* info:
|
||||||
|
* a: b
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
// TODO: 2020/6/26 document
|
|
||||||
public typealias SerialName = kotlinx.serialization.SerialName
|
public typealias SerialName = kotlinx.serialization.SerialName
|
||||||
|
|
||||||
// TODO: 2020/6/26 document
|
/**
|
||||||
|
* [Setting] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动.
|
||||||
|
*
|
||||||
|
* @see Setting
|
||||||
|
*/
|
||||||
public abstract class AbstractSetting : Setting, SettingImpl() {
|
public abstract class AbstractSetting : Setting, SettingImpl() {
|
||||||
|
/**
|
||||||
|
* 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪.
|
||||||
|
*/
|
||||||
public final override operator fun <T> SerializerAwareValue<T>.provideDelegate(
|
public final override operator fun <T> SerializerAwareValue<T>.provideDelegate(
|
||||||
thisRef: Any?,
|
thisRef: Any?,
|
||||||
property: KProperty<*>
|
property: KProperty<*>
|
||||||
@ -42,21 +61,56 @@ public abstract class AbstractSetting : Setting, SettingImpl() {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public final override val updaterSerializer: KSerializer<Unit> get() = super.updaterSerializer
|
/**
|
||||||
|
* 值更新序列化器. 仅供内部使用
|
||||||
|
*/
|
||||||
|
@ConsoleInternalAPI
|
||||||
|
public final override val updaterSerializer: KSerializer<Unit>
|
||||||
|
get() = super.updaterSerializer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当所属于这个 [Setting] 的 [Value] 的 [值][Value.value] 被修改时被调用.
|
||||||
|
*/
|
||||||
|
public abstract override fun onValueChanged(value: Value<*>)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 2020/6/26 document
|
/**
|
||||||
|
* 一个配置对象. 可包含对多个 [Value] 的值变更的跟踪.
|
||||||
|
*
|
||||||
|
* 在 [JvmPlugin] 的实现方式:
|
||||||
|
* ```
|
||||||
|
* object PluginMain : KotlinPlugin()
|
||||||
|
*
|
||||||
|
* object AccountSettings : Setting by PluginMain.getSetting() {
|
||||||
|
* val map: Map<String, String> by value("a" to "b")
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see JvmPlugin.loadSetting 通过 [JvmPlugin] 获取指定 [Setting] 实例.
|
||||||
|
*/
|
||||||
public interface Setting {
|
public interface Setting {
|
||||||
// TODO: 2020/6/26 document
|
/**
|
||||||
|
* 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪.
|
||||||
|
*/
|
||||||
public operator fun <T> SerializerAwareValue<T>.provideDelegate(
|
public operator fun <T> SerializerAwareValue<T>.provideDelegate(
|
||||||
thisRef: Any?,
|
thisRef: Any?,
|
||||||
property: KProperty<*>
|
property: KProperty<*>
|
||||||
): SerializerAwareValue<T>
|
): SerializerAwareValue<T>
|
||||||
|
|
||||||
// TODO: 2020/6/26 document
|
/**
|
||||||
|
* 值更新序列化器. 仅供内部使用
|
||||||
|
*/
|
||||||
public val updaterSerializer: KSerializer<Unit>
|
public val updaterSerializer: KSerializer<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当所属于这个 [Setting] 的 [Value] 的 [值][Value.value] 被修改时被调用.
|
||||||
|
*/
|
||||||
public fun onValueChanged(value: Value<*>)
|
public fun onValueChanged(value: Value<*>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当这个 [Setting] 被放入一个 [SettingStorage] 时调用
|
||||||
|
*/
|
||||||
|
public fun setStorage(storage: SettingStorage)
|
||||||
}
|
}
|
||||||
|
|
||||||
//// region Setting_value_primitives CODEGEN ////
|
//// region Setting_value_primitives CODEGEN ////
|
||||||
@ -101,6 +155,7 @@ public inline fun <reified T> Setting.value(): SerializerAwareValue<T> = value(T
|
|||||||
* Creates a [Value] with specified [KType], and set default value.
|
* Creates a [Value] with specified [KType], and set default value.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
public fun <T> Setting.valueFromKType(type: KType, default: T): SerializerAwareValue<T> =
|
public fun <T> Setting.valueFromKType(type: KType, default: T): SerializerAwareValue<T> =
|
||||||
(valueFromKTypeImpl(type) as SerializerAwareValue<Any?>).apply { this.value = default } as SerializerAwareValue<T>
|
(valueFromKTypeImpl(type) as SerializerAwareValue<Any?>).apply { this.value = default } as SerializerAwareValue<T>
|
||||||
|
|
||||||
|
@ -1,34 +1,31 @@
|
|||||||
@file:Suppress("NOTHING_TO_INLINE")
|
@file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST", "unused")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.setting
|
package net.mamoe.mirai.console.setting
|
||||||
|
|
||||||
import kotlinx.atomicfu.atomic
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||||
import kotlinx.coroutines.launch
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip
|
import net.mamoe.mirai.console.setting.SettingStorage.Companion.load
|
||||||
import net.mamoe.mirai.console.plugin.internal.updateWhen
|
import net.mamoe.mirai.console.setting.internal.*
|
||||||
import net.mamoe.mirai.console.plugin.jvm.getSetting
|
|
||||||
import net.mamoe.mirai.console.setting.AutoSaveSettingHolder.AutoSaveSetting
|
|
||||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
|
||||||
import net.mamoe.mirai.utils.currentTimeMillis
|
|
||||||
import net.mamoe.mirai.utils.minutesToSeconds
|
|
||||||
import net.mamoe.mirai.utils.secondsToMillis
|
|
||||||
import net.mamoe.yamlkt.Yaml
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KParameter
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.createType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Setting] 存储容器
|
* [Setting] 存储容器.
|
||||||
|
*
|
||||||
|
* 此为较低层的 API, 一般插件开发者不会接触.
|
||||||
|
*
|
||||||
|
* [JarPluginLoader] 实现一个 [SettingStorage], 用于管理所有 [JvmPlugin] 的 [Setting] 实例.
|
||||||
*
|
*
|
||||||
* @see SettingHolder
|
* @see SettingHolder
|
||||||
|
* @see JarPluginLoader.settingStorage
|
||||||
*/
|
*/
|
||||||
public interface SettingStorage {
|
public interface SettingStorage {
|
||||||
/**
|
/**
|
||||||
* 读取一个实例
|
* 读取一个实例. 在 [T] 实例创建后 [设置 [SettingStorage]][Setting.setStorage]
|
||||||
*/
|
*/
|
||||||
public fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T
|
public fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T
|
||||||
|
|
||||||
@ -36,39 +33,82 @@ public interface SettingStorage {
|
|||||||
* 保存一个实例
|
* 保存一个实例
|
||||||
*/
|
*/
|
||||||
public fun store(holder: SettingHolder, setting: Setting)
|
public fun store(holder: SettingHolder, setting: Setting)
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* 读取一个实例. 在 [T] 实例创建后 [设置 [SettingStorage]][Setting.setStorage]
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun <T : Setting> SettingStorage.load(holder: SettingHolder, settingClass: KClass<T>): T =
|
||||||
|
this.load(holder, settingClass.java)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取一个实例. 在 [T] 实例创建后 [设置 [SettingStorage]][Setting.setStorage]
|
||||||
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
|
public inline fun <reified T : Setting> SettingStorage.load(holder: SettingHolder): T =
|
||||||
|
this.load(holder, T::class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在内存存储所有 [Setting] 实例的 [SettingStorage]. 在内存数据丢失后相关 [Setting] 实例也会丢失.
|
||||||
|
*/
|
||||||
|
public interface MemorySettingStorage : SettingStorage, Map<Class<out Setting>, Setting> {
|
||||||
|
/**
|
||||||
|
* 当任一 [Setting] 实例拥有的 [Value] 的值被改变后调用的回调函数.
|
||||||
|
*/
|
||||||
|
public /* fun */ interface OnChangedCallback { // TODO: 2020/7/24 make `fun` in 1.4
|
||||||
|
public fun onChanged(storage: MemorySettingStorage, value: Value<*>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无任何操作的 [OnChangedCallback]
|
||||||
|
* @see OnChangedCallback
|
||||||
|
*/
|
||||||
|
public object NoOp : OnChangedCallback {
|
||||||
|
public override fun onChanged(storage: MemorySettingStorage, value: Value<*>) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 2020/7/11 document
|
|
||||||
public interface MemorySettingStorage : SettingStorage {
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
/**
|
||||||
|
* 创建一个 [MemorySettingStorage] 实例.
|
||||||
|
*
|
||||||
|
* @param onChanged 当任一 [Setting] 实例拥有的 [Value] 的值被改变后调用的回调函数.
|
||||||
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmName("create")
|
@JvmName("create")
|
||||||
public operator fun invoke(): MemorySettingStorage = MemorySettingStorageImpl()
|
@JvmOverloads
|
||||||
|
public operator fun invoke(onChanged: OnChangedCallback = OnChangedCallback.NoOp): MemorySettingStorage =
|
||||||
|
MemorySettingStorageImpl(onChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 2020/7/11 document
|
/**
|
||||||
|
* 在内存存储所有 [Setting] 实例的 [SettingStorage].
|
||||||
|
*/
|
||||||
public interface MultiFileSettingStorage : SettingStorage {
|
public interface MultiFileSettingStorage : SettingStorage {
|
||||||
|
/**
|
||||||
|
* 存放 [Setting] 的目录.
|
||||||
|
*/
|
||||||
public val directory: File
|
public val directory: File
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
/**
|
||||||
|
* 创建一个 [MultiFileSettingStorage] 实例.
|
||||||
|
*
|
||||||
|
* @see directory 存放 [Setting] 的目录.
|
||||||
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmName("create")
|
@JvmName("create")
|
||||||
public operator fun invoke(directory: File): MultiFileSettingStorage = MultiFileSettingStorageImpl(directory)
|
public operator fun invoke(directory: File): MultiFileSettingStorage = MultiFileSettingStorageImpl(directory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: 2020/7/11 here or companion?
|
|
||||||
public inline fun <T : Setting> SettingStorage.load(holder: SettingHolder, settingClass: KClass<T>): T =
|
|
||||||
this.load(holder, settingClass.java)
|
|
||||||
|
|
||||||
// TODO: 2020/7/11 here or companion?
|
|
||||||
public inline fun <reified T : Setting> SettingStorage.load(holder: SettingHolder): T =
|
|
||||||
this.load(holder, T::class)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可以持有相关 [Setting] 的对象.
|
* 可以持有相关 [Setting] 实例的对象, 作为 [Setting] 实例的拥有者.
|
||||||
*
|
*
|
||||||
* @see SettingStorage.load
|
* @see SettingStorage.load
|
||||||
* @see SettingStorage.store
|
* @see SettingStorage.store
|
||||||
@ -80,6 +120,28 @@ public interface SettingHolder {
|
|||||||
* 保存时使用的分类名
|
* 保存时使用的分类名
|
||||||
*/
|
*/
|
||||||
public val name: String
|
public val name: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个 [Setting] 实例.
|
||||||
|
*
|
||||||
|
* @see Companion.newSettingInstance
|
||||||
|
* @see KClass.createType
|
||||||
|
*/
|
||||||
|
@JvmDefault
|
||||||
|
public fun <T : Setting> newSettingInstance(type: KType): T =
|
||||||
|
newSettingInstanceUsingReflection<Setting>(type) as T
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* 创建一个 [Setting] 实例.
|
||||||
|
*
|
||||||
|
* @see SettingHolder.newSettingInstance
|
||||||
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
|
public inline fun <reified T : Setting> SettingHolder.newSettingInstance(): T {
|
||||||
|
return this.newSettingInstance(typeOf0<T>())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,158 +156,23 @@ public interface AutoSaveSettingHolder : SettingHolder, CoroutineScope {
|
|||||||
* - 区间的左端点为最小间隔, 一个 [Value] 被修改后, 若此时间段后无其他修改, 将触发自动保存; 若有, 将重新开始计时.
|
* - 区间的左端点为最小间隔, 一个 [Value] 被修改后, 若此时间段后无其他修改, 将触发自动保存; 若有, 将重新开始计时.
|
||||||
* - 区间的右端点为最大间隔, 一个 [Value] 被修改后, 最多不超过这个时间段后就会被保存.
|
* - 区间的右端点为最大间隔, 一个 [Value] 被修改后, 最多不超过这个时间段后就会被保存.
|
||||||
*
|
*
|
||||||
* 若 [coroutineContext] 含有 [Job], 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存.
|
* 若 [AutoSaveSettingHolder.coroutineContext] 含有 [Job],
|
||||||
|
* 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存.
|
||||||
*
|
*
|
||||||
* @see LongRange Java 用户使用 [LongRange] 的构造器创建
|
* @see LongRange Java 用户使用 [LongRange] 的构造器创建
|
||||||
* @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, 如 `3000..50000`
|
* @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, 如 `3000..50000`
|
||||||
*/
|
*/
|
||||||
public val autoSaveIntervalMillis: LongRange
|
public val autoSaveIntervalMillis: LongRange
|
||||||
get() = 30.secondsToMillis..10.minutesToSeconds
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 链接自动保存的 [Setting].
|
* 仅支持确切的 [Setting] 类型
|
||||||
* 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存
|
|
||||||
*
|
|
||||||
* 若 [AutoSaveSettingHolder.coroutineContext] 含有 [Job], 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存.
|
|
||||||
*
|
|
||||||
* @see getSetting
|
|
||||||
*/
|
*/
|
||||||
public open class AutoSaveSetting(private val owner: AutoSaveSettingHolder, private val storage: SettingStorage) :
|
@JvmDefault
|
||||||
AbstractSetting() {
|
public override fun <T : Setting> newSettingInstance(type: KType): T {
|
||||||
@JvmField
|
val classifier = type.classifier?.cast<KClass<*>>()?.java
|
||||||
@Volatile
|
require(classifier == Setting::class.java) {
|
||||||
internal var lastAutoSaveJob: Job? = null
|
"Cannot create Setting instance. AutoSaveSettingHolder supports only Setting type."
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Volatile
|
|
||||||
internal var currentFirstStartTime = atomic(0L)
|
|
||||||
|
|
||||||
init {
|
|
||||||
owner.coroutineContext[Job]?.invokeOnCompletion { doSave() }
|
|
||||||
}
|
}
|
||||||
|
return AutoSaveSetting(this) as T // T is always Setting
|
||||||
private val updaterBlock: suspend CoroutineScope.() -> Unit = {
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final override fun onValueChanged(value: Value<*>) {
|
|
||||||
lastAutoSaveJob = owner.launch(block = updaterBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doSave() = storage.store(owner, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal
|
|
||||||
|
|
||||||
internal class MemorySettingStorageImpl : SettingStorage, MemorySettingStorage {
|
|
||||||
private val list = mutableMapOf<Class<out Setting>, Setting>()
|
|
||||||
|
|
||||||
internal class MemorySettingImpl : AbstractSetting() {
|
|
||||||
override fun onValueChanged(value: Value<*>) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T {
|
|
||||||
return synchronized(list) {
|
|
||||||
list.getOrPut(settingClass) {
|
|
||||||
settingClass.kotlin.run {
|
|
||||||
objectInstance ?: createInstanceOrNull() ?: kotlin.run {
|
|
||||||
if (settingClass != Setting::class.java) {
|
|
||||||
throw IllegalArgumentException(
|
|
||||||
"Cannot create Setting instance. Make sure settingClass is Setting::class.java or a Kotlin's object, " +
|
|
||||||
"or has a constructor which either has no parameters or all parameters of which are optional"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
MemorySettingImpl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun store(holder: SettingHolder, setting: Setting) {
|
|
||||||
synchronized(list) {
|
|
||||||
list[setting::class.java] = setting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public open class MultiFileSettingStorageImpl(
|
|
||||||
public final override val directory: File
|
|
||||||
) : SettingStorage, MultiFileSettingStorage {
|
|
||||||
public override fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T =
|
|
||||||
with(settingClass.kotlin) {
|
|
||||||
val file = getSettingFile(holder, settingClass::class)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val instance = objectInstance ?: this.createInstanceOrNull() ?: kotlin.run {
|
|
||||||
if (settingClass != Setting::class.java) {
|
|
||||||
throw IllegalArgumentException(
|
|
||||||
"Cannot create Setting instance. Make sure settingClass is Setting::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 AutoSaveSettingHolder) {
|
|
||||||
AutoSaveSetting(holder, this@MultiFileSettingStorageImpl) as T?
|
|
||||||
} else null
|
|
||||||
} ?: throw IllegalArgumentException(
|
|
||||||
"Cannot create Setting instance. Make sure 'holder' is a AutoSaveSettingHolder, " +
|
|
||||||
"or 'setting' is an object or has a constructor which either has no parameters or all parameters of which are optional"
|
|
||||||
)
|
|
||||||
if (file.exists() && file.isFile && file.canRead()) {
|
|
||||||
Yaml.default.parse(instance.updaterSerializer, file.readText())
|
|
||||||
}
|
|
||||||
instance
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun getSettingFile(holder: SettingHolder, clazz: KClass<*>): File = with(clazz) {
|
|
||||||
val name = findASerialName()
|
|
||||||
|
|
||||||
val dir = File(directory, holder.name)
|
|
||||||
if (dir.isFile) {
|
|
||||||
error("Target directory ${dir.path} for holder $holder is occupied by a file therefore setting $qualifiedNameOrTip can't be saved.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val file = File(directory, name)
|
|
||||||
if (file.isDirectory) {
|
|
||||||
error("Target file $file is occupied by a directory therefore setting $qualifiedNameOrTip can't be saved.")
|
|
||||||
}
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConsoleExperimentalAPI
|
|
||||||
public override fun store(holder: SettingHolder, setting: Setting): Unit = with(setting::class) {
|
|
||||||
val file = getSettingFile(holder, this)
|
|
||||||
|
|
||||||
if (file.exists() && file.isFile && file.canRead()) {
|
|
||||||
file.writeText(Yaml.default.stringify(setting.updaterSerializer, Unit))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <T : Any> KClass<T>.createInstanceOrNull(): T? {
|
|
||||||
val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) }
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
return noArgsConstructor.callBy(emptyMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun KClass<*>.findASerialName(): String =
|
|
||||||
findAnnotation<SerialName>()?.value
|
|
||||||
?: qualifiedName
|
|
||||||
?: throw IllegalArgumentException("Cannot find a serial name for $this")
|
|
@ -41,11 +41,13 @@ public class SerializableValue<T>(
|
|||||||
/**
|
/**
|
||||||
* The serializer used to update and dump [delegate]
|
* The serializer used to update and dump [delegate]
|
||||||
*/
|
*/
|
||||||
override val serializer: KSerializer<Unit>
|
public override val serializer: KSerializer<Unit>
|
||||||
) : Value<T> by delegate, SerializerAwareValue<T> {
|
) : Value<T> by delegate, SerializerAwareValue<T> {
|
||||||
override fun toString(): String = delegate.toString()
|
public override fun toString(): String = delegate.toString()
|
||||||
}
|
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@JvmName("create")
|
||||||
public fun <T> Value<T>.serializableValueWith(
|
public fun <T> Value<T>.serializableValueWith(
|
||||||
serializer: KSerializer<T>
|
serializer: KSerializer<T>
|
||||||
): SerializableValue<T> {
|
): SerializableValue<T> {
|
||||||
@ -54,22 +56,42 @@ public fun <T> Value<T>.serializableValueWith(
|
|||||||
serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) })
|
serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see SerializableValue
|
* @see SerializableValue
|
||||||
*/
|
*/
|
||||||
public interface SerializerAwareValue<T> : Value<T> {
|
public interface SerializerAwareValue<T> : Value<T> {
|
||||||
public val serializer: KSerializer<Unit>
|
public val serializer: KSerializer<Unit>
|
||||||
}
|
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization")
|
||||||
public fun <T> SerializerAwareValue<T>.serialize(format: StringFormat): String {
|
public fun <T> SerializerAwareValue<T>.serialize(format: StringFormat): String {
|
||||||
return format.stringify(this.serializer, Unit)
|
return format.stringify(this.serializer, Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization")
|
||||||
public fun <T> SerializerAwareValue<T>.serialize(format: BinaryFormat): ByteArray {
|
public fun <T> SerializerAwareValue<T>.serialize(format: BinaryFormat): ByteArray {
|
||||||
return format.dump(this.serializer, Unit)
|
return format.dump(this.serializer, Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization")
|
||||||
|
public fun <T> SerializerAwareValue<T>.deserialize(format: StringFormat, value: String) {
|
||||||
|
format.parse(this.serializer, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@ConsoleExperimentalAPI("will be changed due to reconstruction of kotlinx.serialization")
|
||||||
|
public fun <T> SerializerAwareValue<T>.deserialize(format: BinaryFormat, value: ByteArray) {
|
||||||
|
format.load(this.serializer, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
public inline operator fun <T> Value<T>.getValue(mySetting: Any?, property: KProperty<*>): T = value
|
public inline operator fun <T> Value<T>.getValue(mySetting: Any?, property: KProperty<*>): T = value
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ package net.mamoe.mirai.console.setting.internal
|
|||||||
|
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.builtins.*
|
import kotlinx.serialization.builtins.*
|
||||||
|
import net.mamoe.mirai.console.setting.SerializableValue.Companion.serializableValueWith
|
||||||
import net.mamoe.mirai.console.setting.SerializerAwareValue
|
import net.mamoe.mirai.console.setting.SerializerAwareValue
|
||||||
import net.mamoe.mirai.console.setting.Setting
|
import net.mamoe.mirai.console.setting.Setting
|
||||||
import net.mamoe.mirai.console.setting.serializableValueWith
|
|
||||||
import net.mamoe.mirai.console.setting.valueFromKType
|
import net.mamoe.mirai.console.setting.valueFromKType
|
||||||
import net.mamoe.yamlkt.YamlDynamicSerializer
|
import net.mamoe.yamlkt.YamlDynamicSerializer
|
||||||
import net.mamoe.yamlkt.YamlNullableDynamicSerializer
|
import net.mamoe.yamlkt.YamlNullableDynamicSerializer
|
||||||
|
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.setting.internal
|
||||||
|
|
||||||
|
import kotlinx.atomicfu.atomic
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip
|
||||||
|
import net.mamoe.mirai.console.plugin.internal.updateWhen
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.loadSetting
|
||||||
|
import net.mamoe.mirai.console.setting.*
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
||||||
|
import net.mamoe.mirai.utils.currentTimeMillis
|
||||||
|
import net.mamoe.yamlkt.Yaml
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KParameter
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 链接自动保存的 [Setting].
|
||||||
|
* 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存
|
||||||
|
*
|
||||||
|
* 若 [AutoSaveSettingHolder.coroutineContext] 含有 [Job], 则 [AutoSaveSetting] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存.
|
||||||
|
*
|
||||||
|
* @see loadSetting
|
||||||
|
*/
|
||||||
|
internal open class AutoSaveSetting(private val owner: AutoSaveSettingHolder) :
|
||||||
|
AbstractSetting() {
|
||||||
|
private lateinit var storage: SettingStorage
|
||||||
|
|
||||||
|
override fun setStorage(storage: SettingStorage) {
|
||||||
|
check(!this::storage.isInitialized) { "storage is already initialized" }
|
||||||
|
this.storage = storage
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Volatile
|
||||||
|
internal var lastAutoSaveJob: Job? = null
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Volatile
|
||||||
|
internal var currentFirstStartTime = atomic(0L)
|
||||||
|
|
||||||
|
init {
|
||||||
|
owner.coroutineContext[Job]?.invokeOnCompletion { doSave() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val updaterBlock: suspend CoroutineScope.() -> Unit = {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MemorySettingStorageImpl(
|
||||||
|
private val onChanged: MemorySettingStorage.OnChangedCallback
|
||||||
|
) : SettingStorage, MemorySettingStorage,
|
||||||
|
MutableMap<Class<out Setting>, Setting> by mutableMapOf() {
|
||||||
|
|
||||||
|
internal inner class MemorySettingImpl : AbstractSetting() {
|
||||||
|
@ConsoleInternalAPI
|
||||||
|
override fun onValueChanged(value: Value<*>) {
|
||||||
|
onChanged.onChanged(this@MemorySettingStorageImpl, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setStorage(storage: SettingStorage) {
|
||||||
|
check(storage is MemorySettingStorageImpl) { "storage is not MemorySettingStorageImpl" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T = (synchronized(this) {
|
||||||
|
this.getOrPut(settingClass) {
|
||||||
|
settingClass.kotlin.run {
|
||||||
|
objectInstance ?: createInstanceOrNull() ?: kotlin.run {
|
||||||
|
if (settingClass != Setting::class.java) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Cannot create Setting instance. Make sure settingClass is Setting::class.java or a Kotlin's object, " +
|
||||||
|
"or has a constructor which either has no parameters or all parameters of which are optional"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MemorySettingImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as T).also { it.setStorage(this) }
|
||||||
|
|
||||||
|
override fun store(holder: SettingHolder, setting: Setting) {
|
||||||
|
synchronized(this) {
|
||||||
|
this[setting::class.java] = setting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("RedundantVisibilityModifier") // might be public in the future
|
||||||
|
internal open class MultiFileSettingStorageImpl(
|
||||||
|
public final override val directory: File
|
||||||
|
) : SettingStorage, MultiFileSettingStorage {
|
||||||
|
public override fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T =
|
||||||
|
with(settingClass.kotlin) {
|
||||||
|
val file = getSettingFile(holder, settingClass::class)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val instance = objectInstance ?: this.createInstanceOrNull() ?: kotlin.run {
|
||||||
|
if (settingClass != Setting::class.java) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Cannot create Setting instance. Make sure settingClass is Setting::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 AutoSaveSettingHolder) {
|
||||||
|
AutoSaveSetting(holder) as T?
|
||||||
|
} else null
|
||||||
|
} ?: throw IllegalArgumentException(
|
||||||
|
"Cannot create Setting instance. Make sure 'holder' is a AutoSaveSettingHolder, " +
|
||||||
|
"or 'setting' is an object or has a constructor which either has no parameters or all parameters of which are optional"
|
||||||
|
)
|
||||||
|
if (file.exists() && file.isFile && file.canRead()) {
|
||||||
|
Yaml.default.parse(instance.updaterSerializer, file.readText())
|
||||||
|
}
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getSettingFile(holder: SettingHolder, clazz: KClass<*>): File = with(clazz) {
|
||||||
|
val name = findASerialName()
|
||||||
|
|
||||||
|
val dir = File(directory, holder.name)
|
||||||
|
if (dir.isFile) {
|
||||||
|
error("Target directory ${dir.path} for holder $holder is occupied by a file therefore setting $qualifiedNameOrTip can't be saved.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(directory, name)
|
||||||
|
if (file.isDirectory) {
|
||||||
|
error("Target file $file is occupied by a directory therefore setting $qualifiedNameOrTip can't be saved.")
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
|
public override fun store(holder: SettingHolder, setting: Setting): Unit = with(setting::class) {
|
||||||
|
val file = getSettingFile(holder, this)
|
||||||
|
|
||||||
|
if (file.exists() && file.isFile && file.canRead()) {
|
||||||
|
file.writeText(Yaml.default.stringify(setting.updaterSerializer, Unit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
internal fun <T : Any> KClass<T>.createInstanceOrNull(): T? {
|
||||||
|
val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) }
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
return noArgsConstructor.callBy(emptyMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
internal fun KClass<*>.findASerialName(): String =
|
||||||
|
findAnnotation<SerialName>()?.value
|
||||||
|
?: qualifiedName
|
||||||
|
?: throw IllegalArgumentException("Cannot find a serial name for $this")
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.mamoe.mirai.console.setting.internal
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip
|
||||||
|
import net.mamoe.mirai.console.setting.Setting
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
internal inline fun <reified T : Any> KType.asKClass(): KClass<out T> {
|
||||||
|
val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" }
|
||||||
|
|
||||||
|
val fromClass = arguments[0].type?.classifier as? KClass<*> ?: Any::class
|
||||||
|
val toClass = T::class
|
||||||
|
|
||||||
|
require(toClass.isSubclassOf(fromClass)) {
|
||||||
|
"Cannot cast KClass<${fromClass.qualifiedNameOrTip}> to KClass<${toClass.qualifiedNameOrTip}>"
|
||||||
|
}
|
||||||
|
|
||||||
|
return clazz
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inline fun <reified T : Setting> newSettingInstanceUsingReflection(type: KType): T {
|
||||||
|
val classifier = type.asKClass<T>()
|
||||||
|
|
||||||
|
return with(classifier) {
|
||||||
|
objectInstance
|
||||||
|
?: createInstanceOrNull()
|
||||||
|
?: throw IllegalArgumentException(
|
||||||
|
"Cannot create Setting instance. " +
|
||||||
|
"SettingHolder supports Settings implemented as an object " +
|
||||||
|
"or the ones with a constructor which either has no parameters or all parameters of which are optional, by default newSettingInstance implementation."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -15,9 +15,11 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
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.MiraiConsoleInternal
|
import net.mamoe.mirai.console.MiraiConsoleImplementationBridge
|
||||||
import net.mamoe.mirai.console.setting.*
|
import net.mamoe.mirai.console.setting.*
|
||||||
|
import net.mamoe.mirai.console.setting.SettingStorage.Companion.load
|
||||||
import net.mamoe.mirai.contact.User
|
import net.mamoe.mirai.contact.User
|
||||||
|
import net.mamoe.mirai.utils.minutesToMillis
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
@ -52,7 +54,10 @@ internal fun CoroutineScope.childScope(context: CoroutineContext = EmptyCoroutin
|
|||||||
|
|
||||||
internal object ConsoleBuiltInSettingHolder : AutoSaveSettingHolder,
|
internal object ConsoleBuiltInSettingHolder : AutoSaveSettingHolder,
|
||||||
CoroutineScope by MiraiConsole.childScope() {
|
CoroutineScope by MiraiConsole.childScope() {
|
||||||
|
override val autoSaveIntervalMillis: LongRange
|
||||||
|
get() = 30.minutesToMillis..60.minutesToMillis
|
||||||
override val name: String get() = "ConsoleBuiltIns"
|
override val name: String get() = "ConsoleBuiltIns"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object ConsoleBuiltInSettingStorage : SettingStorage by MiraiConsoleInternal.settingStorageForJarPluginLoader
|
internal object ConsoleBuiltInSettingStorage :
|
||||||
|
SettingStorage by MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader
|
@ -5,6 +5,7 @@ import kotlin.annotation.AnnotationTarget.*
|
|||||||
/**
|
/**
|
||||||
* 表明这个 API 是为了让 Java 使用者调用更方便. Kotlin 使用者不应该使用这些 API.
|
* 表明这个 API 是为了让 Java 使用者调用更方便. Kotlin 使用者不应该使用这些 API.
|
||||||
*/
|
*/
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||||
@Target(PROPERTY, FUNCTION, TYPE, CLASS)
|
@Target(PROPERTY, FUNCTION, TYPE, CLASS)
|
||||||
internal annotation class JavaFriendlyAPI
|
internal annotation class JavaFriendlyAPI
|
||||||
@ -15,7 +16,7 @@ internal annotation class JavaFriendlyAPI
|
|||||||
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
|
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
|
||||||
* 非常不建议在发行版本中使用这些 API.
|
* 非常不建议在发行版本中使用这些 API.
|
||||||
*/
|
*/
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY)
|
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
@ -29,7 +30,7 @@ public annotation class ConsoleInternalAPI(
|
|||||||
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
||||||
* 不建议在发行版本中使用这些 API.
|
* 不建议在发行版本中使用这些 API.
|
||||||
*/
|
*/
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
|
@ -3,12 +3,18 @@
|
|||||||
package net.mamoe.mirai.console.utils
|
package net.mamoe.mirai.console.utils
|
||||||
|
|
||||||
import net.mamoe.mirai.console.encodeToString
|
import net.mamoe.mirai.console.encodeToString
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
|
import net.mamoe.mirai.console.utils.ResourceContainer.Companion.asResourceContainer
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源容器.
|
* 资源容器.
|
||||||
|
*
|
||||||
|
* 资源容器可能使用 [Class.getResourceAsStream], 也可能使用其他方式, 取决于实现方式.
|
||||||
|
*
|
||||||
|
* @see JvmPlugin [JvmPlugin] 实现 [ResourceContainer], 使用 [ResourceContainer.asResourceContainer]
|
||||||
*/
|
*/
|
||||||
public interface ResourceContainer {
|
public interface ResourceContainer {
|
||||||
/**
|
/**
|
||||||
@ -32,27 +38,21 @@ public interface ResourceContainer {
|
|||||||
public companion object {
|
public companion object {
|
||||||
/**
|
/**
|
||||||
* 使用 [Class.getResourceAsStream] 读取资源文件
|
* 使用 [Class.getResourceAsStream] 读取资源文件
|
||||||
*
|
|
||||||
* @see asResourceContainer Kotlin 使用
|
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JavaFriendlyAPI
|
@JvmName("byClass")
|
||||||
public fun byClass(clazz: Class<*>): ResourceContainer = clazz.asResourceContainer()
|
public fun KClass<*>.asResourceContainer(): ResourceContainer = this.java.asResourceContainer()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用 [Class.getResourceAsStream] 读取资源文件
|
|
||||||
*/
|
|
||||||
public fun KClass<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this.java)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用 [Class.getResourceAsStream] 读取资源文件
|
* 使用 [Class.getResourceAsStream] 读取资源文件
|
||||||
*/
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@JvmName("byClass")
|
||||||
public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this)
|
public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassAsResourceContainer(
|
||||||
internal class ClassAsResourceContainer(
|
|
||||||
private val clazz: Class<*>
|
private val clazz: Class<*>
|
||||||
) : ResourceContainer {
|
) : ResourceContainer {
|
||||||
override fun getResourceAsStream(name: String): InputStream = clazz.getResourceAsStream(name)
|
override fun getResourceAsStream(name: String): InputStream = clazz.getResourceAsStream(name)
|
||||||
|
@ -14,6 +14,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
|
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||||
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
|
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
@ -34,7 +35,7 @@ import kotlin.test.assertNotNull
|
|||||||
|
|
||||||
@OptIn(ConsoleInternalAPI::class)
|
@OptIn(ConsoleInternalAPI::class)
|
||||||
fun initTestEnvironment() {
|
fun initTestEnvironment() {
|
||||||
MiraiConsoleInitializer.init(object : IMiraiConsole {
|
object : MiraiConsoleImplementation {
|
||||||
override val rootDir: File = createTempDir()
|
override val rootDir: File = createTempDir()
|
||||||
override val frontEnd: MiraiConsoleFrontEnd = object : MiraiConsoleFrontEnd {
|
override val frontEnd: MiraiConsoleFrontEnd = object : MiraiConsoleFrontEnd {
|
||||||
override val name: String get() = "Test"
|
override val name: String get() = "Test"
|
||||||
@ -52,7 +53,7 @@ fun initTestEnvironment() {
|
|||||||
override val settingStorageForJarPluginLoader: SettingStorage get() = MemorySettingStorage()
|
override val settingStorageForJarPluginLoader: SettingStorage get() = MemorySettingStorage()
|
||||||
override val settingStorageForBuiltIns: SettingStorage get() = MemorySettingStorage()
|
override val settingStorageForBuiltIns: SettingStorage get() = MemorySettingStorage()
|
||||||
override val coroutineContext: CoroutineContext = SupervisorJob()
|
override val coroutineContext: CoroutineContext = SupervisorJob()
|
||||||
})
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object Testing {
|
internal object Testing {
|
||||||
|
@ -12,10 +12,12 @@ package net.mamoe.mirai.console.setting
|
|||||||
import kotlinx.serialization.UnstableDefault
|
import kotlinx.serialization.UnstableDefault
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertSame
|
import kotlin.test.assertSame
|
||||||
|
|
||||||
|
@OptIn(ConsoleInternalAPI::class)
|
||||||
internal class SettingTest {
|
internal class SettingTest {
|
||||||
|
|
||||||
class MySetting : AbstractSetting() {
|
class MySetting : AbstractSetting() {
|
||||||
@ -23,9 +25,13 @@ internal class SettingTest {
|
|||||||
val map by value<MutableMap<String, String>>()
|
val map by value<MutableMap<String, String>>()
|
||||||
val map2 by value<MutableMap<String, MutableMap<String, String>>>()
|
val map2 by value<MutableMap<String, MutableMap<String, String>>>()
|
||||||
|
|
||||||
|
@ConsoleInternalAPI
|
||||||
override fun onValueChanged(value: Value<*>) {
|
override fun onValueChanged(value: Value<*>) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setStorage(storage: SettingStorage) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableDefault::class)
|
@OptIn(UnstableDefault::class)
|
||||||
|
@ -17,6 +17,7 @@ kotlin {
|
|||||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
|
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
|
||||||
|
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||||
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||||
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||||
languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
|
languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
|
||||||
|
@ -48,7 +48,7 @@ internal val LoggerCreator: (identity: String?) -> MiraiLogger = {
|
|||||||
/**
|
/**
|
||||||
* mirai-console-pure 前端实现
|
* mirai-console-pure 前端实现
|
||||||
*
|
*
|
||||||
* @see MiraiConsolePure 后端实现
|
* @see MiraiConsoleImplementationPure 后端实现
|
||||||
* @see MiraiConsolePureLoader CLI 入口点
|
* @see MiraiConsolePureLoader CLI 入口点
|
||||||
*/
|
*/
|
||||||
@ConsoleInternalAPI
|
@ConsoleInternalAPI
|
||||||
|
@ -18,16 +18,16 @@
|
|||||||
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
|
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
|
||||||
"EXPOSED_SUPER_CLASS"
|
"EXPOSED_SUPER_CLASS"
|
||||||
)
|
)
|
||||||
@file:OptIn(ConsoleInternalAPI::class)
|
@file:OptIn(ConsoleInternalAPI::class, ConsoleFrontEndImplementation::class)
|
||||||
|
|
||||||
package net.mamoe.mirai.console.pure
|
package net.mamoe.mirai.console.pure
|
||||||
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import net.mamoe.mirai.console.IMiraiConsole
|
import net.mamoe.mirai.console.ConsoleFrontEndImplementation
|
||||||
import net.mamoe.mirai.console.MiraiConsoleFrontEnd
|
import net.mamoe.mirai.console.MiraiConsoleFrontEnd
|
||||||
import net.mamoe.mirai.console.MiraiConsoleInitializer
|
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||||
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
|
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
@ -44,7 +44,8 @@ import java.io.File
|
|||||||
* @see MiraiConsoleFrontEndPure 前端实现
|
* @see MiraiConsoleFrontEndPure 前端实现
|
||||||
* @see MiraiConsolePureLoader CLI 入口点
|
* @see MiraiConsolePureLoader CLI 入口点
|
||||||
*/
|
*/
|
||||||
class MiraiConsolePure @JvmOverloads constructor(
|
class MiraiConsoleImplementationPure
|
||||||
|
@JvmOverloads constructor(
|
||||||
override val rootDir: File = File("."),
|
override val rootDir: File = File("."),
|
||||||
override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JarPluginLoader }),
|
override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JarPluginLoader }),
|
||||||
override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure,
|
override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure,
|
||||||
@ -52,21 +53,9 @@ class MiraiConsolePure @JvmOverloads constructor(
|
|||||||
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,
|
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,
|
||||||
override val settingStorageForJarPluginLoader: SettingStorage = MultiFileSettingStorage(rootDir),
|
override val settingStorageForJarPluginLoader: SettingStorage = MultiFileSettingStorage(rootDir),
|
||||||
override val settingStorageForBuiltIns: SettingStorage = MultiFileSettingStorage(rootDir)
|
override val settingStorageForBuiltIns: SettingStorage = MultiFileSettingStorage(rootDir)
|
||||||
) : IMiraiConsole, CoroutineScope by CoroutineScope(SupervisorJob()) {
|
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) {
|
||||||
init {
|
init {
|
||||||
rootDir.mkdir()
|
rootDir.mkdir()
|
||||||
require(rootDir.isDirectory) { "rootDir ${rootDir.absolutePath} is not a directory" }
|
require(rootDir.isDirectory) { "rootDir ${rootDir.absolutePath} is not a directory" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmField
|
|
||||||
internal var started: Boolean = false
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun MiraiConsolePure.start() = synchronized(this) {
|
|
||||||
check(!started) { "mirai-console is already started and can't be restarted." }
|
|
||||||
MiraiConsoleInitializer.init(this)
|
|
||||||
started = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -22,9 +22,8 @@ package net.mamoe.mirai.console.pure
|
|||||||
|
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
|
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.*
|
||||||
import net.mamoe.mirai.console.job
|
|
||||||
import net.mamoe.mirai.console.pure.MiraiConsolePure.Companion.start
|
|
||||||
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
||||||
import net.mamoe.mirai.message.data.Message
|
import net.mamoe.mirai.message.data.Message
|
||||||
import net.mamoe.mirai.message.data.content
|
import net.mamoe.mirai.message.data.content
|
||||||
@ -46,7 +45,7 @@ object MiraiConsolePureLoader {
|
|||||||
internal fun startup() {
|
internal fun startup() {
|
||||||
DefaultLogger = { MiraiConsoleFrontEndPure.loggerFor(it) }
|
DefaultLogger = { MiraiConsoleFrontEndPure.loggerFor(it) }
|
||||||
overrideSTD()
|
overrideSTD()
|
||||||
MiraiConsolePure().start()
|
MiraiConsoleImplementationPure().start()
|
||||||
startConsoleThread()
|
startConsoleThread()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user