Public api stabilization:

Separate PluginManager and its implementations;
Add Setting extensions;
Documentation updates
This commit is contained in:
Him188 2020-07-31 16:35:41 +08:00
parent b2177c16cf
commit 74925ff6e8
11 changed files with 274 additions and 220 deletions

View File

@ -1,6 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.text.SimpleDateFormat
import java.util.*
plugins {
kotlin("jvm") version Versions.kotlinCompiler
@ -75,6 +74,8 @@ dependencies {
api("com.vdurmont:semver4j:3.1.0")
//api(kotlinx("collections-immutable", Versions.collectionsImmutable))
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
testApi(kotlin("stdlib-jdk8"))
testApi(kotlin("test"))

View File

@ -23,10 +23,10 @@ import net.mamoe.mirai.console.command.internal.InternalCommandManager
import net.mamoe.mirai.console.command.primaryName
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.PluginManagerImpl
import net.mamoe.mirai.console.plugin.center.CuiPluginCenter
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.plugin.jvm.PluginManagerImpl
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleBuiltInSettingStorage
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
@ -64,7 +64,9 @@ public interface MiraiConsole : CoroutineScope {
public val mainLogger: MiraiLogger
/**
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
* 内建加载器列表, 一般需要包含 [JarPluginLoader].
*
* @return 不可变 [List] ([java.util.Collections.unmodifiableList])
*/
public val builtInPluginLoaders: List<PluginLoader<*, *>>

View File

@ -55,7 +55,9 @@ public interface MiraiConsoleImplementation : CoroutineScope {
public val mainLogger: MiraiLogger
/**
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
* 内建加载器列表, 一般需要包含 [JarPluginLoader].
*
* @return 不可变的 [List]
*/
public val builtInPluginLoaders: List<PluginLoader<*, *>>

View File

@ -36,7 +36,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
*/
@get:JvmName("getPluginDescription")
@get:Throws(PluginLoadException::class)
public val P.description: D // Java signature: `public P getDescription(P)`
public val P.description: D // Java signature: `public D getDescription(P)`
/**
* 加载一个插件 (实例), 但不 [启用][enable] . 返回加载成功的主类实例
@ -50,6 +50,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
public fun disable(plugin: P)
}
@JvmSynthetic
public inline fun <D : PluginDescription, P : Plugin> PluginLoader<in P, out D>.getDescription(plugin: P): D =
plugin.description

View File

@ -11,30 +11,22 @@
package net.mamoe.mirai.console.plugin
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.setting.internal.cast
import net.mamoe.mirai.utils.info
import net.mamoe.mirai.console.plugin.jvm.PluginManagerImpl
import java.io.File
import java.util.concurrent.locks.ReentrantLock
// TODO: 2020/7/11 top-level or in PluginManager.companion?
public val Plugin.description: PluginDescription
get() = PluginManagerImpl.resolvedPlugins.firstOrNull { it == this }
?.loader?.cast<PluginLoader<Plugin, PluginDescription>>()
?.getDescription(this)
?: error("Plugin is unloaded")
@JvmSynthetic
public inline fun PluginLoader<*, *>.register(): Boolean = PluginManager.registerPluginLoader(this)
@JvmSynthetic
public inline fun PluginLoader<*, *>.unregister(): Boolean = PluginManager.unregisterPluginLoader(this)
// TODO: 2020/7/11 document
/**
* 插件管理器.
*/
public interface PluginManager {
/**
* `$rootDir/plugins`
*/
public val pluginsDir: File
/**
* `$rootDir/data`
*/
public val pluginsDataFolder: File
/**
@ -43,7 +35,9 @@ public interface PluginManager {
public val plugins: List<Plugin>
/**
* 内建的插件加载器列表. [MiraiConsole] 初始化
* 内建的插件加载器列表. [MiraiConsole] 初始化.
*
* @return 不可变的 list.
*/
public val builtInLoaders: List<PluginLoader<*, *>>
@ -56,9 +50,20 @@ public interface PluginManager {
public fun unregisterPluginLoader(loader: PluginLoader<*, *>): Boolean
/**
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription]
*/
public val Plugin.description: PluginDescription
public companion object INSTANCE : PluginManager by PluginManagerImpl
}
@JvmSynthetic
public inline fun PluginLoader<*, *>.register(): Boolean = PluginManager.registerPluginLoader(this)
@JvmSynthetic
public inline fun PluginLoader<*, *>.unregister(): Boolean = PluginManager.unregisterPluginLoader(this)
public class PluginMissingDependencyException : PluginResolutionException {
public constructor() : super()
public constructor(message: String?) : super(message)
@ -71,175 +76,4 @@ public open class PluginResolutionException : Exception {
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
// internal
internal object PluginManagerImpl : PluginManager {
override val pluginsDir = File(MiraiConsole.rootDir, "plugins").apply { mkdir() }
override val pluginsDataFolder = File(MiraiConsole.rootDir, "data").apply { mkdir() }
@Suppress("ObjectPropertyName")
private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf()
private val loadersLock: ReentrantLock = ReentrantLock()
private val logger = MiraiConsole.newLogger("PluginManager")
@JvmField
internal val resolvedPlugins: MutableList<Plugin> = mutableListOf()
override val plugins: List<Plugin>
get() = resolvedPlugins.toList()
override val builtInLoaders: List<PluginLoader<*, *>>
get() = MiraiConsole.builtInPluginLoaders
override val pluginLoaders: List<PluginLoader<*, *>>
get() = _pluginLoaders.toList()
override fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean = loadersLock.withLock {
if (_pluginLoaders.any { it::class == loader }) {
return false
}
_pluginLoaders.add(loader)
}
override fun unregisterPluginLoader(loader: PluginLoader<*, *>) = loadersLock.withLock {
_pluginLoaders.remove(loader)
}
// region LOADING
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(description: D): P {
return kotlin.runCatching {
this.load(description).also { resolvedPlugins.add(it) }
}.fold(
onSuccess = {
logger.info { "Successfully loaded plugin ${description.name}" }
it
},
onFailure = {
logger.info { "Cannot load plugin ${description.name}" }
throw it
}
)
}
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.enablePlugin(plugin: Plugin) {
kotlin.runCatching {
@Suppress("UNCHECKED_CAST")
this.enable(plugin as P)
}.fold(
onSuccess = {
logger.info { "Successfully enabled plugin ${plugin.description.name}" }
},
onFailure = {
logger.info { "Cannot enable plugin ${plugin.description.name}" }
throw it
}
)
}
/**
* STEPS:
* 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件
* 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件
* 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表.
* 4. 解决依赖并排序
* 5. 依次 [PluginLoader.load]
* 但不 [PluginLoader.enable]
*
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
internal fun loadEnablePlugins() {
(loadAndEnableLoaderProviders() + _pluginLoaders.listAllPlugins().flatMap { it.second })
.sortByDependencies().loadAndEnableAllInOrder()
}
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
return this.map { (loader, desc) ->
loader to loader.loadPluginNoEnable(desc)
}.forEach { (loader, plugin) ->
loader.enablePlugin(plugin)
}
}
/**
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
val allDescriptions =
this.builtInLoaders.listAllPlugins()
.asSequence()
.onEach { (loader, descriptions) ->
loader as PluginLoader<Plugin, PluginDescription>
descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies().loadAndEnableAllInOrder()
}
.flatMap { it.second.asSequence() }
return allDescriptions.toList()
}
private fun List<PluginLoader<*, *>>.listAllPlugins(): List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>> {
return associateWith { loader -> loader.listPlugins().map { desc -> desc.wrapWith(loader) } }.toList()
}
@Throws(PluginMissingDependencyException::class)
private fun <D : PluginDescription> List<D>.sortByDependencies(): List<D> {
val resolved = ArrayList<D>(this.size)
fun D.canBeLoad(): Boolean = this.dependencies.all { it.isOptional || it in resolved }
fun List<D>.consumeLoadable(): List<D> {
val (canBeLoad, cannotBeLoad) = this.partition { it.canBeLoad() }
resolved.addAll(canBeLoad)
return cannotBeLoad
}
fun List<PluginDependency>.filterIsMissing(): List<PluginDependency> =
this.filterNot { it.isOptional || it in resolved }
tailrec fun List<D>.doSort() {
if (this.isEmpty()) return
val beforeSize = this.size
this.consumeLoadable().also { resultPlugins ->
check(resultPlugins.size < beforeSize) {
throw PluginMissingDependencyException(resultPlugins.joinToString("\n") { badPlugin ->
"Cannot load plugin ${badPlugin.name}, missing dependencies: ${
badPlugin.dependencies.filterIsMissing()
.joinToString()
}"
})
}
}.doSort()
}
this.doSort()
return resolved
}
// endregion
}
internal data class PluginDescriptionWithLoader(
@JvmField val loader: PluginLoader<*, PluginDescription>, // easier type
@JvmField val delegate: PluginDescription
) : PluginDescription by delegate
@Suppress("UNCHECKED_CAST")
internal fun <D : PluginDescription> PluginDescription.unwrap(): D =
if (this is PluginDescriptionWithLoader) this.delegate as D else this as D
@Suppress("UNCHECKED_CAST")
internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>): PluginDescriptionWithLoader =
PluginDescriptionWithLoader(
loader as PluginLoader<*, PluginDescription>, this
)
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
any { it.name == dependency.name }
}

View File

@ -20,6 +20,21 @@ import net.mamoe.yamlkt.YamlDynamicSerializer
import java.io.File
/**
* 插件描述
*/
public interface PluginDescription {
public val kind: PluginKind
public val name: String
public val author: String
public val version: Semver
public val info: String
/** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */
public val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency>
}
/** 插件类型 */
@Serializable(with = PluginKind.AsStringSerializer::class)
public enum class PluginKind {
@ -39,21 +54,6 @@ public enum class PluginKind {
)
}
/**
* 插件描述
*/
public interface PluginDescription {
public val kind: PluginKind
public val name: String
public val author: String
public val version: Semver
public val info: String
/** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */
public val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency>
}
/** 插件的一个依赖的信息 */
@Serializable
public data class PluginDependency(

View File

@ -0,0 +1,193 @@
/*
* 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("NOTHING_TO_INLINE", "unused")
package net.mamoe.mirai.console.plugin.jvm
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.console.setting.internal.cast
import net.mamoe.mirai.utils.info
import java.io.File
import java.util.concurrent.locks.ReentrantLock
internal object PluginManagerImpl : PluginManager {
override val pluginsDir = File(MiraiConsole.rootDir, "plugins").apply { mkdir() }
override val pluginsDataFolder = File(MiraiConsole.rootDir, "data").apply { mkdir() }
@Suppress("ObjectPropertyName")
private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf()
private val loadersLock: ReentrantLock = ReentrantLock()
private val logger = MiraiConsole.newLogger("PluginManager")
@JvmField
internal val resolvedPlugins: MutableList<Plugin> = mutableListOf()
override val plugins: List<Plugin>
get() = resolvedPlugins.toList()
override val builtInLoaders: List<PluginLoader<*, *>>
get() = MiraiConsole.builtInPluginLoaders
override val pluginLoaders: List<PluginLoader<*, *>>
get() = _pluginLoaders.toList()
override val Plugin.description: PluginDescription
get() = resolvedPlugins.firstOrNull { it == this }
?.loader?.cast<PluginLoader<Plugin, PluginDescription>>()
?.getDescription(this)
?: error("Plugin is unloaded")
override fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean = loadersLock.withLock {
if (_pluginLoaders.any { it::class == loader }) {
return false
}
_pluginLoaders.add(loader)
}
override fun unregisterPluginLoader(loader: PluginLoader<*, *>) = loadersLock.withLock {
_pluginLoaders.remove(loader)
}
// region LOADING
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(description: D): P {
return kotlin.runCatching {
this.load(description).also { resolvedPlugins.add(it) }
}.fold(
onSuccess = {
logger.info { "Successfully loaded plugin ${description.name}" }
it
},
onFailure = {
logger.info { "Cannot load plugin ${description.name}" }
throw it
}
)
}
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.enablePlugin(plugin: Plugin) {
kotlin.runCatching {
@Suppress("UNCHECKED_CAST")
this.enable(plugin as P)
}.fold(
onSuccess = {
logger.info { "Successfully enabled plugin ${plugin.description.name}" }
},
onFailure = {
logger.info { "Cannot enable plugin ${plugin.description.name}" }
throw it
}
)
}
/**
* STEPS:
* 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件
* 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件
* 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表.
* 4. 解决依赖并排序
* 5. 依次 [PluginLoader.load]
* 但不 [PluginLoader.enable]
*
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
internal fun loadEnablePlugins() {
(loadAndEnableLoaderProviders() + _pluginLoaders.listAllPlugins().flatMap { it.second })
.sortByDependencies().loadAndEnableAllInOrder()
}
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
return this.map { (loader, desc) ->
loader to loader.loadPluginNoEnable(desc)
}.forEach { (loader, plugin) ->
loader.enablePlugin(plugin)
}
}
/**
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
val allDescriptions =
this.builtInLoaders.listAllPlugins()
.asSequence()
.onEach { (loader, descriptions) ->
loader as PluginLoader<Plugin, PluginDescription>
descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies().loadAndEnableAllInOrder()
}
.flatMap { it.second.asSequence() }
return allDescriptions.toList()
}
private fun List<PluginLoader<*, *>>.listAllPlugins(): List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>> {
return associateWith { loader -> loader.listPlugins().map { desc -> desc.wrapWith(loader) } }.toList()
}
@Throws(PluginMissingDependencyException::class)
private fun <D : PluginDescription> List<D>.sortByDependencies(): List<D> {
val resolved = ArrayList<D>(this.size)
fun D.canBeLoad(): Boolean = this.dependencies.all { it.isOptional || it in resolved }
fun List<D>.consumeLoadable(): List<D> {
val (canBeLoad, cannotBeLoad) = this.partition { it.canBeLoad() }
resolved.addAll(canBeLoad)
return cannotBeLoad
}
fun List<PluginDependency>.filterIsMissing(): List<PluginDependency> =
this.filterNot { it.isOptional || it in resolved }
tailrec fun List<D>.doSort() {
if (this.isEmpty()) return
val beforeSize = this.size
this.consumeLoadable().also { resultPlugins ->
check(resultPlugins.size < beforeSize) {
throw PluginMissingDependencyException(resultPlugins.joinToString("\n") { badPlugin ->
"Cannot load plugin ${badPlugin.name}, missing dependencies: ${
badPlugin.dependencies.filterIsMissing()
.joinToString()
}"
})
}
}.doSort()
}
this.doSort()
return resolved
}
// endregion
}
internal data class PluginDescriptionWithLoader(
@JvmField val loader: PluginLoader<*, PluginDescription>, // easier type
@JvmField val delegate: PluginDescription
) : PluginDescription by delegate
@Suppress("UNCHECKED_CAST")
internal fun <D : PluginDescription> PluginDescription.unwrap(): D =
if (this is PluginDescriptionWithLoader) this.delegate as D else this as D
@Suppress("UNCHECKED_CAST")
internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>): PluginDescriptionWithLoader =
PluginDescriptionWithLoader(
loader as PluginLoader<*, PluginDescription>, this
)
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
any { it.name == dependency.name }

View File

@ -88,7 +88,7 @@ public abstract class AbstractSetting : Setting, SettingImpl() {
*
* @see JvmPlugin.loadSetting 通过 [JvmPlugin] 获取指定 [Setting] 实例.
*/
public interface Setting {
public interface Setting : ExperimentalSettingExtensions {
/**
* 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪.
*/
@ -113,6 +113,21 @@ public interface Setting {
public fun setStorage(storage: SettingStorage)
}
@ConsoleExperimentalAPI("")
public interface ExperimentalSettingExtensions {
public fun <E, V, K> MutableMap<E, V>.shadowMap(
eToK: (E) -> K,
kToE: (K) -> E
): MutableMap<K, V> {
return this.shadowMap(
kTransform = eToK,
kTransformBack = kToE,
vTransform = { it },
vTransformBack = { it }
)
}
}
//// region Setting_value_primitives CODEGEN ////
public fun Setting.value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default)

View File

@ -41,7 +41,7 @@ internal fun Bot.addManager(id: Long): Boolean {
}
internal object ManagersConfig : Setting by (ConsoleBuiltInSettingStorage.load(ConsoleBuiltInSettingHolder)) {
internal object ManagersConfig : Setting by ConsoleBuiltInSettingStorage.load() {
private val managers: MutableMap<Long, MutableSet<Long>> by value()
internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf)
@ -54,10 +54,12 @@ internal fun CoroutineScope.childScope(context: CoroutineContext = EmptyCoroutin
internal object ConsoleBuiltInSettingHolder : AutoSaveSettingHolder,
CoroutineScope by MiraiConsole.childScope() {
override val autoSaveIntervalMillis: LongRange
get() = 30.minutesToMillis..60.minutesToMillis
override val autoSaveIntervalMillis: LongRange = 30.minutesToMillis..60.minutesToMillis
override val name: String get() = "ConsoleBuiltIns"
}
internal object ConsoleBuiltInSettingStorage :
SettingStorage by MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader
SettingStorage by MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader {
inline fun <reified T : Setting> load(): T = load(ConsoleBuiltInSettingHolder)
}

View File

@ -42,21 +42,21 @@ public interface ResourceContainer {
*/
@JvmDefault
public fun getResource(name: String, charset: Charset): String =
this.getResourceAsStream(name).use { it.readBytes() }.encodeToString()
this.getResourceAsStream(name).use { it.readBytes() }.encodeToString(charset)
public companion object {
/**
* 使用 [Class.getResourceAsStream] 读取资源文件
*/
@JvmStatic
@JvmName("byClass")
@JvmName("create")
public fun KClass<*>.asResourceContainer(): ResourceContainer = this.java.asResourceContainer()
/**
* 使用 [Class.getResourceAsStream] 读取资源文件
*/
@JvmStatic
@JvmName("byClass")
@JvmName("create")
public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this)
}
}

View File

@ -37,6 +37,7 @@ import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.util.*
/**
* mirai-console-pure 后端实现
@ -47,7 +48,10 @@ import java.io.File
class MiraiConsoleImplementationPure
@JvmOverloads constructor(
override val rootDir: File = File("."),
override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JarPluginLoader }),
override val builtInPluginLoaders: List<PluginLoader<*, *>> = Collections.unmodifiableList(
listOf(
DeferredPluginLoader { JarPluginLoader })
),
override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure,
override val mainLogger: MiraiLogger = frontEnd.loggerFor("main"),
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,