Console: add PluginBase::onReload, plugin now can handle reload event

This commit is contained in:
PeratX 2020-04-12 12:43:57 +08:00
parent fdf2cec4d5
commit 98e52f0c63
5 changed files with 138 additions and 61 deletions

View File

@ -160,8 +160,7 @@ class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
fun reloadPlugins() {
with(PluginManager) {
disablePlugins()
loadPlugins()
reloadPlugins()
}
fire(ReloadEvent) // 广播插件重载事件
@ -201,4 +200,4 @@ class GraphicalLoginSolver : LoginSolver() {
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}

View File

@ -279,8 +279,7 @@ object DefaultCommands {
alias = listOf("reloadPlugins")
description = "重新加载全部插件"
onCommand{
PluginManager.disablePlugins()
PluginManager.loadPlugins()
PluginManager.reloadPlugins()
sendMessage("重新加载完成")
true
}

View File

@ -14,9 +14,7 @@ package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.JCommandManager
import net.mamoe.mirai.console.events.EventListener
import net.mamoe.mirai.console.scheduler.PluginScheduler
import net.mamoe.mirai.console.scheduler.SchedulerTaskManagerInstance
@ -24,7 +22,6 @@ import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SimpleLogger
import java.io.File
import java.io.InputStream
import java.net.URLClassLoader
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@ -37,6 +34,9 @@ abstract class PluginBase
private val supervisorJob = SupervisorJob()
final override val coroutineContext: CoroutineContext = coroutineContext + supervisorJob
private var loaded = false
private var enabled = false
/**
* 插件被分配的数据目录数据目录会与插件名称同名
*/
@ -66,6 +66,13 @@ abstract class PluginBase
}
/**
* 当Console reload时被调用
*/
open fun onReload(): Boolean {
return true
}
/**
* 当任意指令被使用时调用.
*
@ -117,28 +124,50 @@ abstract class PluginBase
// internal
internal fun load() {
if (!loaded) {
onLoad()
loaded = true
}
}
internal fun enable() {
this.onEnable()
if (!enabled) {
onEnable()
enabled = true
}
}
internal fun disable(throwable: CancellationException? = null) {
this.coroutineContext[Job]!!.cancelChildren(throwable)
try {
this.onDisable()
} catch (e: Exception) {
logger.info(e)
if (enabled) {
this.coroutineContext[Job]!!.cancelChildren(throwable)
try {
onDisable()
} catch (e: Exception) {
logger.error(e)
}
enabled = false
}
}
internal fun reload(): Boolean {
try {
return onReload()
} catch (e: Exception) {
logger.error(e)
}
return true
}
internal var pluginName: String = ""
/**
* Java API Scheduler
*/
private var scheduler:PluginScheduler? = null
fun getScheduler():PluginScheduler{
if(scheduler === null){
private var scheduler: PluginScheduler? = null
fun getScheduler(): PluginScheduler {
if (scheduler === null) {
scheduler = SchedulerTaskManagerInstance.getPluginScheduler(this)
}
return scheduler!!
@ -147,9 +176,9 @@ abstract class PluginBase
/**
* Java API EventListener
*/
private var eventListener:EventListener? = null
fun getEventListener():EventListener{
if(eventListener === null){
private var eventListener: EventListener? = null
fun getEventListener(): EventListener {
if (eventListener === null) {
eventListener = EventListener(this)
}
return eventListener!!
@ -161,6 +190,7 @@ abstract class PluginBase
* 插件描述
*/
class PluginDescription(
val file: File,
val name: String,
val author: String,
val basePath: String,
@ -173,11 +203,13 @@ class PluginDescription(
override fun toString(): String {
return "name: $name\nauthor: $author\npath: $basePath\nver: $version\ninfo: $info\ndepends: $depends"
}
companion object {
fun readFromContent(content_: String): PluginDescription {
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")

View File

@ -94,12 +94,9 @@ object PluginManager {
return pluginDescriptions.values
}
@Volatile
internal var lastPluginName: String = ""
/**
* 寻找所有安装的插件在文件夹, 并将它读取, 记录位置
* 这个不等同于加载的插件, 可以理解为还没有加载的插件
@ -109,7 +106,19 @@ object PluginManager {
val pluginsFound: MutableMap<String, PluginDescription>
)
internal fun findPlugins():FindPluginsResult{
/**
* 判断文件名/插件名是否已加载
*/
private fun isPluginLoaded(file: File, name: String): Boolean {
pluginDescriptions.forEach {
if (it.key == name || it.value.file == file) {
return true
}
}
return false
}
internal fun findPlugins(): FindPluginsResult {
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
@ -132,9 +141,12 @@ object PluginManager {
// 关闭jarFile解决热更新插件问题
(it as JarURLConnection).jarFile.close()
res
})
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}, file
)
if (!isPluginLoaded(file, description.name)) {
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}
} catch (e: Exception) {
logger.info(e)
}
@ -190,7 +202,7 @@ object PluginManager {
return false
}
if(description.loaded || nameToPluginBaseMap.containsKey(description.name)){
if (description.loaded || nameToPluginBaseMap.containsKey(description.name)) {
return true
}
@ -210,10 +222,10 @@ object PluginManager {
logger.info("loading plugin " + description.name)
val jarFile = pluginsLocation[description.name]!!
val pluginClass = try{
pluginsLoader.loadPluginMainClassByJarFile(description.name,description.basePath,jarFile)
val pluginClass = try {
pluginsLoader.loadPluginMainClassByJarFile(description.name, description.basePath, jarFile)
} catch (e: ClassNotFoundException) {
pluginsLoader.loadPluginMainClassByJarFile(description.name,"${description.basePath}Kt",jarFile)
pluginsLoader.loadPluginMainClassByJarFile(description.name, "${description.basePath}Kt", jarFile)
}
val subClass = pluginClass.asSubclass(PluginBase::class.java)
@ -241,16 +253,25 @@ object PluginManager {
pluginsSequence.clear()
pluginsFound.values.forEach {
try{
try {
// 尝试加载插件
loadPlugin(it)
}catch (e: Throwable) {
} 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)
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)
}
}
}
@ -258,14 +279,14 @@ object PluginManager {
pluginsSequence.forEach {
try {
it.onLoad()
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, ignored)
} else {
disablePlugin(it)
}
}
@ -279,8 +300,8 @@ object PluginManager {
logger.info(it.pluginName + " failed to enable, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it,ignored)
}else{
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
@ -290,9 +311,9 @@ object PluginManager {
}
private fun disablePlugin(
plugin:PluginBase,
plugin: PluginBase,
exception: CancellationException? = null
){
) {
CommandManager.clearPluginCommands(plugin)
plugin.disable(exception)
nameToPluginBaseMap.remove(plugin.pluginName)
@ -314,6 +335,15 @@ object PluginManager {
pluginsSequence.clear()
}
fun reloadPlugins() {
pluginsSequence.forEach {
if (it.reload()) {
disablePlugin(it)
}
}
loadPlugins()
}
/**
* 根据插件名字找Jar的文件
@ -331,7 +361,8 @@ object PluginManager {
PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use {
it.readBytes().encodeToString()
})
}, file
)
if (description.name.toLowerCase() == pluginName.toLowerCase()) {
return file
}

View File

@ -1,14 +1,22 @@
/*
* 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 net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.utils.SimpleLogger
import java.io.File
import java.io.IOException
import java.net.URLClassLoader
internal class PluginsLoader(private val parentClassLoader: ClassLoader) {
private val loggerName = "PluginsLoader"
private val pluginLoaders = linkedMapOf<String,PluginClassLoader>()
private val pluginLoaders = linkedMapOf<String, PluginClassLoader>()
private val classesCache = mutableMapOf<String, Class<*>>()
private val logger = SimpleLogger(loggerName) { p, message, e ->
MiraiConsole.logger(p, "[${loggerName}]", 0, message)
@ -20,15 +28,15 @@ internal class PluginsLoader(private val parentClassLoader: ClassLoader) {
*/
fun clear() {
val iterator = pluginLoaders.iterator()
while(iterator.hasNext()){
while (iterator.hasNext()) {
val plugin = iterator.next()
var cl = ""
try {
cl = plugin.value.toString()
plugin.value.close()
iterator.remove()
}catch (e: Throwable){
logger.error("Plugin(${plugin.key}) can't not close its ClassLoader(${cl})",e)
} catch (e: Throwable) {
logger.error("Plugin(${plugin.key}) can't not close its ClassLoader(${cl})", e)
}
}
classesCache.clear()
@ -43,16 +51,19 @@ internal class PluginsLoader(private val parentClassLoader: ClassLoader) {
return true
}
fun loadPluginMainClassByJarFile(pluginName:String, mainClass: String, jarFile:File): Class<*> {
fun loadPluginMainClassByJarFile(pluginName: String, mainClass: String, jarFile: File): Class<*> {
try {
if(!pluginLoaders.containsKey(pluginName)){
pluginLoaders[pluginName] = PluginClassLoader(pluginName,jarFile, this, parentClassLoader)
if (!pluginLoaders.containsKey(pluginName)) {
pluginLoaders[pluginName] = PluginClassLoader(pluginName, jarFile, this, parentClassLoader)
}
return pluginLoaders[pluginName]!!.loadClass(mainClass)
}catch (e : ClassNotFoundException){
throw ClassNotFoundException("PluginsClassLoader(${pluginName}) can't load this pluginMainClass:${mainClass}",e)
}catch (e : Throwable){
throw Throwable("init or load class error",e)
} catch (e: ClassNotFoundException) {
throw ClassNotFoundException(
"PluginsClassLoader(${pluginName}) can't load this pluginMainClass:${mainClass}",
e
)
} catch (e: Throwable) {
throw Throwable("init or load class error", e)
}
}
@ -87,7 +98,12 @@ internal class PluginsLoader(private val parentClassLoader: ClassLoader) {
}
}
internal class PluginClassLoader(private val pluginName: String,files: File, private val pluginsLoader: PluginsLoader, parent: ClassLoader) :
internal class PluginClassLoader(
private val pluginName: String,
files: File,
private val pluginsLoader: PluginsLoader,
parent: ClassLoader
) :
URLClassLoader(arrayOf((files.toURI().toURL())), parent) {
private val classesCache = mutableMapOf<String, Class<*>?>()
@ -125,4 +141,4 @@ internal class PluginClassLoader(private val pluginName: String,files: File, pri
super.close()
classesCache.clear()
}
}
}