diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt index bac4a2fe2..084e0bce1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt @@ -1,9 +1,6 @@ package net.mamoe.mirai.console.plugins.builtin -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.plugins.AbstractFilePluginLoader import net.mamoe.mirai.console.plugins.PluginLoadException @@ -16,8 +13,7 @@ import kotlin.reflect.full.createInstance /** * 内建的 Jar (JVM) 插件加载器 */ -object JarPluginLoader : AbstractFilePluginLoader("jar"), - CoroutineScope { +object JarPluginLoader : AbstractFilePluginLoader("jar"), CoroutineScope { private val logger: MiraiLogger by lazy { MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!) } @@ -29,9 +25,15 @@ object JarPluginLoader : AbstractFilePluginLoader logger.error(throwable) } .plus(parentCoroutineContext) .plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext } -} -/* -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") + private var firstRun = true + + internal fun internalOnDisable() { + firstRun = false + this.onDisable() } - /** - * 获取所有插件摘要 - */ - fun getAllPluginDescriptions(): Collection { - return pluginDescriptions.values + internal fun internalOnLoad() { + this.onLoad() } - /** - * 关闭所有插件 - */ - @JvmOverloads - fun disablePlugins(throwable: CancellationException? = null) { - pluginsSequence.forEach { plugin -> - plugin.unregisterAllCommands() - plugin.disable(throwable) - } - nameToPluginBaseMap.clear() - pluginDescriptions.clear() - pluginsLoader.clear() - pluginsSequence.clear() + internal fun internalOnEnable() { + if (!firstRun) refreshCoroutineContext() + this.onEnable() } - /** - * 重载所有插件 - */ - fun reloadPlugins() { - pluginsSequence.forEach { - it.disable() - } - loadPlugins(false) + private fun refreshCoroutineContext(): CoroutineContext { + return coroutineContextInitializer().also { _coroutineContext = it } } - /** - * 尝试加载全部插件 - */ - fun loadPlugins(clear: Boolean = true) = loadPluginsImpl(clear) + private val contextUpdateLock: ReentrantLock = ReentrantLock() + private var _coroutineContext: CoroutineContext? = null + final override val coroutineContext: CoroutineContext + get() = _coroutineContext + ?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() } - - ////////////////// - //// internal //// - ////////////////// - - internal val pluginsPath = (MiraiConsole.path + "/plugins/").replace("//", "/").also { - File(it).mkdirs() - } - - private val logger = MiraiConsole.newLogger("Plugin Manager") - - /** - * 加载成功的插件, 名字->插件 - */ - internal val nameToPluginBaseMap: MutableMap = mutableMapOf() - - /** - * 加载成功的插件, 名字->插件摘要 - */ - private val pluginDescriptions: MutableMap = mutableMapOf() - - /** - * 加载插件的PluginsLoader - */ - private val pluginsLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) - - /** - * 插件优先级队列 - * 任何操作应该按这个Sequence顺序进行 - * 他的优先级取决于依赖, - * 在这个队列中, 被依赖的插件会在依赖的插件之前 - */ - private val pluginsSequence: LockFreeLinkedList = LockFreeLinkedList() - - - /** - * 广播Command方法 - */ - internal fun onCommand(command: Command, sender: CommandSender, args: List) { - 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, - val pluginsFound: MutableMap - ) - - internal fun findPlugins(): FindPluginsResult { - val pluginsLocation: MutableMap = mutableMapOf() - val pluginsFound: MutableMap = 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, - existDepends: MutableList - ) { - - 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 - } -}*/ \ No newline at end of file +} \ No newline at end of file