Support loading PluginLoaders by ServiceLoader for each plugin;

Rearrange implementations
This commit is contained in:
Him188 2020-08-28 11:31:07 +08:00
parent dee8e44110
commit 5a5d45778a
12 changed files with 191 additions and 121 deletions

View File

@ -19,7 +19,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.util.childScopeContext
import net.mamoe.mirai.console.internal.data.builtin.childScopeContext
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter

View File

@ -24,9 +24,9 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.data.builtin.ConsoleDataScope
import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.ConsoleDataScope
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter

View File

@ -0,0 +1,46 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.internal.data.builtin
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys
import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault
import net.mamoe.mirai.console.data.getValue
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.util.BotManager
import net.mamoe.mirai.contact.User
internal object BotManagerImpl : BotManager {
override val User.isManager: Boolean get() = this.id in ManagersConfig[this.bot]
override fun Bot.removeManager(id: Long): Boolean {
return ManagersConfig[this].remove(id)
}
override val Bot.managers: List<Long>
get() = ManagersConfig[this].toList()
override fun Bot.addManager(id: Long): Boolean {
return ManagersConfig[this].add(id)
}
}
internal object ManagersConfig : AutoSavePluginConfig() {
override val saveName: String
get() = "Managers"
private val managers by value<MutableMap<Long, MutableSet<Long>>>().withEmptyDefault()
.mapKeys(Bot::getInstance, Bot::id)
internal operator fun get(bot: Bot): MutableSet<Long> = managers[bot]!!
}

View File

@ -0,0 +1,52 @@
/*
* 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.internal.data.builtin
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.utils.minutesToMillis
internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("ConsoleDataScope") {
private val data: Array<out PluginData> = arrayOf()
private val configs: Array<out PluginConfig> = arrayOf(ManagersConfig)
fun reloadAll() {
data.forEach { dt ->
ConsoleBuiltInPluginDataStorage.load(ConsoleBuiltInPluginDataHolder, dt)
}
configs.forEach { config ->
ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config)
}
}
}
internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginDataHolder") {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val name: String get() = "ConsoleBuiltIns"
}
internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginConfigHolder") {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val name: String get() = "ConsoleBuiltIns"
}
internal object ConsoleBuiltInPluginDataStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns
internal object ConsoleBuiltInPluginConfigStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.configStorageForBuiltIns

View File

@ -18,11 +18,11 @@ import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.data.createInstanceOrNull
import net.mamoe.mirai.console.internal.util.childScopeContext
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.PluginLoadException
import net.mamoe.mirai.console.plugin.jvm.*
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.childScopeContext
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.yamlkt.Yaml
import java.io.File
@ -48,7 +48,7 @@ internal object JarPluginLoaderImpl :
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
})
private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
internal val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
init { // delayed
coroutineContext[Job]!!.invokeOnCompletion {

View File

@ -15,18 +15,23 @@ import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.data.cast
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.internal.util.childScope
import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.util.childScope
import net.mamoe.mirai.utils.error
import net.mamoe.mirai.utils.info
import java.io.File
import java.nio.file.Path
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.ArrayList
import kotlin.streams.asSequence
internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsole.childScope("PluginManager") {
@ -123,8 +128,37 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
internal fun loadEnablePlugins() {
(loadAndEnableLoaderProviders() + _pluginLoaders.listAllPlugins().flatMap { it.second })
.sortByDependencies().loadAndEnableAllInOrder()
loadAndEnableLoaderProviders()
loadPluginLoaderProvidedByPlugins()
loadersLock.withLock {
_pluginLoaders.listAllPlugins().flatMap { it.second }
.sortByDependencies().loadAndEnableAllInOrder()
}
}
private fun loadPluginLoaderProvidedByPlugins() {
loadersLock.withLock {
JarPluginLoaderImpl.classLoader.pluginLoaders.asSequence()
.flatMap { (name, pluginClassLoader) ->
ServiceLoader.load(PluginLoader::class.java, pluginClassLoader)
.stream().asSequence()
.associateBy { name }
.asSequence()
}
.forEach { (name, provider) ->
val pluginLoader = kotlin.runCatching {
provider.get()
}.getOrElse {
logger.error(
{ "Could not load PluginLoader ${it::class.qualifiedNameOrTip} from plugin $name" },
it
)
return@forEach
}
_pluginLoaders.add(pluginLoader)
logger.info { "Successfully loaded PluginLoader ${pluginLoader::class.qualifiedNameOrTip} from plugin $name" }
}
}
}
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {

View File

@ -15,7 +15,7 @@ import java.net.URLClassLoader
internal class PluginsLoader(private val parentClassLoader: ClassLoader) {
private val loggerName = "PluginsLoader"
private val pluginLoaders = linkedMapOf<String, PluginClassLoader>()
internal val pluginLoaders = linkedMapOf<String, PluginClassLoader>()
private val classesCache = mutableMapOf<String, Class<*>>()
private val logger = MiraiConsole.newLogger(loggerName)

View File

@ -1,107 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.internal.util
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.*
import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys
import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.plugin.NamedSupervisorJob
import net.mamoe.mirai.console.util.BotManager
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.utils.minutesToMillis
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
internal object BotManagerImpl : BotManager {
/**
* 判断此用户是否为 console 管理员
*/
override val User.isManager: Boolean get() = this.id in ManagersConfig[this.bot]
override fun Bot.removeManager(id: Long): Boolean {
return ManagersConfig[this].remove(id)
}
override val Bot.managers: List<Long>
get() = ManagersConfig[this].toList()
override fun Bot.addManager(id: Long): Boolean {
return ManagersConfig[this].add(id)
}
}
@ValueName("Managers")
internal object ManagersConfig : AutoSavePluginConfig() {
override val saveName: String
get() = "Managers"
private val managers by value<MutableMap<Long, MutableSet<Long>>>().withEmptyDefault()
.mapKeys(Bot::getInstance, Bot::id)
internal operator fun get(bot: Bot): MutableSet<Long> = managers[bot]!!
}
internal fun CoroutineContext.overrideWithSupervisorJob(name: String? = null): CoroutineContext =
this + NamedSupervisorJob(name ?: "<unnamed>", this[Job])
internal fun CoroutineScope.childScope(
name: String? = null,
context: CoroutineContext = EmptyCoroutineContext
): CoroutineScope =
CoroutineScope(this.childScopeContext(name, context))
internal fun CoroutineScope.childScopeContext(
name: String? = null,
context: CoroutineContext = EmptyCoroutineContext
): CoroutineContext =
this.coroutineContext.overrideWithSupervisorJob(name) + context.let {
if (name != null) it + CoroutineName(name)
else it
}
internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("ConsoleDataScope") {
private val data: Array<out PluginData> = arrayOf()
private val configs: Array<out PluginConfig> = arrayOf(ManagersConfig)
fun reloadAll() {
data.forEach { dt ->
ConsoleBuiltInPluginDataStorage.load(ConsoleBuiltInPluginDataHolder, dt)
}
configs.forEach { config ->
ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config)
}
}
}
internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginDataHolder") {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val name: String get() = "ConsoleBuiltIns"
}
internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginConfigHolder") {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val name: String get() = "ConsoleBuiltIns"
}
internal object ConsoleBuiltInPluginDataStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns
internal object ConsoleBuiltInPluginConfigStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.configStorageForBuiltIns

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.register
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import java.io.File
import java.util.*
/**
* 插件加载器.
@ -25,11 +26,16 @@ import java.io.File
*
* 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护.
*
* ### 内建加载器
* ## 内建加载器
* - [JarPluginLoader] Jar 插件加载器
*
* ### 扩展加载器
* 插件被允许扩展一个加载器 可通过 [PluginManager.register]
* ## 扩展加载器
* 插件被允许扩展一个加载器.
* Console 使用 [ServiceLoader] 加载 [PluginLoader] 的实例.
* 插件也可通过 [PluginManager.register] 手动注册, 然而这是不推荐的.
*
* ### 实现扩展加载器
* 直接实现接口 [PluginLoader] [FilePluginLoader], 并添加 [ServiceLoader] 相关资源文件即可.
*
* @see JarPluginLoader Jar 插件加载器
* @see PluginManager.register 注册一个扩展的插件加载器

View File

@ -14,8 +14,10 @@ package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File
import java.nio.file.Path
import java.util.*
/**
* 插件管理器.
@ -102,10 +104,11 @@ public interface PluginManager {
public val pluginLoaders: List<PluginLoader<*, *>>
/**
* 注册一个扩展的插件加载器
* 手动注册一个扩展的插件加载器. 在启动时会通过 [ServiceLoader] 加载, 但也可以手动注册.
*
* @see PluginLoader 插件加载器
*/
@ConsoleExperimentalAPI
public fun PluginLoader<*, *>.register(): Boolean
/**
@ -113,6 +116,7 @@ public interface PluginManager {
*
* @see PluginLoader 插件加载器
*/
@ConsoleExperimentalAPI
public fun PluginLoader<*, *>.unregister(): Boolean
/**

View File

@ -11,7 +11,6 @@
package net.mamoe.mirai.console.plugin.jvm
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@ -27,7 +26,6 @@ public abstract class KotlinPlugin @JvmOverloads constructor(
/**
* 在内存动态加载的插件. 此为预览版本 API.
*/
@ConsoleExperimentalAPI
public abstract class KotlinMemoryPlugin @JvmOverloads constructor(
description: JvmPluginDescription,
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext

View File

@ -0,0 +1,37 @@
/*
* 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:JvmName("CoroutineScopeUtils")
package net.mamoe.mirai.console.util
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.console.internal.plugin.NamedSupervisorJob
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
internal fun CoroutineContext.overrideWithSupervisorJob(name: String? = null): CoroutineContext =
this + NamedSupervisorJob(name ?: "<unnamed>", this[Job])
internal fun CoroutineScope.childScope(
name: String? = null,
context: CoroutineContext = EmptyCoroutineContext
): CoroutineScope =
CoroutineScope(this.childScopeContext(name, context))
internal fun CoroutineScope.childScopeContext(
name: String? = null,
context: CoroutineContext = EmptyCoroutineContext
): CoroutineContext =
this.coroutineContext.overrideWithSupervisorJob(name) + context.let {
if (name != null) it + CoroutineName(name)
else it
}