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 new file mode 100644 index 000000000..bac4a2fe2 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JarPluginLoader.kt @@ -0,0 +1,83 @@ +package net.mamoe.mirai.console.plugins.builtin + +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.plugins.AbstractFilePluginLoader +import net.mamoe.mirai.console.plugins.PluginLoadException +import net.mamoe.mirai.console.plugins.PluginsLoader +import net.mamoe.mirai.utils.MiraiLogger +import java.io.File +import kotlin.coroutines.CoroutineContext +import kotlin.reflect.full.createInstance + +/** + * 内建的 Jar (JVM) 插件加载器 + */ +object JarPluginLoader : AbstractFilePluginLoader("jar"), + CoroutineScope { + private val logger: MiraiLogger by lazy { + MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!) + } + + override val coroutineContext: CoroutineContext by lazy { + MiraiConsole.coroutineContext + SupervisorJob( + MiraiConsole.coroutineContext[Job] + ) + CoroutineExceptionHandler { _, throwable -> + logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) + } + } + + private val classLoader: PluginsLoader = + PluginsLoader(this.javaClass.classLoader) + + override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description + + override fun Sequence.mapToDescription(): List { + TODO( + """ + CHECK IS JAR FILE AND CAN BE READ + READ JAR FILE, EXTRACT PLUGIN DESCRIPTION + SET JvmPluginDescription._file + RETURN PLUGIN + """.trimIndent() + ) + } + + @Throws(PluginLoadException::class) + override fun load(description: JvmPluginDescription): JvmPlugin = description.runCatching { + val main = classLoader.loadPluginMainClassByJarFile(name, mainClassName, file).kotlin.run { + objectInstance + ?: kotlin.runCatching { createInstance() }.getOrNull() + ?: (java.constructors + java.declaredConstructors) + .firstOrNull { it.parameterCount == 0 } + ?.apply { kotlin.runCatching { isAccessible = true } } + ?.newInstance() + } ?: error("No Kotlin object or public no-arg constructor found") + + check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" } + + if (main is JvmPluginImpl) { + main._description = description + } + + TODO( + """ + FIND PLUGIN MAIN, THEN LOAD + SET JvmPluginImpl._description + SET JvmPluginImpl._intrinsicCoroutineContext + """.trimIndent() + ) + // no need to check dependencies + }.getOrElse { + throw PluginLoadException( + "Exception while loading ${description.name}", + it + ) + } + + override fun enable(plugin: JvmPlugin) = plugin.onEnable() + override fun disable(plugin: JvmPlugin) = plugin.onDisable() +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPlugin.kt similarity index 87% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JvmPlugin.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPlugin.kt index 0ed7eae34..7adbd6940 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/JvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPlugin.kt @@ -9,24 +9,31 @@ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS") -package net.mamoe.mirai.console.plugins +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.serialization.Serializable -import kotlinx.serialization.Transient import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.scheduler.PluginScheduler +import net.mamoe.mirai.console.plugins.Plugin +import net.mamoe.mirai.console.utils.JavaPluginScheduler import net.mamoe.mirai.utils.MiraiLogger -import java.io.File import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +/** + * Java 或 Kotlin Jar 插件 + * + * @see JavaPlugin Java 插件 + * @see KotlinPlugin Kotlin 插件 + */ interface JvmPlugin : Plugin, CoroutineScope { + /** 日志 */ val logger: MiraiLogger + + /** 插件描述 */ val description: JvmPluginDescription @JvmDefault @@ -42,7 +49,9 @@ interface JvmPlugin : Plugin, CoroutineScope { } } - +/** + * Java 插件的父类 + */ abstract class JavaPlugin @JvmOverloads constructor( coroutineContext: CoroutineContext = EmptyCoroutineContext ) : JvmPlugin, JvmPluginImpl(coroutineContext) { @@ -50,7 +59,8 @@ abstract class JavaPlugin @JvmOverloads constructor( /** * Java API Scheduler */ - val scheduler: PluginScheduler? = PluginScheduler(this.coroutineContext) + val scheduler: JavaPluginScheduler = + JavaPluginScheduler(this.coroutineContext) } abstract class KotlinPlugin @JvmOverloads constructor( @@ -59,41 +69,6 @@ abstract class KotlinPlugin @JvmOverloads constructor( // that's it } -@Serializable -data class JvmPluginDescription internal constructor( // serializer 可以用这个构造器 - override val kind: PluginKind, - override val name: String, - override val author: String, - override val version: String, - override val info: String, - override val loadBefore: List, - override val dependencies: List -) : PluginDescription, FilePluginDescription { - /** - * 在手动实现时使用这个构造器. - */ - @Suppress("unused") - constructor( - kind: PluginKind, - name: String, - author: String, - version: String, - info: String, - loadBefore: List, - depends: List, - file: File - ) : this(kind, name, author, version, info, loadBefore, depends) { - this._file = file - } - - @Suppress("PropertyName") - @Transient - internal var _file: File? = null - - override val file: File - get() = _file ?: error("Internal error: JvmPluginDescription(name=$name)._file == null") -} - internal abstract class JvmPluginImpl( parentCoroutineContext: CoroutineContext ) : JvmPlugin, CoroutineScope { @@ -102,6 +77,11 @@ internal abstract class JvmPluginImpl( */ @Suppress("PropertyName") internal lateinit var _description: JvmPluginDescription + + // for future use + @Suppress("PropertyName") + internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext + override val description: JvmPluginDescription get() = _description final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) } @@ -109,37 +89,10 @@ internal abstract class JvmPluginImpl( final override val coroutineContext: CoroutineContext by lazy { CoroutineExceptionHandler { _, throwable -> logger.error(throwable) } .plus(parentCoroutineContext) - .plus(SupervisorJob(parentCoroutineContext[Job])) + .plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext } } -/** - * 内建的 Jar (JVM) 插件加载器 - */ -object JarPluginLoader : AbstractFilePluginLoader("jar") { - override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description - - override fun Sequence.mapToDescription(): List { - TODO( - """ - CHECK IS JAR FILE AND CAN BE READ - READ JAR FILE, EXTRACT PLUGIN DESCRIPTION - SET JvmPluginDescription._file - RETURN PLUGIN - """.trimIndent() - ) - } - - @Throws(PluginLoadException::class) - override fun load(description: JvmPluginDescription): JvmPlugin { - TODO("FIND PLUGIN MAIN, THEN LOAD") - // no need to check dependencies - } - - override fun enable(plugin: JvmPlugin) = plugin.onEnable() - override fun disable(plugin: JvmPlugin) = plugin.onDisable() -} - /* object PluginManagerOld { /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPluginDescription.kt new file mode 100644 index 000000000..03cf5c579 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/builtin/JvmPluginDescription.kt @@ -0,0 +1,48 @@ +package net.mamoe.mirai.console.plugins.builtin + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.mamoe.mirai.console.plugins.FilePluginDescription +import net.mamoe.mirai.console.plugins.PluginDependency +import net.mamoe.mirai.console.plugins.PluginDescription +import net.mamoe.mirai.console.plugins.PluginKind +import java.io.File + +@Serializable +class JvmPluginDescription internal constructor( + override val kind: PluginKind, + override val name: String, + @SerialName("main") + val mainClassName: String, + override val author: String, + override val version: String, + override val info: String, + override val dependencies: List +) : PluginDescription, FilePluginDescription { + + /** + * 在手动实现时使用这个构造器. + */ + @Suppress("unused") + constructor( + kind: PluginKind, name: String, mainClassName: String, author: String, + version: String, info: String, depends: List, + file: File + ) : this(kind, name, mainClassName, author, version, info, depends) { + this._file = file + } + + override val file: File + get() = _file ?: error("Internal error: JvmPluginDescription(name=$name)._file == null") + + + @Suppress("PropertyName") + @Transient + @JvmField + internal var _file: File? = null + + override fun toString(): String { + return "JvmPluginDescription(kind=$kind, name='$name', mainClassName='$mainClassName', author='$author', version='$version', info='$info', dependencies=$dependencies, _file=$_file)" + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/description.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/description.kt new file mode 100644 index 000000000..15622c178 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/description.kt @@ -0,0 +1,84 @@ +/* + * 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 kotlinx.serialization.Serializable +import java.io.File + + +/** 插件类型 */ +enum class PluginKind { + /** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */ + LOADER, + + /** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */ + NORMAL +} + +/** + * 插件描述 + */ +interface PluginDescription { + val kind: PluginKind + + val name: String + val author: String + val version: String + val info: String + + /** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */ + val dependencies: List +} + +/** 插件的一个依赖的信息 */ +@Serializable +data class PluginDependency( + /** 依赖插件名 */ + val name: String, + /** + * 依赖版本号 + * @see versionKind 版本号类型 + */ + val version: String, + /** 版本号类型 */ + val versionKind: VersionKind, + /** + * 若为 `false`, 插件在找不到此依赖时也能正常加载. + */ + val isOptional: Boolean +) { + enum class VersionKind { + /** 要求依赖精确的版本 */ + EXACT, + + /** 要求依赖最低版本 */ + AT_LEAST, + + /** 要求依赖最高版本 */ + AT_MOST + } + + override fun toString(): String { + return "$name ${versionKind.toEnglishString()}v$version" + } +} + +/** + * 基于文件的插件的描述 + */ +interface FilePluginDescription : PluginDescription { + val file: File +} + +internal fun PluginDependency.VersionKind.toEnglishString(): String = when (this) { + PluginDependency.VersionKind.EXACT -> "" + PluginDependency.VersionKind.AT_LEAST -> "at least " + PluginDependency.VersionKind.AT_MOST -> "at most " +}