ExtensionSelectors and improved extensions API

This commit is contained in:
Him188 2020-09-08 21:28:56 +08:00
parent e1f8125163
commit bf4c82f1a5
13 changed files with 256 additions and 78 deletions

View File

@ -17,6 +17,8 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
* 表示一个扩展.
*
* Console 许多不容易通过
*
* @see ExtensionWithConfig
*/
@ConsoleExperimentalAPI
public interface Extension

View File

@ -11,15 +11,48 @@
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.util.*
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.contracts.contract
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
@ConsoleExperimentalAPI
public interface ExtensionPoint<T : Extension> {
public val type: KClass<T>
public fun registerExtension(plugin: Plugin, extension: T)
public fun getExtensions(): Set<ExtensionRegistry<T>>
public companion object {
@JvmStatic
@JvmSynthetic
@ConsoleExperimentalAPI
public inline fun <reified T : Extension> ExtensionPoint<*>.isFor(exactType: Boolean = false): Boolean {
return if (exactType) {
T::class == type
} else T::class.isSubclassOf(type)
}
}
}
@ConsoleExperimentalAPI
public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T> {
public companion object {
@JvmStatic
@ConsoleExperimentalAPI
public fun <T : SingletonExtension<*>> SingletonExtensionPoint<T>.findSingleton(): T? {
return SingletonExtensionSelector.selectSingleton(type, this.getExtensions())
}
}
}
/**
* 表示一个扩展点
@ -27,26 +60,28 @@ import kotlin.reflect.KClass
@ConsoleExperimentalAPI
public open class AbstractExtensionPoint<T : Extension>(
@ConsoleExperimentalAPI
public val type: KClass<T>
) {
@ConsoleExperimentalAPI
public data class ExtensionRegistry<T>(
public val plugin: Plugin,
public val extension: T
)
public override val type: KClass<T>
) : ExtensionPoint<T> {
init {
@Suppress("LeakingThis")
allExtensionPoints.add(this)
}
private val instances: MutableSet<ExtensionRegistry<T>> = CopyOnWriteArraySet()
@Synchronized
@ConsoleExperimentalAPI
public fun registerExtension(plugin: Plugin, extension: T) {
public override fun registerExtension(plugin: Plugin, extension: T) {
// require(plugin.isEnabled) { "Plugin $plugin must be enabled before registering an extension." }
requireNotNull(extension::class.qualifiedName) { "Extension must not be an anonymous object" }
instances.add(ExtensionRegistry(plugin, extension))
}
@Synchronized
internal fun getExtensions(): Set<ExtensionRegistry<T>> = instances
public override fun getExtensions(): Set<ExtensionRegistry<T>> = Collections.unmodifiableSet(instances)
internal companion object {
@ConsoleExperimentalAPI
internal val allExtensionPoints: MutableList<AbstractExtensionPoint<*>> = mutableListOf()
}
}

View File

@ -0,0 +1,19 @@
/*
* 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.extension
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
@ConsoleExperimentalAPI
public data class ExtensionRegistry<T>(
public val plugin: Plugin,
public val extension: T
)

View File

@ -5,7 +5,6 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.FunctionExtension
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.BotConfiguration
/**
@ -13,7 +12,6 @@ import net.mamoe.mirai.utils.BotConfiguration
*
* @see MiraiConsole.addBot
*/
@ConsoleExperimentalAPI
public interface BotConfigurationAlterer : FunctionExtension {
/**

View File

@ -2,16 +2,19 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.SingletonExtension
import net.mamoe.mirai.console.extension.SingletonExtensionPoint
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.description.PluginKind
/**
* [权限服务][PermissionService] 提供器.
*
* 此扩展
* 此扩展可由 [PluginKind.LOADER] [PluginKind.HIGH_PRIORITY_EXTENSIONS] 插件提供
*/
@ExperimentalPermission
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
public companion object ExtensionPoint :
AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class)
AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class),
SingletonExtensionPoint<PermissionServiceProvider>
}

View File

@ -3,9 +3,12 @@ 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.description.PluginKind
/**
* 提供扩展 [PluginLoader]
*
* 此扩展可由 [PluginKind.LOADER] 插件提供
*/
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)

View File

@ -16,6 +16,8 @@ import net.mamoe.mirai.console.extension.FunctionExtension
* Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化.
*
* 这些扩展只会, 且一定会被调用正好一次.
*
* 此扩展可由所有插件提供
*/
public fun interface PostStartupExtension : FunctionExtension {
/**

View File

@ -0,0 +1,57 @@
/*
* 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.extensions
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.extension.*
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.utils.info
import kotlin.reflect.KClass
/**
* 用于同时拥有多个 [SingletonExtension] 时选择一个实例.
*
* 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器.
*
* 此扩展可由 [PluginKind.LOADER] [PluginKind.HIGH_PRIORITY_EXTENSIONS] 插件提供
*/
public interface SingletonExtensionSelector : FunctionExtension {
public fun <T : Extension> selectSingleton(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>
): T?
public companion object ExtensionPoint :
AbstractExtensionPoint<SingletonExtensionSelector>(SingletonExtensionSelector::class) {
internal val instance: SingletonExtensionSelector by lazy {
val instances = SingletonExtensionSelector.getExtensions()
when {
instances.isEmpty() -> BuiltInSingletonExtensionSelector
instances.size == 1 -> {
instances.single().also { (plugin, ext) ->
MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin.name}" }
}.extension
}
else -> {
error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p.name}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors")
}
}
}
internal fun <T : Extension> selectSingleton(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>
): T? =
instance.selectSingleton(extensionType, candidates)
}
}

View File

@ -28,14 +28,15 @@ import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.extension.useExtensions
import net.mamoe.mirai.console.extensions.PostStartupExtension
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.permission.BuiltInPermissionService
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.StorablePermissionService
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
@ -135,10 +136,12 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
}
SingletonExtensionSelector // init
phase `load PermissionService`@{
mainLogger.verbose { "Loading PermissionService..." }
PermissionService.INSTANCE.let { ps ->
if (ps is StorablePermissionService<*>) {
if (ps is BuiltInPermissionService) {
ConsoleDataScope.addAndReloadConfig(ps.config)
mainLogger.verbose { "Reloaded PermissionService settings." }
}

View File

@ -0,0 +1,69 @@
package net.mamoe.mirai.console.internal.extensions
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.AutoSavePluginData
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.extension.Extension
import net.mamoe.mirai.console.extension.ExtensionRegistry
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.data.kClassQualifiedName
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.utils.info
import kotlin.reflect.KClass
internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
private val config: SaveData = SaveData()
private class SaveData : AutoSavePluginData() {
override val saveName: String get() = "ExtensionSelector"
val value: MutableMap<String, String> by value()
}
override fun <T : Extension> selectSingleton(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>
): T? = when {
candidates.isEmpty() -> null
candidates.size == 1 -> candidates.single().extension
else -> kotlin.run {
val target = config.value[extensionType.qualifiedName!!]
?: return promptForSelectionAndSave(extensionType, candidates)
val found = candidates.firstOrNull { it.extension::class.qualifiedName == target }?.extension
?: return promptForSelectionAndSave(extensionType, candidates)
found
}
}
private fun <T : Extension> promptForSelectionAndSave(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>
) = promptForManualSelection(extensionType, candidates)
.also { config.value[extensionType.qualifiedName!!] = it.extension.kClassQualifiedName!! }.extension
private fun <T : Any> promptForManualSelection(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>
): ExtensionRegistry<T> {
MiraiConsole.mainLogger.info { "There are multiple ${extensionType.simpleName}s, please select one:" }
val candidatesList = candidates.toList()
for ((index, candidate) in candidatesList.withIndex()) {
MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin.name}'" }
}
MiraiConsole.mainLogger.info { "Please choose a number from 1 to ${candidatesList.count()}" }
val choiceStr = runBlocking { ConsoleInput.requestInput("Your choice: ") }
val choice = choiceStr.toIntOrNull() ?: error("Bad choice")
return candidatesList[choice - 1]
}
}

View File

@ -10,8 +10,16 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.data.valueFromKType
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.createType
@ -72,8 +80,8 @@ public object AllDenyPermissionService : PermissionService<PermissionImpl> {
}
@ExperimentalPermission
internal object BuiltInPermissionService : AbstractConcurrentPermissionService<PermissionImpl>(),
StorablePermissionService<PermissionImpl> {
public object BuiltInPermissionService : AbstractConcurrentPermissionService<PermissionImpl>(),
PermissionService<PermissionImpl> {
@ExperimentalPermission
override val permissionType: KClass<PermissionImpl>
@ -87,10 +95,43 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
override fun createPermission(id: PermissionId, description: String, base: PermissionId?): PermissionImpl =
PermissionImpl(id, description, base)
override val config: StorablePermissionService.ConcurrentSaveData<PermissionImpl> =
StorablePermissionService.ConcurrentSaveData(
internal val config: ConcurrentSaveData<PermissionImpl> =
ConcurrentSaveData(
PermissionImpl::class.createType(),
"PermissionService",
AutoSavePluginConfig()
)
@Suppress("RedundantVisibilityModifier")
@ExperimentalPermission
internal class ConcurrentSaveData<P : Permission> private constructor(
permissionType: KType,
public override val saveName: String,
delegate: PluginConfig,
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
) : PluginConfig by delegate {
public val permissions: MutableMap<PermissionId, P>
by valueFromKType<MutableMap<PermissionId, P>>(
MutableMap::class.createType(
listOf(
KTypeProjection(KVariance.INVARIANT, PermissionId::class.createType()),
KTypeProjection(KVariance.INVARIANT, permissionType),
)
),
ConcurrentHashMap()
)
public val grantedPermissionMap: MutableMap<PermissionId, List<PermissibleIdentifier>>
by value<MutableMap<PermissionId, List<PermissibleIdentifier>>>(ConcurrentHashMap())
.withDefault { CopyOnWriteArrayList() }
public companion object {
@JvmStatic
public operator fun <P : Permission> invoke(
permissionType: KType,
saveName: String,
delegate: PluginConfig,
): ConcurrentSaveData<P> = ConcurrentSaveData(permissionType, saveName, delegate, null)
}
}
}

View File

@ -11,6 +11,7 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.extension.SingletonExtensionPoint.Companion.findSingleton
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grantedWith
import java.util.concurrent.CopyOnWriteArrayList
@ -49,13 +50,10 @@ public interface PermissionService<P : Permission> {
public fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P)
public companion object {
private val builtIn: PermissionService<out Permission> get() = BuiltInPermissionService
@get:JvmName("getInstance")
@JvmStatic
public val INSTANCE: PermissionService<out Permission> by lazy {
PermissionServiceProvider.getExtensions().singleOrNull()?.extension?.instance ?: builtIn
// TODO: 2020/9/4 ExtensionSelector
PermissionServiceProvider.findSingleton()?.instance ?: BuiltInPermissionService
}
}
}

View File

@ -1,52 +0,0 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.data.valueFromKType
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.createType
@ExperimentalPermission
public interface StorablePermissionService<P : Permission> : PermissionService<P> {
/**
* The config to be stored
*/
public val config: PluginConfig
@ExperimentalPermission
public class ConcurrentSaveData<P : Permission> private constructor(
permissionType: KType,
public override val saveName: String,
delegate: PluginConfig,
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
) : PluginConfig by delegate {
public val permissions: MutableMap<PermissionId, P>
by valueFromKType<MutableMap<PermissionId, P>>(
MutableMap::class.createType(
listOf(
KTypeProjection(KVariance.INVARIANT, PermissionId::class.createType()),
KTypeProjection(KVariance.INVARIANT, permissionType),
)
),
ConcurrentHashMap()
)
public val grantedPermissionMap: MutableMap<PermissionId, List<PermissibleIdentifier>>
by value<MutableMap<PermissionId, List<PermissibleIdentifier>>>(ConcurrentHashMap())
.withDefault { CopyOnWriteArrayList() }
public companion object {
@JvmStatic
public operator fun <P : Permission> invoke(
permissionType: KType,
saveName: String,
delegate: PluginConfig,
): ConcurrentSaveData<P> = ConcurrentSaveData(permissionType, saveName, delegate, null)
}
}
}