mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-22 13:46:13 +08:00
Console: add PluginBase::onReload, plugin now can handle reload event
This commit is contained in:
parent
fdf2cec4d5
commit
98e52f0c63
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -279,8 +279,7 @@ object DefaultCommands {
|
||||
alias = listOf("reloadPlugins")
|
||||
description = "重新加载全部插件"
|
||||
onCommand{
|
||||
PluginManager.disablePlugins()
|
||||
PluginManager.loadPlugins()
|
||||
PluginManager.reloadPlugins()
|
||||
sendMessage("重新加载完成")
|
||||
true
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user