Review plugins:

Move PluginLoader into plugin.loader;
Improve docs;
Change extension PluginLoader.description to member function PluginLoader.getPluginDescription;
Change builtIn loader lists with Lazy items;
Add Regex for testing plugin names and ids.
This commit is contained in:
Him188 2020-09-12 20:20:41 +08:00
parent 5c6b4aa8cc
commit 6b9ec05c98
22 changed files with 211 additions and 177 deletions

View File

@ -21,10 +21,10 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.extension.GlobalComponentStorage
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
@ -68,7 +68,7 @@ public interface MiraiConsole : CoroutineScope {
*
* @return 不可变 [List] ([java.util.Collections.unmodifiableList])
*/
public val builtInPluginLoaders: List<PluginLoader<*, *>>
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
/**
* Console 后端构建时间
@ -80,6 +80,7 @@ public interface MiraiConsole : CoroutineScope {
*/
public val version: Semver
@ConsoleExperimentalApi
public val pluginCenter: PluginCenter

View File

@ -18,8 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.BotConfiguration
@ -76,7 +76,7 @@ public interface MiraiConsoleImplementation : CoroutineScope {
*
* @return 不可变的 [List], [Collections.unmodifiableList]
*/
public val builtInPluginLoaders: List<PluginLoader<*, *>>
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
/**
* Kotlin 用户实现
@ -120,8 +120,8 @@ public interface MiraiConsoleImplementation : CoroutineScope {
public val consoleCommandSender: ConsoleCommandSenderImpl
public val dataStorageForJarPluginLoader: PluginDataStorage
public val configStorageForJarPluginLoader: PluginDataStorage
public val dataStorageForJvmPluginLoader: PluginDataStorage
public val configStorageForJvmPluginLoader: PluginDataStorage
public val dataStorageForBuiltIns: PluginDataStorage
public val configStorageForBuiltIns: PluginDataStorage

View File

@ -11,7 +11,7 @@
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
/**
* 提供扩展 [PluginLoader]

View File

@ -42,10 +42,10 @@ import net.mamoe.mirai.console.internal.util.autoHexToBytes
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
import net.mamoe.mirai.console.permission.RootPermission
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.utils.*
@ -74,11 +74,11 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
createLogger("main")
}
override val coroutineContext: CoroutineContext by instance::coroutineContext
override val builtInPluginLoaders: List<PluginLoader<*, *>> by instance::builtInPluginLoaders
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> by instance::builtInPluginLoaders
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl by instance::consoleCommandSender
override val dataStorageForJarPluginLoader: PluginDataStorage by instance::dataStorageForJarPluginLoader
override val configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader
override val dataStorageForJvmPluginLoader: PluginDataStorage by instance::dataStorageForJvmPluginLoader
override val configStorageForJvmPluginLoader: PluginDataStorage by instance::configStorageForJvmPluginLoader
override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
override val consoleInput: ConsoleInput by instance::consoleInput

View File

@ -17,9 +17,9 @@ import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.util.PluginServiceHelper.findServices
import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.PluginLoadException
import net.mamoe.mirai.console.plugin.jvm.*
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
@ -28,25 +28,24 @@ import java.util.concurrent.ConcurrentHashMap
internal object BuiltInJvmPluginLoaderImpl :
AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
CoroutineScope by MiraiConsole.childScope("JarPluginLoader", CoroutineExceptionHandler { _, throwable ->
CoroutineScope by MiraiConsole.childScope("JvmPluginLoader", CoroutineExceptionHandler { _, throwable ->
BuiltInJvmPluginLoaderImpl.logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
}),
JvmPluginLoader {
override val configStorage: PluginDataStorage
get() = MiraiConsoleImplementationBridge.configStorageForJarPluginLoader
get() = MiraiConsoleImplementationBridge.configStorageForJvmPluginLoader
@JvmStatic
internal val logger: MiraiLogger = MiraiConsole.createLogger(JvmPluginLoader::class.simpleName!!)
override val dataStorage: PluginDataStorage
get() = MiraiConsoleImplementationBridge.dataStorageForJarPluginLoader
get() = MiraiConsoleImplementationBridge.dataStorageForJvmPluginLoader
internal val classLoaders: MutableList<ClassLoader> = mutableListOf()
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
override val JvmPlugin.description: JvmPluginDescription
get() = this.description
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description
private val pluginFileToInstanceMap: MutableMap<File, JvmPlugin> = ConcurrentHashMap()

View File

@ -18,10 +18,14 @@ import net.mamoe.mirai.console.extension.GlobalComponentStorage
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
import net.mamoe.mirai.console.internal.data.cast
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.info
import java.io.File
@ -39,8 +43,9 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
@Suppress("ObjectPropertyName")
private val _pluginLoaders: MutableList<PluginLoader<*, *>> by lazy {
MiraiConsole.builtInPluginLoaders.toMutableList()
builtInLoaders.toMutableList()
}
private val logger = MiraiConsole.createLogger("plugin")
@JvmField
@ -48,17 +53,18 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
CopyOnWriteArrayList() // write operations are mostly performed on init
override val plugins: List<Plugin>
get() = resolvedPlugins.toList()
override val builtInLoaders: List<PluginLoader<*, *>>
get() = MiraiConsole.builtInPluginLoaders
override val builtInLoaders: List<PluginLoader<*, *>> by lazy {
MiraiConsole.builtInPluginLoaders.map { it.value }
}
override val pluginLoaders: List<PluginLoader<*, *>>
get() = _pluginLoaders.toList()
override val Plugin.description: PluginDescription
get() = if (this is JvmPlugin) {
this.safeLoader.getDescription(this)
this.safeLoader.getPluginDescription(this)
} else resolvedPlugins.firstOrNull { it == this }
?.loader?.cast<PluginLoader<Plugin, PluginDescription>>()
?.getDescription(this)
?.getPluginDescription(this)
?: error("Plugin is unloaded")
@ -76,10 +82,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
resolvedPlugins.add(plugin)
}.fold(
onSuccess = {
logger.info { "Successfully loaded plugin ${plugin.description.name}" }
logger.info { "Successfully loaded plugin ${getPluginDescription(plugin).name}" }
},
onFailure = {
logger.info { "Cannot load plugin ${plugin.description.name}" }
logger.info { "Cannot load plugin ${getPluginDescription(plugin).name}" }
throw it
}
)

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.loader.PluginLoader
/**
* 表示一个 mirai-console 插件.
@ -51,7 +52,7 @@ public interface Plugin : CommandOwner {
/**
* 获取 [PluginDescription]
*/
public inline val Plugin.description: PluginDescription get() = this.safeLoader.getDescription(this)
public inline val Plugin.description: PluginDescription get() = this.safeLoader.getPluginDescription(this)
/**
* 获取 [PluginDescription.name`]

View File

@ -14,6 +14,7 @@ package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import java.io.File
import java.nio.file.Path
@ -102,7 +103,7 @@ public interface PluginManager {
public val pluginLoaders: List<PluginLoader<*, *>>
/**
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription]
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getPluginDescription]
*/
public val Plugin.description: PluginDescription

View File

@ -14,6 +14,9 @@ import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import java.io.File
/**
* 插件中心, 计划实现中
*/
@ConsoleExperimentalApi
public interface PluginCenter {
@ -29,7 +32,7 @@ public interface PluginCenter {
val author: String,
val description: String,
val tags: List<String>,
val commands: List<String>
val commands: List<String>,
)
@ConsoleExperimentalApi
@ -48,7 +51,7 @@ public interface PluginCenter {
val usage: String,
val vcs: String,
val commands: List<String>,
val changeLog: List<String>
val changeLog: List<String>,
)
/**

View File

@ -1,5 +1,21 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.plugin.description
/**
* 在检查到非法 [PluginDescription] 时抛出.
*
* @see PluginDescription.checkPluginDescription
*/
public class IllegalPluginDescriptionException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)

View File

@ -11,7 +11,6 @@ package net.mamoe.mirai.console.plugin.description
import com.vdurmont.semver4j.Semver
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginLoadException
/**
@ -21,34 +20,40 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
*/
public interface PluginDescription {
/**
* 插件 ID, 必须全英文, 仅允许英文字母, '-', '_', '.'.
* 插件 ID.
*
* - 仅允许英文字母, '-', '_', '.'. 在内部操作处理时不区分大小写.
* - 类似于 Java 包名, 插件 ID 需要 '域名.名称' 格式, `net.mamoe.mirai.example-plugin`
* - 域名和名称都是必须的
* - '.' 不允许位于首位或末尾
* - '-' '_' 仅允许存在于两个英文字母之间
* - 数字和符号都不允许位于首位
* - '-' '_' 仅允许存在于两个英文字母之间. 不推荐使用 '_'. 请参照 `org.example.mirai.plugin.my-example-plugin` 格式.
*
* 可使用 [ID_REGEX] 检验格式合法性.
*
* ID 在插件发布后就应该保持不变, 以便其他插件添加依赖.
*
* 插件 ID 的域名和名称都不能完全是以下其中一个 ([FORBIDDEN_ID_WORDS]).
* 插件 ID 的域名和名称都不能完全是以下其中一个 ([FORBIDDEN_ID_NAMES]).
* - "console"
* - "main"
* - "plugin"
* - "config"
* - "data"
*
* ### 示例
* - 合法 `net.mamoe.mirai.example-plugin`
* - 非法 `.example-plugin`
*
* ID 用于指令权限等一些内部处理
*
* @see FORBIDDEN_ID_LETTERS
* @see FORBIDDEN_ID_WORDS
* @see ID_REGEX
* @see FORBIDDEN_ID_NAMES
*/
public val id: String
/**
* 插件名称. 允许中文, 允许各类符号.
*
* 插件名称不能完全是以下其中一种 ([FORBIDDEN_ID_WORDS]).
* 插件名称不能完全是以下其中一种 ([FORBIDDEN_ID_NAMES]).
* - console
* - main
* - plugin
@ -57,8 +62,7 @@ public interface PluginDescription {
*
* 插件名称用于显示给用户.
*
* @see FORBIDDEN_ID_LETTERS
* @see FORBIDDEN_ID_WORDS
* @see FORBIDDEN_ID_NAMES
*/
public val name: String
@ -103,8 +107,22 @@ public interface PluginDescription {
public val dependencies: Set<PluginDependency>
public companion object {
public val FORBIDDEN_ID_LETTERS: Array<String> = "~!@#$%^&*()+/*<>{}|[]\\?".map(Char::toString).toTypedArray()
public val FORBIDDEN_ID_WORDS: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
/**
* [PluginDescription.id] 的合法 [Regex].
*
* - Group 1: 域名
* - Group 2: 名称
*
* @see PluginDescription.id
*/
public val ID_REGEX: Regex = Regex("""([a-zA-Z]+[a-zA-Z0-9]*)\.([a-zA-Z]+[a-zA-Z0-9]*)""")
/**
* [PluginDescription.id] [PluginDescription.name] 中禁止用的完全匹配名称列表.
*
* @see PluginDescription.id
*/
public val FORBIDDEN_ID_NAMES: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
/**
* 依次检查 [PluginDescription] [PluginDescription.id], [PluginDescription.name], [PluginDescription.dependencies] 的合法性
@ -126,50 +144,55 @@ public interface PluginDescription {
}
/**
* 检查 [PluginDescription.id] 的合法性.
* 检查 [PluginDescription.id] 的合法性. 忽略大小写.
*
* @throws IllegalPluginDescriptionException 当不合法时抛出.
*/
@Throws(IllegalPluginDescriptionException::class)
public fun checkPluginId(id: String) {
if (id.isBlank()) throw IllegalPluginDescriptionException("Plugin id cannot be blank")
if (id.count { it == '.' } < 2) throw IllegalPluginDescriptionException("'$id' is illegal. Plugin id must consist of both domain and name. ")
if (id.none { it == '.' }) throw IllegalPluginDescriptionException("'$id' is illegal. Plugin id must consist of both domain and name. ")
FORBIDDEN_ID_LETTERS.firstOrNull { it in id }?.let { illegal ->
throw IllegalPluginDescriptionException("Plugin id contains illegal char: $illegal.")
val lowercaseId = id.toLowerCase()
if (ID_REGEX.matchEntire(id) == null) {
throw IllegalPluginDescriptionException("Plugin does not match regex '${ID_REGEX.pattern}'.")
}
val idSections = id.split('.')
FORBIDDEN_ID_WORDS.firstOrNull { it in idSections }?.let { illegal ->
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal ->
throw IllegalPluginDescriptionException("Plugin id contains illegal word: '$illegal'.")
}
}
/**
* 检查 [PluginDescription.name] 的合法性.
* 检查 [PluginDescription.name] 的合法性. 忽略大小写.
*
* @throws IllegalPluginDescriptionException 当不合法时抛出.
*/
@Throws(IllegalPluginDescriptionException::class)
public fun checkPluginName(name: String) {
if (name.isBlank()) throw IllegalPluginDescriptionException("Plugin name cannot be blank")
FORBIDDEN_ID_WORDS.firstOrNull { it in name }?.let { illegal ->
val lowercaseName = name.toLowerCase()
FORBIDDEN_ID_NAMES.firstOrNull { it in lowercaseName }?.let { illegal ->
throw IllegalPluginDescriptionException("Plugin name is illegal: '$illegal'.")
}
}
/**
* 检查 [PluginDescription.dependencies] 的合法性.
* 检查 [PluginDescription.dependencies] 的合法性. 忽略大小写.
*
* @throws IllegalPluginDescriptionException 当不合法时抛出.
*/
@Throws(IllegalPluginDescriptionException::class)
public fun checkDependencies(pluginId: String, dependencies: Set<PluginDependency>) {
if (dependencies.distinctBy { it.id }.size != dependencies.size)
throw PluginLoadException("Duplicated dependency detected: A plugin cannot depend on different versions of dependencies of the same id")
val lowercaseId = pluginId.toLowerCase()
val lowercaseDependencies = dependencies.mapTo(LinkedHashSet(dependencies.size)) { it.id.toLowerCase() }
if (dependencies.any { it.id == pluginId })
throw PluginLoadException("Recursive dependency detected: A plugin cannot depend on itself")
if (lowercaseDependencies.size != dependencies.size)
throw IllegalPluginDescriptionException("Duplicated dependency detected: A plugin cannot depend on different versions of dependencies of the same id")
if (lowercaseDependencies.any { it == lowercaseId })
throw IllegalPluginDescriptionException("Recursive dependency detected: A plugin cannot depend on itself")
}
}
}

View File

@ -12,13 +12,16 @@ package net.mamoe.mirai.console.plugin.jvm
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl
import net.mamoe.mirai.console.plugin.FilePluginLoader
import net.mamoe.mirai.console.plugin.loader.FilePluginLoader
/**
* 内建的 Jar (JVM) 插件加载器
*/
public interface JvmPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
public override val fileSuffix: String get() = ".jar"
/**
* ".jar"
*/
public override val fileSuffix: String
/**
* [AbstractJvmPlugin.reloadPluginData] 默认使用的实例
@ -32,7 +35,7 @@ public interface JvmPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, J
public companion object BuiltIn : JvmPluginLoader by BuiltInJvmPluginLoaderImpl {
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
override val JvmPlugin.description: JvmPluginDescription
get() = BuiltInJvmPluginLoaderImpl.run { description }
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription =
BuiltInJvmPluginLoaderImpl.run { plugin.description }
}
}

View File

@ -0,0 +1,43 @@
package net.mamoe.mirai.console.plugin.loader
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import java.io.File
/**
* ['/plugins'][PluginManager.pluginsPath] 目录中的插件的加载器. 每个加载器需绑定一个后缀.
*
* @see AbstractFilePluginLoader 默认基础实现
* @see JvmPluginLoader 内建的 Jar (JVM) 插件加载器.
*/
public interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> {
/**
* 所支持的插件文件后缀, '.', 不区分大小写. [JvmPluginLoader] ".jar"
*/
public val fileSuffix: String
}
/**
* [FilePluginLoader] 的默认基础实现.
*
* @see FilePluginLoader
*/
public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
/**
* 所支持的插件文件后缀, '.', 不区分大小写. [JvmPluginLoader] ".jar"
*/
public override val fileSuffix: String,
) : FilePluginLoader<P, D> {
private fun pluginsFilesSequence(): Sequence<File> =
PluginManager.pluginsFolder.listFiles().orEmpty().asSequence()
.filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
/**
* 读取扫描到的后缀与 [fileSuffix] 相同的文件中的插件实例, 但不 [加载][PluginLoader.load]
*/
protected abstract fun Sequence<File>.extractPlugins(): List<P>
public final override fun listPlugins(): List<P> = pluginsFilesSequence().extractPlugins()
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.plugin.loader
/**
* 在加载插件过程中遇到的意料之中的问题.
*
* @see PluginLoader.load
* @see PluginLoader.enable
* @see PluginLoader.disable
* @see PluginLoader.getPluginDescription
*/
public open class PluginLoadException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -9,13 +9,14 @@
@file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "NOTHING_TO_INLINE")
package net.mamoe.mirai.console.plugin
package net.mamoe.mirai.console.plugin.loader
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import java.io.File
import java.util.*
/**
@ -58,11 +59,10 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
*
* @see PluginDescription 插件描述
* @see getDescription receiver, 接受参数的版本.
* @see getPluginDescription receiver, 接受参数的版本.
*/
@get:JvmName("getPluginDescription")
@get:Throws(PluginLoadException::class)
public val P.description: D // Java signature: `public D getDescription(P)`
@Throws(PluginLoadException::class)
public fun getPluginDescription(plugin: P): D // Java signature: `public D getDescription(P)`
/**
* 主动加载一个插件 (实例), 但不 [启用][enable] . 返回加载成功的主类实例
@ -101,85 +101,3 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
@Throws(IllegalStateException::class, PluginLoadException::class)
public fun disable(plugin: P)
}
/**
* 获取此插件的描述.
*
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
*
* 若在 console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件.
*
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
*
* @see PluginDescription 插件描述
* @see PluginLoader.description
*/
@Suppress("UNCHECKED_CAST")
@JvmSynthetic
public inline fun <D : PluginDescription, P : Plugin> PluginLoader<in P, out D>.getDescription(plugin: P): D =
plugin.description
/**
* 在加载插件过程中遇到的意料之中的问题.
*
* @see PluginLoader.load
* @see PluginLoader.enable
* @see PluginLoader.disable
* @see PluginLoader.description
*/
public open class PluginLoadException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
/**
* ['/plugins'][PluginManager.pluginsPath] 目录中的插件的加载器. 每个加载器需绑定一个后缀.
*
* @see AbstractFilePluginLoader 默认基础实现
* @see JvmPluginLoader 内建的 Jar (JVM) 插件加载器.
*/
public interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> {
/**
* 所支持的插件文件后缀, '.', 不区分大小写. [JvmPluginLoader] ".jar"
*/
public val fileSuffix: String
}
/**
* [FilePluginLoader] 的默认基础实现.
*
* @see FilePluginLoader
*/
public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
/**
* 所支持的插件文件后缀, '.', 不区分大小写. [JvmPluginLoader] ".jar"
*/
public override val fileSuffix: String,
) : FilePluginLoader<P, D> {
private fun pluginsFilesSequence(): Sequence<File> =
PluginManager.pluginsFolder.listFiles().orEmpty().asSequence()
.filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
/**
* 读取扫描到的后缀与 [fileSuffix] 相同的文件中的插件实例, 但不 [加载][PluginLoader.load]
*/
protected abstract fun Sequence<File>.extractPlugins(): List<P>
public final override fun listPlugins(): List<P> = pluginsFilesSequence().extractPlugins()
}
// Not yet decided to make public API
internal class DeferredPluginLoader<P : Plugin, D : PluginDescription>(
initializer: () -> PluginLoader<P, D>,
) : PluginLoader<P, D> {
private val instance by lazy(initializer)
override fun listPlugins(): List<P> = instance.run { listPlugins() }
override val P.description: D get() = instance.run { description }
override fun load(plugin: P) = instance.load(plugin)
override fun enable(plugin: P) = instance.enable(plugin)
override fun disable(plugin: P) = instance.disable(plugin)
}

View File

@ -15,9 +15,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.data.MemoryPluginDataStorage
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalApi
@ -48,7 +47,7 @@ fun initTestEnvironment() {
get() = Semver("1.0.0")
}
override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JvmPluginLoader })
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader })
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl =
object : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
override suspend fun sendMessage(message: Message) {
@ -59,8 +58,8 @@ fun initTestEnvironment() {
println(message)
}
}
override val dataStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
override val configStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
override val dataStorageForJvmPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
override val configStorageForJvmPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
override val dataStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage()
override val configStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage()

View File

@ -3,9 +3,9 @@
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
[`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
[`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
[`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
[`JvmPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
[`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt

View File

@ -2,9 +2,9 @@
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
[`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
[`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
[`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
[`JvmPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
[`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
@ -35,7 +35,6 @@
[`RawCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt
[`CommandManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt

View File

@ -2,9 +2,9 @@
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
[`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
[`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
[`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
[`JvmPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
[`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
@ -63,7 +63,7 @@ interface Plugin : CommandOwner { // CommandOwner 是空的 interface
## JVM 平台插件接口 - [`JvmPlugin`]
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JarPluginLoader`] 加载)。
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。
### 主类和描述
@ -174,7 +174,7 @@ Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务
#### 加载
[`JarPluginLoader`] 调用插件的 `onLoad()`,在 `onLoad()` 正常返回后插件被认为成功加载。
[`JvmPluginLoader`] 调用插件的 `onLoad()`,在 `onLoad()` 正常返回后插件被认为成功加载。
由于 `onLoad()` 只会被初始化一次,插件可以在该方法内进行一些*一次性*的*初始化*任务。
@ -184,13 +184,13 @@ Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务
#### 启用
[`JarPluginLoader`] 调用插件的 `onEnable()`,意为启用一个插件。
[`JvmPluginLoader`] 调用插件的 `onEnable()`,意为启用一个插件。
此时插件可以启动所有协程,事件监听,和其他任务。**但这些任务都应该拥有生命周期管理,详见 [任务生命周期管理](#任务生命周期管理)。**
#### 禁用
[`JarPluginLoader`] 调用插件的 `onDisable()`,意为禁用一个插件。
[`JvmPluginLoader`] 调用插件的 `onDisable()`,意为禁用一个插件。
插件的任何类和对象都不会被卸载。「禁用」仅表示停止关闭所有正在进行的任务,保存所有数据,停止处理将来的数据。

View File

@ -5,7 +5,7 @@
## 目录
- **[准备工作](#准备工作)**
- **[启动 Console](#Run.md)**
- **[启动 Console](Run.md)**
### 后端插件开发基础
@ -29,10 +29,9 @@
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
[`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
## 准备工作

View File

@ -30,9 +30,8 @@ import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput
@ -51,7 +50,6 @@ import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.*
/**
* mirai-console-pure 后端实现
@ -61,15 +59,13 @@ import java.util.*
@ConsoleExperimentalApi
class MiraiConsoleImplementationPure
@JvmOverloads constructor(
override val rootPath: Path = Paths.get("."),
override val builtInPluginLoaders: List<PluginLoader<*, *>> = Collections.unmodifiableList(
listOf(DeferredPluginLoader { JvmPluginLoader })
),
override val rootPath: Path = Paths.get(".").toAbsolutePath(),
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }),
override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl,
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplPure,
override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
NamedSupervisorJob("MiraiConsoleImplementationPure") +