mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Support plugin reloading
This commit is contained in:
parent
c3120cf1ac
commit
633e333609
@ -1,9 +1,6 @@
|
|||||||
package net.mamoe.mirai.console.plugins.builtin
|
package net.mamoe.mirai.console.plugins.builtin
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.plugins.AbstractFilePluginLoader
|
import net.mamoe.mirai.console.plugins.AbstractFilePluginLoader
|
||||||
import net.mamoe.mirai.console.plugins.PluginLoadException
|
import net.mamoe.mirai.console.plugins.PluginLoadException
|
||||||
@ -16,8 +13,7 @@ import kotlin.reflect.full.createInstance
|
|||||||
/**
|
/**
|
||||||
* 内建的 Jar (JVM) 插件加载器
|
* 内建的 Jar (JVM) 插件加载器
|
||||||
*/
|
*/
|
||||||
object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>("jar"),
|
object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>("jar"), CoroutineScope {
|
||||||
CoroutineScope {
|
|
||||||
private val logger: MiraiLogger by lazy {
|
private val logger: MiraiLogger by lazy {
|
||||||
MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
|
MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
|
||||||
}
|
}
|
||||||
@ -29,9 +25,15 @@ object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescriptio
|
|||||||
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private val supervisor: Job = coroutineContext[Job]!!
|
||||||
|
|
||||||
private val classLoader: PluginsLoader =
|
private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
|
||||||
PluginsLoader(this.javaClass.classLoader)
|
|
||||||
|
init {
|
||||||
|
supervisor.invokeOnCompletion {
|
||||||
|
classLoader.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description
|
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescriptio
|
|||||||
|
|
||||||
@Throws(PluginLoadException::class)
|
@Throws(PluginLoadException::class)
|
||||||
override fun load(description: JvmPluginDescription): JvmPlugin = description.runCatching {
|
override fun load(description: JvmPluginDescription): JvmPlugin = description.runCatching {
|
||||||
|
ensureActive()
|
||||||
val main = classLoader.loadPluginMainClassByJarFile(name, mainClassName, file).kotlin.run {
|
val main = classLoader.loadPluginMainClassByJarFile(name, mainClassName, file).kotlin.run {
|
||||||
objectInstance
|
objectInstance
|
||||||
?: kotlin.runCatching { createInstance() }.getOrNull()
|
?: kotlin.runCatching { createInstance() }.getOrNull()
|
||||||
@ -61,16 +64,9 @@ object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescriptio
|
|||||||
|
|
||||||
if (main is JvmPluginImpl) {
|
if (main is JvmPluginImpl) {
|
||||||
main._description = description
|
main._description = description
|
||||||
}
|
main.internalOnLoad()
|
||||||
|
} else main.onLoad()
|
||||||
TODO(
|
main
|
||||||
"""
|
|
||||||
FIND PLUGIN MAIN, THEN LOAD
|
|
||||||
SET JvmPluginImpl._description
|
|
||||||
SET JvmPluginImpl._intrinsicCoroutineContext
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
// no need to check dependencies
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
throw PluginLoadException(
|
throw PluginLoadException(
|
||||||
"Exception while loading ${description.name}",
|
"Exception while loading ${description.name}",
|
||||||
@ -78,6 +74,16 @@ object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescriptio
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enable(plugin: JvmPlugin) = plugin.onEnable()
|
override fun enable(plugin: JvmPlugin) {
|
||||||
override fun disable(plugin: JvmPlugin) = plugin.onDisable()
|
ensureActive()
|
||||||
|
if (plugin is JvmPluginImpl) {
|
||||||
|
plugin.internalOnEnable()
|
||||||
|
} else plugin.onEnable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disable(plugin: JvmPlugin) {
|
||||||
|
if (plugin is JvmPluginImpl) {
|
||||||
|
plugin.internalOnDisable()
|
||||||
|
} else plugin.onDisable()
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.plugins.builtin
|
package net.mamoe.mirai.console.plugins.builtin
|
||||||
|
|
||||||
|
import kotlinx.atomicfu.locks.withLock
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -20,6 +21,7 @@ import net.mamoe.mirai.console.plugins.Plugin
|
|||||||
import net.mamoe.mirai.console.plugins.PluginLoader
|
import net.mamoe.mirai.console.plugins.PluginLoader
|
||||||
import net.mamoe.mirai.console.utils.JavaPluginScheduler
|
import net.mamoe.mirai.console.utils.JavaPluginScheduler
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
@ -84,388 +86,44 @@ internal abstract class JvmPluginImpl(
|
|||||||
|
|
||||||
// for future use
|
// for future use
|
||||||
@Suppress("PropertyName")
|
@Suppress("PropertyName")
|
||||||
|
@JvmField
|
||||||
internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||||
|
|
||||||
override val description: JvmPluginDescription get() = _description
|
override val description: JvmPluginDescription get() = _description
|
||||||
|
|
||||||
final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) }
|
final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) }
|
||||||
|
|
||||||
final override val coroutineContext: CoroutineContext by lazy {
|
@JvmField
|
||||||
|
internal val coroutineContextInitializer = {
|
||||||
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) }
|
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) }
|
||||||
.plus(parentCoroutineContext)
|
.plus(parentCoroutineContext)
|
||||||
.plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext
|
.plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var firstRun = true
|
||||||
|
|
||||||
|
internal fun internalOnDisable() {
|
||||||
|
firstRun = false
|
||||||
|
this.onDisable()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun internalOnLoad() {
|
||||||
|
this.onLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun internalOnEnable() {
|
||||||
|
if (!firstRun) refreshCoroutineContext()
|
||||||
|
this.onEnable()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshCoroutineContext(): CoroutineContext {
|
||||||
|
return coroutineContextInitializer().also { _coroutineContext = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val contextUpdateLock: ReentrantLock = ReentrantLock()
|
||||||
|
private var _coroutineContext: CoroutineContext? = null
|
||||||
|
final override val coroutineContext: CoroutineContext
|
||||||
|
get() = _coroutineContext
|
||||||
|
?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}*/
|
|
Loading…
Reference in New Issue
Block a user