diff --git a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/controller/MiraiGraphicalUIController.kt b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/controller/MiraiGraphicalUIController.kt index d1c0921b3..4fced2c38 100644 --- a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/controller/MiraiGraphicalUIController.kt +++ b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/controller/MiraiGraphicalUIController.kt @@ -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. } -} \ No newline at end of file +} diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/DefaultCommands.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/DefaultCommands.kt index 40f4bfcbd..104e6071f 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/DefaultCommands.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/DefaultCommands.kt @@ -279,8 +279,7 @@ object DefaultCommands { alias = listOf("reloadPlugins") description = "重新加载全部插件" onCommand{ - PluginManager.disablePlugins() - PluginManager.loadPlugins() + PluginManager.reloadPlugins() sendMessage("重新加载完成") true } diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt index 09abbbe3a..36a535135 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt @@ -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") diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt index 29c5cc316..e4e4a639e 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt @@ -94,12 +94,9 @@ object PluginManager { return pluginDescriptions.values } - @Volatile internal var lastPluginName: String = "" - - /** * 寻找所有安装的插件(在文件夹), 并将它读取, 记录位置 * 这个不等同于加载的插件, 可以理解为还没有加载的插件 @@ -109,7 +106,19 @@ object PluginManager { val pluginsFound: MutableMap ) - 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 = mutableMapOf() val pluginsFound: MutableMap = 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 } diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsLoader.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsLoader.kt index dedf7a32b..04d11defd 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsLoader.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsLoader.kt @@ -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() + private val pluginLoaders = linkedMapOf() private val classesCache = mutableMapOf>() 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?>() @@ -125,4 +141,4 @@ internal class PluginClassLoader(private val pluginName: String,files: File, pri super.close() classesCache.clear() } -} \ No newline at end of file +}