Plugin infrastructure

This commit is contained in:
Him188 2020-05-14 21:18:58 +08:00
parent 3efebfb627
commit 027286a226
8 changed files with 551 additions and 451 deletions

View File

@ -13,7 +13,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.io.charsets.Charset
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@ -43,7 +42,7 @@ interface IMiraiConsole : CoroutineScope {
val mainLogger: MiraiLogger
}
object MiraiConsole : CoroutineScope, IMiraiConsole, CommandOwner {
object MiraiConsole : CoroutineScope, IMiraiConsole {
private lateinit var instance: IMiraiConsole
/** 由前端调用 */

View File

@ -4,6 +4,7 @@
package net.mamoe.mirai.console.command
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import java.util.*
@ -11,7 +12,12 @@ import java.util.concurrent.locks.ReentrantLock
typealias CommandFullName = Array<out Any>
interface CommandOwner
sealed class CommandOwner
abstract class PluginCommandOwner(plugin: PluginBase) : CommandOwner()
// 由前端实现
internal abstract class ConsoleCommandOwner : CommandOwner()
val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }

View File

@ -0,0 +1,436 @@
/*
* 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.plugins
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.description
import net.mamoe.mirai.console.encodeToString
import net.mamoe.mirai.utils.LockFreeLinkedList
import java.io.File
import java.io.InputStream
import java.net.JarURLConnection
import java.net.URL
import java.util.jar.JarFile
sealed class JarPlugin : Plugin(), CoroutineScope {
internal lateinit var _description: JarPluginDescription
final override val description: PluginDescription get() = _description
final override val loader: JarPluginLoader get() = JarPluginLoader
}
@Serializable
internal class JarPluginDescription(
override val name: String,
override val author: String,
override val version: String,
override val info: String,
override val depends: List<String>
) : PluginDescription
abstract class JavaPlugin : JarPlugin()
abstract class KotlinPlugin : JarPlugin()
/**
* 内建的 Jar (JVM) 插件加载器
*/
object JarPluginLoader : PluginLoader<JarPlugin> {
override val list: List<JarPlugin>
get() = TODO("Not yet implemented")
override fun load(plugin: JarPlugin) {
TODO("Not yet implemented")
}
override fun enable(plugin: JarPlugin) {
TODO("Not yet implemented")
}
}
object PluginManagerOld {
/**
* 通过插件获取介绍
* @see description
*/
fun getPluginDescription(base: PluginBase): PluginDescription {
nameToPluginBaseMap.forEach { (s, pluginBase) ->
if (pluginBase == base) {
return pluginDescriptions[s]!!
}
}
error("can not find plugin description")
}
/**
* 获取所有插件摘要
*/
fun getAllPluginDescriptions(): Collection<PluginDescription> {
return pluginDescriptions.values
}
/**
* 关闭所有插件
*/
@JvmOverloads
fun disablePlugins(throwable: CancellationException? = null) {
pluginsSequence.forEach { plugin ->
plugin.unregisterAllCommands()
plugin.disable(throwable)
}
nameToPluginBaseMap.clear()
pluginDescriptions.clear()
pluginsLoader.clear()
pluginsSequence.clear()
}
/**
* 重载所有插件
*/
fun reloadPlugins() {
pluginsSequence.forEach {
it.disable()
}
loadPlugins(false)
}
/**
* 尝试加载全部插件
*/
fun loadPlugins(clear: Boolean = true) = loadPluginsImpl(clear)
//////////////////
//// internal ////
//////////////////
internal val pluginsPath = (MiraiConsole.path + "/plugins/").replace("//", "/").also {
File(it).mkdirs()
}
private val logger = MiraiConsole.newLogger("Plugin Manager")
/**
* 加载成功的插件, 名字->插件
*/
internal val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
/**
* 加载成功的插件, 名字->插件摘要
*/
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
/**
* 加载插件的PluginsLoader
*/
private val pluginsLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
/**
* 插件优先级队列
* 任何操作应该按这个Sequence顺序进行
* 他的优先级取决于依赖,
* 在这个队列中, 被依赖的插件会在依赖的插件之前
*/
private val pluginsSequence: LockFreeLinkedList<PluginBase> = LockFreeLinkedList()
/**
* 广播Command方法
*/
internal fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
pluginsSequence.forEach {
try {
it.onCommand(command, sender, args)
} catch (e: Throwable) {
logger.info(e)
}
}
}
@Volatile
internal var lastPluginName: String = ""
/**
* 判断文件名/插件名是否已加载
*/
private fun isPluginLoaded(file: File, name: String): Boolean {
pluginDescriptions.forEach {
if (it.key == name || it.value.file == file) {
return true
}
}
return false
}
/**
* 寻找所有安装的插件在文件夹, 并将它读取, 记录位置
* 这个不等同于加载的插件, 可以理解为还没有加载的插件
*/
internal data class FindPluginsResult(
val pluginsLocation: MutableMap<String, File>,
val pluginsFound: MutableMap<String, PluginDescription>
)
internal fun findPlugins(): FindPluginsResult {
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml == null) {
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
} else {
try {
val description = PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().let {
val res = it.inputStream.use { input ->
input.readBytes().encodeToString()
}
// 关闭jarFile解决热更新插件问题
(it as JarURLConnection).jarFile.close()
res
}, file
)
if (!isPluginLoaded(file, description.name)) {
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}
} catch (e: Exception) {
logger.info(e)
}
}
}
}
return FindPluginsResult(pluginsLocation, pluginsFound)
}
internal fun loadPluginsImpl(clear: Boolean = true) {
logger.info("""开始加载${pluginsPath}下的插件""")
val findPluginsResult = findPlugins()
val pluginsFound = findPluginsResult.pluginsFound
val pluginsLocation = findPluginsResult.pluginsLocation
//不仅要解决A->B->C->A, 还要解决A->B->A->A
fun checkNoCircularDepends(
target: PluginDescription,
needDepends: List<String>,
existDepends: MutableList<String>
) {
if (!target.noCircularDepend) {
return
}
existDepends.add(target.name)
if (needDepends.any { existDepends.contains(it) }) {
target.noCircularDepend = false
}
existDepends.addAll(needDepends)
needDepends.forEach {
if (pluginsFound.containsKey(it)) {
checkNoCircularDepends(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends)
}
}
}
pluginsFound.values.forEach {
checkNoCircularDepends(it, it.depends, mutableListOf())
}
//load plugin individually
fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency")
return false
}
if (description.loaded || nameToPluginBaseMap.containsKey(description.name)) {
return true
}
description.depends.forEach { dependent ->
if (!pluginsFound.containsKey(dependent)) {
logger.error("Failed to load plugin " + description.name + " because it need " + dependent + " as dependency")
return false
}
val depend = pluginsFound[dependent]!!
if (!loadPlugin(depend)) {//先加载depend
logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load")
return false
}
}
logger.info("loading plugin " + description.name)
val jarFile = pluginsLocation[description.name]!!
val pluginClass = try {
pluginsLoader.loadPluginMainClassByJarFile(description.name, description.basePath, jarFile)
} catch (e: ClassNotFoundException) {
pluginsLoader.loadPluginMainClassByJarFile(description.name, "${description.basePath}Kt", jarFile)
}
val subClass = pluginClass.asSubclass(PluginBase::class.java)
lastPluginName = description.name
val plugin: PluginBase =
subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply {
kotlin.runCatching {
this.isAccessible = true
}
}.newInstance()
plugin.dataFolder // initialize right now
description.loaded = true
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
logger.info(description.info)
nameToPluginBaseMap[description.name] = plugin
pluginDescriptions[description.name] = description
plugin.pluginName = description.name
pluginsSequence.addLast(plugin)//按照实际加载顺序加入队列
return true
}
if (clear) {
//清掉优先级队列, 来重新填充
pluginsSequence.clear()
}
pluginsFound.values.forEach {
try {
// 尝试加载插件
loadPlugin(it)
} catch (e: Throwable) {
pluginsLoader.remove(it.name)
when (e) {
is ClassCastException -> logger.error(
"failed to load plugin " + it.name + " , Main class does not extends PluginBase",
e
)
is ClassNotFoundException -> logger.error(
"failed to load plugin " + it.name + " , Main class not found under " + it.basePath,
e
)
is NoClassDefFoundError -> logger.error(
"failed to load plugin " + it.name + " , dependent class not found.",
e
)
else -> logger.error("failed to load plugin " + it.name, e)
}
}
}
pluginsSequence.forEach {
try {
it.load()
} catch (ignored: Throwable) {
logger.info(ignored)
logger.info(it.pluginName + " failed to load, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
}
pluginsSequence.forEach {
try {
it.enable()
} catch (ignored: Throwable) {
logger.info(ignored)
logger.info(it.pluginName + " failed to enable, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
}
logger.info("""加载了${nameToPluginBaseMap.size}个插件""")
}
private fun disablePlugin(
plugin: PluginBase,
exception: CancellationException? = null
) {
plugin.unregisterAllCommands()
plugin.disable(exception)
nameToPluginBaseMap.remove(plugin.pluginName)
pluginDescriptions.remove(plugin.pluginName)
pluginsLoader.remove(plugin.pluginName)
pluginsSequence.remove(plugin)
}
/**
* 根据插件名字找Jar的文件
* null => 没找到
* 这里的url的jarFile没关热更新插件可能出事
*/
internal fun getJarFileByName(pluginName: String): File? {
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml != null) {
val description =
PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use {
it.readBytes().encodeToString()
}, file
)
if (description.name.toLowerCase() == pluginName.toLowerCase()) {
return file
}
}
}
}
return null
}
/**
* 根据插件名字找Jar中的文件
* null => 没找到
* 这里的url的jarFile没关热更新插件可能出事
*/
internal fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
val jarFile = getJarFileByName(pluginName) ?: return null
val jar = JarFile(jarFile)
val toFindFile =
jar.entries().asSequence().filter { it.name == toFind }.firstOrNull() ?: return null
return URL("jar:file:" + jarFile.absoluteFile + "!/" + toFindFile.name).openConnection().inputStream
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.plugins
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.CoroutineContext
/**
* 插件信息
*/
interface PluginDescription {
val name: String
val author: String
val version: String
val info: String
val depends: List<String>
}
/**
* 插件基类.
*
* 内建的插件类型:
* - [JarPlugin]
*/
abstract class Plugin : CoroutineScope {
abstract val description: PluginDescription
abstract val loader: PluginLoader<*>
@OptIn(MiraiExperimentalAPI::class)
val logger: MiraiLogger by lazy { MiraiConsole.newLogger(description.name) }
override val coroutineContext: CoroutineContext
get() = SupervisorJob(MiraiConsole.coroutineContext[Job]) + CoroutineExceptionHandler { _, throwable ->
logger.error(throwable)
}
open fun onLoaded() {}
open fun onDisabled() {}
open fun onEnabled() {}
}

View File

@ -13,7 +13,6 @@ package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.*
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.events.EventListener
import net.mamoe.mirai.console.scheduler.PluginScheduler
@ -27,7 +26,7 @@ import kotlin.coroutines.EmptyCoroutineContext
* 所有插件的基类
*/
abstract class PluginBase
@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope, CommandOwner {
@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope {
final override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob()
/**
@ -166,64 +165,3 @@ abstract class PluginBase
internal var pluginName: String = ""
}
/**
* 插件描述
* @see PluginBase.description
*/
class PluginDescription(
val file: File,
val name: String,
val author: String,
val basePath: String,
val version: String,
val info: String,
val depends: List<String>,//插件的依赖
internal var loaded: Boolean = false,
internal var noCircularDepend: Boolean = true
) {
override fun toString(): String {
return "name: $name\nauthor: $author\npath: $basePath\nver: $version\ninfo: $info\ndepends: $depends"
}
companion object {
@OptIn(ToBeRemoved::class)
fun readFromContent(content_: String, file: File): PluginDescription {
with(Config.load(content_, "yml")) {
try {
return PluginDescription(
file = file,
name = this.getString("name"),
author = kotlin.runCatching {
this.getString("author")
}.getOrElse {
"unknown"
},
basePath = kotlin.runCatching {
this.getString("path")
}.getOrElse {
this.getString("main")
},
version = kotlin.runCatching {
this.getString("version")
}.getOrElse {
"unknown"
},
info = kotlin.runCatching {
this.getString("info")
}.getOrElse {
"unknown"
},
depends = kotlin.runCatching {
this.getStringList("depends")
}.getOrElse {
listOf()
}
)
} catch (e: Exception) {
error("Failed to read Plugin.YML")
}
}
}
}
}

View File

@ -0,0 +1,29 @@
package net.mamoe.mirai.console.plugins
/**
* 插件加载器
*
* @see JarPluginLoader 内建的 Jar (JVM) 插件加载器.
*/
interface PluginLoader<P : Plugin> {
val list: List<P>
fun loadAll() = list.forEach(::load)
fun enableAll() = list.forEach(::enable)
fun unloadAll() = list.forEach(::unload)
fun reloadAll() = list.forEach(::reload)
val isUnloadSupported: Boolean
get() = false
fun load(plugin: P)
fun enable(plugin: P)
fun unload(plugin: P) {
error("NotImplemented")
}
fun reload(plugin: P) {
unload(plugin)
load(plugin)
}
}

View File

@ -0,0 +1,24 @@
package net.mamoe.mirai.console.plugins
object PluginManager {
private val _loaders: MutableSet<PluginLoader<*>> = mutableSetOf()
val loaders: Set<PluginLoader<*>> get() = _loaders
fun registerPluginLoader(loader: PluginLoader<*>) {
_loaders.add(loader)
}
fun unregisterPluginLoader(loader: PluginLoader<*>) {
_loaders.remove(loader)
}
fun loadPlugins() {
loaders.forEach(PluginLoader<*>::loadAll)
}
fun enablePlugins() {
loaders.forEach(PluginLoader<*>::enableAll)
}
}

View File

@ -11,390 +11,5 @@
package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.CancellationException
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.description
import net.mamoe.mirai.console.command.unregisterAllCommands
import net.mamoe.mirai.console.encodeToString
import net.mamoe.mirai.utils.LockFreeLinkedList
import java.io.File
import java.io.InputStream
import java.net.JarURLConnection
import java.net.URL
import java.util.jar.JarFile
val PluginBase.description: PluginDescription get() = TODO()
object PluginManagerOld {
/**
* 通过插件获取介绍
* @see description
*/
fun getPluginDescription(base: PluginBase): PluginDescription {
nameToPluginBaseMap.forEach { (s, pluginBase) ->
if (pluginBase == base) {
return pluginDescriptions[s]!!
}
}
error("can not find plugin description")
}
/**
* 获取所有插件摘要
*/
fun getAllPluginDescriptions(): Collection<PluginDescription> {
return pluginDescriptions.values
}
/**
* 关闭所有插件
*/
@JvmOverloads
fun disablePlugins(throwable: CancellationException? = null) {
pluginsSequence.forEach { plugin ->
plugin.unregisterAllCommands()
plugin.disable(throwable)
}
nameToPluginBaseMap.clear()
pluginDescriptions.clear()
pluginsLoader.clear()
pluginsSequence.clear()
}
/**
* 重载所有插件
*/
fun reloadPlugins() {
pluginsSequence.forEach {
it.disable()
}
loadPlugins(false)
}
/**
* 尝试加载全部插件
*/
fun loadPlugins(clear: Boolean = true) = loadPluginsImpl(clear)
//////////////////
//// internal ////
//////////////////
internal val pluginsPath = (MiraiConsole.path + "/plugins/").replace("//", "/").also {
File(it).mkdirs()
}
private val logger = MiraiConsole.newLogger("Plugin Manager")
/**
* 加载成功的插件, 名字->插件
*/
internal val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
/**
* 加载成功的插件, 名字->插件摘要
*/
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
/**
* 加载插件的PluginsLoader
*/
private val pluginsLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
/**
* 插件优先级队列
* 任何操作应该按这个Sequence顺序进行
* 他的优先级取决于依赖,
* 在这个队列中, 被依赖的插件会在依赖的插件之前
*/
private val pluginsSequence: LockFreeLinkedList<PluginBase> = LockFreeLinkedList()
/**
* 广播Command方法
*/
internal fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
pluginsSequence.forEach {
try {
it.onCommand(command, sender, args)
} catch (e: Throwable) {
logger.info(e)
}
}
}
@Volatile
internal var lastPluginName: String = ""
/**
* 判断文件名/插件名是否已加载
*/
private fun isPluginLoaded(file: File, name: String): Boolean {
pluginDescriptions.forEach {
if (it.key == name || it.value.file == file) {
return true
}
}
return false
}
/**
* 寻找所有安装的插件在文件夹, 并将它读取, 记录位置
* 这个不等同于加载的插件, 可以理解为还没有加载的插件
*/
internal data class FindPluginsResult(
val pluginsLocation: MutableMap<String, File>,
val pluginsFound: MutableMap<String, PluginDescription>
)
internal fun findPlugins(): FindPluginsResult {
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml == null) {
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
} else {
try {
val description = PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().let {
val res = it.inputStream.use { input ->
input.readBytes().encodeToString()
}
// 关闭jarFile解决热更新插件问题
(it as JarURLConnection).jarFile.close()
res
}, file
)
if (!isPluginLoaded(file, description.name)) {
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}
} catch (e: Exception) {
logger.info(e)
}
}
}
}
return FindPluginsResult(pluginsLocation, pluginsFound)
}
internal fun loadPluginsImpl(clear: Boolean = true) {
logger.info("""开始加载${pluginsPath}下的插件""")
val findPluginsResult = findPlugins()
val pluginsFound = findPluginsResult.pluginsFound
val pluginsLocation = findPluginsResult.pluginsLocation
//不仅要解决A->B->C->A, 还要解决A->B->A->A
fun checkNoCircularDepends(
target: PluginDescription,
needDepends: List<String>,
existDepends: MutableList<String>
) {
if (!target.noCircularDepend) {
return
}
existDepends.add(target.name)
if (needDepends.any { existDepends.contains(it) }) {
target.noCircularDepend = false
}
existDepends.addAll(needDepends)
needDepends.forEach {
if (pluginsFound.containsKey(it)) {
checkNoCircularDepends(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends)
}
}
}
pluginsFound.values.forEach {
checkNoCircularDepends(it, it.depends, mutableListOf())
}
//load plugin individually
fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency")
return false
}
if (description.loaded || nameToPluginBaseMap.containsKey(description.name)) {
return true
}
description.depends.forEach { dependent ->
if (!pluginsFound.containsKey(dependent)) {
logger.error("Failed to load plugin " + description.name + " because it need " + dependent + " as dependency")
return false
}
val depend = pluginsFound[dependent]!!
if (!loadPlugin(depend)) {//先加载depend
logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load")
return false
}
}
logger.info("loading plugin " + description.name)
val jarFile = pluginsLocation[description.name]!!
val pluginClass = try {
pluginsLoader.loadPluginMainClassByJarFile(description.name, description.basePath, jarFile)
} catch (e: ClassNotFoundException) {
pluginsLoader.loadPluginMainClassByJarFile(description.name, "${description.basePath}Kt", jarFile)
}
val subClass = pluginClass.asSubclass(PluginBase::class.java)
lastPluginName = description.name
val plugin: PluginBase =
subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply {
kotlin.runCatching {
this.isAccessible = true
}
}.newInstance()
plugin.dataFolder // initialize right now
description.loaded = true
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
logger.info(description.info)
nameToPluginBaseMap[description.name] = plugin
pluginDescriptions[description.name] = description
plugin.pluginName = description.name
pluginsSequence.addLast(plugin)//按照实际加载顺序加入队列
return true
}
if (clear) {
//清掉优先级队列, 来重新填充
pluginsSequence.clear()
}
pluginsFound.values.forEach {
try {
// 尝试加载插件
loadPlugin(it)
} catch (e: Throwable) {
pluginsLoader.remove(it.name)
when (e) {
is ClassCastException -> logger.error(
"failed to load plugin " + it.name + " , Main class does not extends PluginBase",
e
)
is ClassNotFoundException -> logger.error(
"failed to load plugin " + it.name + " , Main class not found under " + it.basePath,
e
)
is NoClassDefFoundError -> logger.error(
"failed to load plugin " + it.name + " , dependent class not found.",
e
)
else -> logger.error("failed to load plugin " + it.name, e)
}
}
}
pluginsSequence.forEach {
try {
it.load()
} catch (ignored: Throwable) {
logger.info(ignored)
logger.info(it.pluginName + " failed to load, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
}
pluginsSequence.forEach {
try {
it.enable()
} catch (ignored: Throwable) {
logger.info(ignored)
logger.info(it.pluginName + " failed to enable, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
}
logger.info("""加载了${nameToPluginBaseMap.size}个插件""")
}
private fun disablePlugin(
plugin: PluginBase,
exception: CancellationException? = null
) {
plugin.unregisterAllCommands()
plugin.disable(exception)
nameToPluginBaseMap.remove(plugin.pluginName)
pluginDescriptions.remove(plugin.pluginName)
pluginsLoader.remove(plugin.pluginName)
pluginsSequence.remove(plugin)
}
/**
* 根据插件名字找Jar的文件
* null => 没找到
* 这里的url的jarFile没关热更新插件可能出事
*/
internal fun getJarFileByName(pluginName: String): File? {
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml != null) {
val description =
PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use {
it.readBytes().encodeToString()
}, file
)
if (description.name.toLowerCase() == pluginName.toLowerCase()) {
return file
}
}
}
}
return null
}
/**
* 根据插件名字找Jar中的文件
* null => 没找到
* 这里的url的jarFile没关热更新插件可能出事
*/
internal fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
val jarFile = getJarFileByName(pluginName) ?: return null
val jar = JarFile(jarFile)
val toFindFile =
jar.entries().asSequence().filter { it.name == toFind }.firstOrNull() ?: return null
return URL("jar:file:" + jarFile.absoluteFile + "!/" + toFindFile.name).openConnection().inputStream
}
}