plugin supporting

This commit is contained in:
jiahua.liu 2020-01-18 21:34:32 +08:00
parent 3a2f0f3267
commit 60455bb0ac

View File

@ -1,16 +1,17 @@
package net.mamoe.mirai.plugin package net.mamoe.mirai.plugin
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.io.encodeToString import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.InputStream
import java.io.InputStreamReader
import java.net.JarURLConnection
import java.net.URL import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarFile import java.util.jar.JarFile
abstract class PluginBase constructor() { abstract class PluginBase constructor() {
val dataFolder by lazy {
File(PluginManager.pluginsPath + pluginDescription.name).also { it.mkdir() }
}
open fun onLoad() { open fun onLoad() {
@ -24,27 +25,38 @@ abstract class PluginBase constructor() {
} }
fun getPluginManager(): PluginManager {
return PluginManager
}
private lateinit var pluginDescription: PluginDescription private lateinit var pluginDescription: PluginDescription
internal fun init(pluginDescription: PluginDescription) { internal fun init(pluginDescription: PluginDescription) {
this.pluginDescription = pluginDescription this.pluginDescription = pluginDescription
this.onLoad() this.onLoad()
} }
fun getDataFolder(): File {
return File(PluginManager.pluginsPath + pluginDescription.pluginName).also {
it.mkdirs()
}
}
} }
class PluginDescription( class PluginDescription(
val name: String, val pluginName: String,
val author: String, val pluginAuthor: String,
val basePath: String, val pluginBasePath: String,
val version: String, val pluginVersion: String,
val info: String, val pluginInfo: String,
val depends: List<String>,//插件的依赖 val depends: List<String>,//插件的依赖
internal var loaded: Boolean = false, internal var loaded: Boolean = false,
internal var noCircularDepend: Boolean = true internal var noCircularDepend: Boolean = true
) { ) {
override fun toString(): String { override fun toString(): String {
return "name: $name\nauthor: $author\npath: $basePath\nver: $version\ninfo: $info\ndepends: $depends" return "name: $pluginName\nauthor: $pluginAuthor\npath: $pluginBasePath\nver: $pluginVersion\ninfo: $pluginInfo\ndepends: $depends"
} }
companion object { companion object {
@ -72,9 +84,9 @@ class PluginDescription(
lowercaseLine.startsWith("info") || lowercaseLine.startsWith("information") -> { lowercaseLine.startsWith("info") || lowercaseLine.startsWith("information") -> {
info = line.substringAfter(":").trim() info = line.substringAfter(":").trim()
} }
lowercaseLine.startsWith("main") || lowercaseLine.startsWith("main") || lowercaseLine.startsWith("path") || lowercaseLine.startsWith(
lowercaseLine.startsWith("path") || "basepath"
lowercaseLine.startsWith("basepath") -> { ) -> {
basePath = line.substringAfter(":").trim() basePath = line.substringAfter(":").trim()
} }
lowercaseLine.startsWith("version") || lowercaseLine.startsWith("ver") -> { lowercaseLine.startsWith("version") || lowercaseLine.startsWith("ver") -> {
@ -91,6 +103,13 @@ class PluginDescription(
} }
class PluginClassLoader(file: File, parent: ClassLoader) : URLClassLoader(arrayOf(file.toURI().toURL()), parent) {
override fun findClass(moduleName: String?, name: String?): Class<*> {
return super.findClass(name)
}
}
object PluginManager { object PluginManager {
internal val pluginsPath = System.getProperty("user.dir") + "/plugins/".replace("//", "/").also { internal val pluginsPath = System.getProperty("user.dir") + "/plugins/".replace("//", "/").also {
File(it).mkdirs() File(it).mkdirs()
@ -107,32 +126,47 @@ object PluginManager {
*/ */
fun loadPlugins() { fun loadPlugins() {
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf() val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
val pluginsLocation: MutableMap<String, JarFile> = mutableMapOf() val pluginsLocation: MutableMap<String, File> = mutableMapOf()
File(pluginsPath).listFiles()?.forEach { file -> File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") { if (file != null) {
val jar = JarFile(file) if (file.extension == "jar") {
val pluginYml = val jar = JarFile(file)
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull() val pluginYml =
if (pluginYml == null) { jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be considered as a Plugin") if (pluginYml == null) {
} else { logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
val description = } else {
PluginDescription.readFromContent(URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.readAllBytes().encodeToString()) val url = URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name)
println(description) val jarConnection: JarURLConnection = url
pluginsFound[description.name] = description .openConnection() as JarURLConnection
pluginsLocation[description.name] = jar val inputStream: InputStream = jarConnection.getInputStream()
val br = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
var con: String?
val sb = StringBuffer()
while (br.readLine().also { con = it } != null) {
sb.append(con).append("\n")
}
val description = PluginDescription.readFromContent(sb.toString())
println(description)
pluginsFound[description.pluginName] = description
pluginsLocation[description.pluginName] = file
}
} }
} }
} }
fun checkNoCircularDepends(target: PluginDescription, needDepends: List<String>, existDepends: MutableList<String>) { fun checkNoCircularDependsCheck(
target: PluginDescription,
needDepends: List<String>,
existDepends: MutableList<String>
) {
if (!target.noCircularDepend) { if (!target.noCircularDepend) {
return return
} }
existDepends.add(target.name) existDepends.add(target.pluginName)
if (needDepends.any { existDepends.contains(it) }) { if (needDepends.any { existDepends.contains(it) }) {
target.noCircularDepend = false target.noCircularDepend = false
@ -142,14 +176,14 @@ object PluginManager {
needDepends.forEach { needDepends.forEach {
if (pluginsFound.containsKey(it)) { if (pluginsFound.containsKey(it)) {
checkNoCircularDepends(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends) checkNoCircularDependsCheck(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends)
} }
} }
} }
pluginsFound.values.forEach { pluginsFound.values.forEach {
checkNoCircularDepends(it, it.depends, mutableListOf()) checkNoCircularDependsCheck(it, it.depends, mutableListOf())
} }
//load //load
@ -157,50 +191,58 @@ object PluginManager {
fun loadPlugin(description: PluginDescription): Boolean { fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) { if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency") return false.also {
return false logger.error("Failed to load plugin " + description.pluginName + " because it has circular dependency")
}
} }
//load depends first //load depends first
description.depends.forEach { dependentName -> description.depends.forEach {
val dependent = pluginsFound[dependentName] if (!pluginsFound.containsKey(it)) {
if (dependent == null) { return false.also { _ ->
logger.error("Failed to load plugin " + description.name + " because it need " + dependentName + " as dependency") logger.error("Failed to load plugin " + description.pluginName + " because it need " + it + " as dependency")
return false }
} }
val depend = pluginsFound[it]!!
//还没有加载 //还没有加载
if (!dependent.loaded && !loadPlugin(dependent)) { if (!depend.loaded) {
logger.error("Failed to load plugin " + description.name + " because " + dependentName + " as dependency failed to load") if (!loadPlugin(pluginsFound[it]!!)) {
return false return false.also { _ ->
logger.error("Failed to load plugin " + description.pluginName + " because " + it + " as dependency failed to load")
}
}
} }
} }
//在这里所有的depends都已经加载了 //在这里所有的depends都已经加载了
//real load //real load
logger.info("loading plugin " + description.name) logger.info("loading plugin " + description.pluginName)
try { try {
this.javaClass.classLoader.loadClass(description.basePath) val pluginClass =
PluginClassLoader((pluginsLocation[description.pluginName]!!), this.javaClass.classLoader)
.loadClass(description.pluginBasePath)
return try { return try {
val subClass = javaClass.asSubclass(PluginBase::class.java) val subClass = pluginClass.asSubclass(PluginBase::class.java)
val plugin: PluginBase = subClass.getDeclaredConstructor().newInstance() val plugin: PluginBase = subClass.getDeclaredConstructor().newInstance()
description.loaded = true description.loaded = true
logger.info("successfully loaded plugin " + description.name) logger.info("successfully loaded plugin " + description.pluginName)
logger.info(description.info) logger.info(description.pluginInfo)
nameToPluginBaseMap[description.name] = plugin nameToPluginBaseMap[description.pluginName] = plugin
plugin.init(description) plugin.init(description)
true true
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
logger.error("failed to load plugin " + description.name + " , Main class does not extends PluginBase ") false.also {
false logger.error("failed to load plugin " + description.pluginName + " , Main class does not extends PluginBase ")
}
} }
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
e.printStackTrace() e.printStackTrace()
logger.error("failed to load plugin " + description.name + " , Main class not found under " + description.basePath) return false.also {
return false logger.error("failed to load plugin " + description.pluginName + " , Main class not found under " + description.pluginBasePath)
}
} }
} }