mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-27 00:30:17 +08:00
Public api stabilization:
Separate PluginManager and its implementations; Add Setting extensions; Documentation updates
This commit is contained in:
parent
b2177c16cf
commit
74925ff6e8
@ -1,6 +1,5 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version Versions.kotlinCompiler
|
kotlin("jvm") version Versions.kotlinCompiler
|
||||||
@ -75,6 +74,8 @@ dependencies {
|
|||||||
|
|
||||||
api("com.vdurmont:semver4j:3.1.0")
|
api("com.vdurmont:semver4j:3.1.0")
|
||||||
|
|
||||||
|
//api(kotlinx("collections-immutable", Versions.collectionsImmutable))
|
||||||
|
|
||||||
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||||
testApi(kotlin("stdlib-jdk8"))
|
testApi(kotlin("stdlib-jdk8"))
|
||||||
testApi(kotlin("test"))
|
testApi(kotlin("test"))
|
||||||
|
@ -23,10 +23,10 @@ import net.mamoe.mirai.console.command.internal.InternalCommandManager
|
|||||||
import net.mamoe.mirai.console.command.primaryName
|
import net.mamoe.mirai.console.command.primaryName
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginManager
|
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.CuiPluginCenter
|
||||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
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.setting.SettingStorage
|
||||||
import net.mamoe.mirai.console.utils.ConsoleBuiltInSettingStorage
|
import net.mamoe.mirai.console.utils.ConsoleBuiltInSettingStorage
|
||||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||||
@ -64,7 +64,9 @@ public interface MiraiConsole : CoroutineScope {
|
|||||||
public val mainLogger: MiraiLogger
|
public val mainLogger: MiraiLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
|
* 内建加载器列表, 一般需要包含 [JarPluginLoader].
|
||||||
|
*
|
||||||
|
* @return 不可变 [List] ([java.util.Collections.unmodifiableList])
|
||||||
*/
|
*/
|
||||||
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
||||||
|
|
||||||
|
@ -55,7 +55,9 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
|||||||
public val mainLogger: MiraiLogger
|
public val mainLogger: MiraiLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
|
* 内建加载器列表, 一般需要包含 [JarPluginLoader].
|
||||||
|
*
|
||||||
|
* @return 不可变的 [List]
|
||||||
*/
|
*/
|
||||||
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
|||||||
*/
|
*/
|
||||||
@get:JvmName("getPluginDescription")
|
@get:JvmName("getPluginDescription")
|
||||||
@get:Throws(PluginLoadException::class)
|
@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] 它. 返回加载成功的主类实例
|
* 加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例
|
||||||
@ -50,6 +50,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
|||||||
public fun disable(plugin: P)
|
public fun disable(plugin: P)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
public inline fun <D : PluginDescription, P : Plugin> PluginLoader<in P, out D>.getDescription(plugin: P): D =
|
public inline fun <D : PluginDescription, P : Plugin> PluginLoader<in P, out D>.getDescription(plugin: P): D =
|
||||||
plugin.description
|
plugin.description
|
||||||
|
|
||||||
|
@ -11,30 +11,22 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.plugin
|
package net.mamoe.mirai.console.plugin
|
||||||
|
|
||||||
import kotlinx.atomicfu.locks.withLock
|
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.setting.internal.cast
|
import net.mamoe.mirai.console.plugin.jvm.PluginManagerImpl
|
||||||
import net.mamoe.mirai.utils.info
|
|
||||||
import java.io.File
|
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 {
|
public interface PluginManager {
|
||||||
|
/**
|
||||||
|
* `$rootDir/plugins`
|
||||||
|
*/
|
||||||
public val pluginsDir: File
|
public val pluginsDir: File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `$rootDir/data`
|
||||||
|
*/
|
||||||
public val pluginsDataFolder: File
|
public val pluginsDataFolder: File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +35,9 @@ public interface PluginManager {
|
|||||||
public val plugins: List<Plugin>
|
public val plugins: List<Plugin>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内建的插件加载器列表. 由 [MiraiConsole] 初始化
|
* 内建的插件加载器列表. 由 [MiraiConsole] 初始化.
|
||||||
|
*
|
||||||
|
* @return 不可变的 list.
|
||||||
*/
|
*/
|
||||||
public val builtInLoaders: List<PluginLoader<*, *>>
|
public val builtInLoaders: List<PluginLoader<*, *>>
|
||||||
|
|
||||||
@ -56,9 +50,20 @@ public interface PluginManager {
|
|||||||
|
|
||||||
public fun unregisterPluginLoader(loader: PluginLoader<*, *>): Boolean
|
public fun unregisterPluginLoader(loader: PluginLoader<*, *>): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription]
|
||||||
|
*/
|
||||||
|
public val Plugin.description: PluginDescription
|
||||||
|
|
||||||
public companion object INSTANCE : PluginManager by PluginManagerImpl
|
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 class PluginMissingDependencyException : PluginResolutionException {
|
||||||
public constructor() : super()
|
public constructor() : super()
|
||||||
public constructor(message: String?) : super(message)
|
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?) : super(message)
|
||||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||||
public constructor(cause: Throwable?) : super(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 }
|
|
@ -20,6 +20,21 @@ import net.mamoe.yamlkt.YamlDynamicSerializer
|
|||||||
import java.io.File
|
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)
|
@Serializable(with = PluginKind.AsStringSerializer::class)
|
||||||
public enum class PluginKind {
|
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
|
@Serializable
|
||||||
public data class PluginDependency(
|
public data class PluginDependency(
|
||||||
|
@ -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 }
|
@ -88,7 +88,7 @@ public abstract class AbstractSetting : Setting, SettingImpl() {
|
|||||||
*
|
*
|
||||||
* @see JvmPlugin.loadSetting 通过 [JvmPlugin] 获取指定 [Setting] 实例.
|
* @see JvmPlugin.loadSetting 通过 [JvmPlugin] 获取指定 [Setting] 实例.
|
||||||
*/
|
*/
|
||||||
public interface Setting {
|
public interface Setting : ExperimentalSettingExtensions {
|
||||||
/**
|
/**
|
||||||
* 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪.
|
* 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪.
|
||||||
*/
|
*/
|
||||||
@ -113,6 +113,21 @@ public interface Setting {
|
|||||||
public fun setStorage(storage: SettingStorage)
|
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 ////
|
//// region Setting_value_primitives CODEGEN ////
|
||||||
|
|
||||||
public fun Setting.value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default)
|
public fun Setting.value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default)
|
||||||
|
@ -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()
|
private val managers: MutableMap<Long, MutableSet<Long>> by value()
|
||||||
|
|
||||||
internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf)
|
internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf)
|
||||||
@ -54,10 +54,12 @@ 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
|
override val autoSaveIntervalMillis: LongRange = 30.minutesToMillis..60.minutesToMillis
|
||||||
get() = 30.minutesToMillis..60.minutesToMillis
|
|
||||||
override val name: String get() = "ConsoleBuiltIns"
|
override val name: String get() = "ConsoleBuiltIns"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object ConsoleBuiltInSettingStorage :
|
internal object ConsoleBuiltInSettingStorage :
|
||||||
SettingStorage by MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader
|
SettingStorage by MiraiConsoleImplementationBridge.settingStorageForJarPluginLoader {
|
||||||
|
|
||||||
|
inline fun <reified T : Setting> load(): T = load(ConsoleBuiltInSettingHolder)
|
||||||
|
}
|
@ -42,21 +42,21 @@ public interface ResourceContainer {
|
|||||||
*/
|
*/
|
||||||
@JvmDefault
|
@JvmDefault
|
||||||
public fun getResource(name: String, charset: Charset): String =
|
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 {
|
public companion object {
|
||||||
/**
|
/**
|
||||||
* 使用 [Class.getResourceAsStream] 读取资源文件
|
* 使用 [Class.getResourceAsStream] 读取资源文件
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmName("byClass")
|
@JvmName("create")
|
||||||
public fun KClass<*>.asResourceContainer(): ResourceContainer = this.java.asResourceContainer()
|
public fun KClass<*>.asResourceContainer(): ResourceContainer = this.java.asResourceContainer()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用 [Class.getResourceAsStream] 读取资源文件
|
* 使用 [Class.getResourceAsStream] 读取资源文件
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmName("byClass")
|
@JvmName("create")
|
||||||
public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this)
|
public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import net.mamoe.mirai.console.setting.SettingStorage
|
|||||||
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mirai-console-pure 后端实现
|
* mirai-console-pure 后端实现
|
||||||
@ -47,7 +48,10 @@ import java.io.File
|
|||||||
class MiraiConsoleImplementationPure
|
class MiraiConsoleImplementationPure
|
||||||
@JvmOverloads constructor(
|
@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<*, *>> = Collections.unmodifiableList(
|
||||||
|
listOf(
|
||||||
|
DeferredPluginLoader { JarPluginLoader })
|
||||||
|
),
|
||||||
override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure,
|
override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure,
|
||||||
override val mainLogger: MiraiLogger = frontEnd.loggerFor("main"),
|
override val mainLogger: MiraiLogger = frontEnd.loggerFor("main"),
|
||||||
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,
|
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,
|
||||||
|
Loading…
Reference in New Issue
Block a user