Public API stabilization: misc improvements

This commit is contained in:
Him188 2020-08-23 16:36:49 +08:00
parent 66e4852c4d
commit c821f9e9e9
10 changed files with 106 additions and 45 deletions

View File

@ -54,6 +54,7 @@ kotlin {
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
useExperimentalAnnotation("net.mamoe.mirai.console.data.ExperimentalPluginConfig")
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalAPI")
}
}
}

View File

@ -21,9 +21,9 @@ import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandManagerImpl
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.ConsoleBuiltInPluginDataStorage

View File

@ -0,0 +1,23 @@
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.CommandPermission
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.hasPermission
internal class OrCommandPermissionImpl(
private val first: CommandPermission,
private val second: CommandPermission
) : CommandPermission {
override fun CommandSender.hasPermission(): Boolean {
return this.hasPermission(first) || this.hasPermission(second)
}
}
internal class AndCommandPermissionImpl(
private val first: CommandPermission,
private val second: CommandPermission
) : CommandPermission {
override fun CommandSender.hasPermission(): Boolean {
return this.hasPermission(first) && this.hasPermission(second)
}
}

View File

@ -1,10 +1,7 @@
package net.mamoe.mirai.console.internal.data
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.mamoe.mirai.console.data.*
import net.mamoe.mirai.console.internal.plugin.updateWhen
import net.mamoe.mirai.console.util.ConsoleInternalAPI
@ -27,6 +24,15 @@ internal open class AutoSavePluginData(
override fun setStorage(storage: PluginDataStorage) {
check(!this::storage.isInitialized) { "storage is already initialized" }
this.storage = storage
if (shouldPerformAutoSaveWheneverChanged()) {
owner.launch {
while (isActive) {
delay(owner.autoSaveIntervalMillis.last) // 定时自动保存一次, 用于 kts 序列化的对象
doSave()
}
}
}
}
@JvmField
@ -34,25 +40,30 @@ internal open class AutoSavePluginData(
internal var lastAutoSaveJob: Job? = null
@JvmField
@Volatile
internal var currentFirstStartTime = atomic(0L)
internal val currentFirstStartTime = atomic(0L)
protected open fun shouldPerformAutoSaveWheneverChanged(): Boolean {
return true
}
init {
owner.coroutineContext[Job]?.invokeOnCompletion { doSave() }
}
private val updaterBlock: suspend CoroutineScope.() -> Unit = {
currentFirstStartTime.updateWhen({ it == 0L }, { currentTimeMillis })
if (::storage.isInitialized) {
currentFirstStartTime.updateWhen({ it == 0L }, { currentTimeMillis })
delay(owner.autoSaveIntervalMillis.first.coerceAtLeast(1000)) // for safety
delay(owner.autoSaveIntervalMillis.first.coerceAtLeast(1000)) // for safety
if (lastAutoSaveJob == this.coroutineContext[Job]) {
doSave()
} else {
if (currentFirstStartTime.updateWhen(
{ currentTimeMillis - it >= owner.autoSaveIntervalMillis.last },
{ 0 })
) doSave()
if (lastAutoSaveJob == this.coroutineContext[Job]) {
doSave()
} else {
if (currentFirstStartTime.updateWhen(
{ currentTimeMillis - it >= owner.autoSaveIntervalMillis.last },
{ 0 })
) doSave()
}
}
}

View File

@ -52,7 +52,7 @@ internal open class MultiFilePluginDataStorageImpl(
}.also { it.setStorage(this) }
protected open fun getPluginDataFile(holder: PluginDataHolder, clazz: KClass<*>): File = with(clazz) {
val name = findASerialName()
val name = findValueName()
val dir = directoryPath.resolve(holder.name)
if (dir.isFile) {

View File

@ -29,7 +29,7 @@ import net.mamoe.yamlkt.YamlNullableDynamicSerializer
* - Auto-saving
*/
internal abstract class PluginDataImpl {
internal fun findNodeInstance(name: String): ValueNode<*>? = valueNodes.firstOrNull { it.serialName == name }
internal fun findNodeInstance(name: String): ValueNode<*>? = valueNodes.firstOrNull { it.valueName == name }
internal abstract val valueNodes: MutableList<ValueNode<*>>
@ -43,8 +43,8 @@ internal abstract class PluginDataImpl {
if (decodeSequentially()) {
var index = 0
repeat(decodeCollectionSize(descriptor)) {
val serialName = decodeSerializableElement(descriptor, index++, String.serializer())
val node = findNodeInstance(serialName)
val valueName = decodeSerializableElement(descriptor, index++, String.serializer())
val node = findNodeInstance(valueName)
if (node == null) {
decodeSerializableElement(descriptor, index++, YamlNullableDynamicSerializer)
} else {
@ -53,21 +53,21 @@ internal abstract class PluginDataImpl {
}
} else {
outerLoop@ while (true) {
var serialName: String? = null
var valueName: String? = null
innerLoop@ while (true) {
val index = decodeElementIndex(descriptor)
if (index == CompositeDecoder.DECODE_DONE) {
check(serialName == null) { "name must be null at this moment." }
check(valueName == null) { "name must be null at this moment." }
break@outerLoop
}
if (!index.isOdd()) { // key
check(serialName == null) { "name must be null at this moment" }
serialName = decodeSerializableElement(descriptor, index, String.serializer())
check(valueName == null) { "name must be null at this moment" }
valueName = decodeSerializableElement(descriptor, index, String.serializer())
} else {
check(serialName != null) { "name must not be null at this moment" }
check(valueName != null) { "name must not be null at this moment" }
val node = findNodeInstance(serialName)
val node = findNodeInstance(valueName)
if (node == null) {
decodeSerializableElement(descriptor, index, YamlNullableDynamicSerializer)
} else {
@ -92,8 +92,8 @@ internal abstract class PluginDataImpl {
var index = 0
// val vSerializer = dataUpdaterSerializerTypeArguments[1] as KSerializer<Any?>
valueNodes.forEach { (serialName, _, valueSerializer) ->
encodeSerializableElement(descriptor, index++, String.serializer(), serialName)
valueNodes.forEach { (valueName, _, valueSerializer) ->
encodeSerializableElement(descriptor, index++, String.serializer(), valueName)
encodeSerializableElement(descriptor, index++, valueSerializer, Unit)
}
endStructure(descriptor)

View File

@ -9,8 +9,8 @@
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.SerialName
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.ValueName
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
@ -20,7 +20,7 @@ import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.isSubclassOf
@Suppress("UNCHECKED_CAST")
internal inline fun <reified T : Any> KType.asKClass(): KClass<out T> {
internal inline fun <reified T : Any> KType.toKClass(): KClass<out T> {
val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" }
val fromClass = arguments[0].type?.classifier as? KClass<*> ?: Any::class
@ -34,24 +34,22 @@ internal inline fun <reified T : Any> KType.asKClass(): KClass<out T> {
}
internal inline fun <reified T : PluginData> newPluginDataInstanceUsingReflection(type: KType): T {
val classifier = type.asKClass<T>()
val classifier = type.toKClass<T>()
return with(classifier) {
objectInstance
?: createInstanceOrNull()
?: throw IllegalArgumentException(
"Cannot create PluginData instance. " +
"PluginDataHolder supports PluginDatas implemented as an object " +
"PluginDataHolder supports PluginData implemented as an object " +
"or the ones with a constructor which either has no parameters or all parameters of which are optional, by default newPluginDataInstance implementation."
)
}
}
internal fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass.java.isArray
@Suppress("UNCHECKED_CAST")
internal fun KType.kclass() = when (val t = classifier) {
internal fun KType.classifierAsKClass() = when (val t = classifier) {
is KClass<*> -> t
else -> error("Only KClass supported as classifier, got $t")
} as KClass<Any>
@ -65,14 +63,12 @@ internal fun <T : Any> KClass<T>.createInstanceOrNull(): T? {
}
@JvmSynthetic
internal fun KClass<*>.findASerialName(): String =
findAnnotation<SerialName>()?.value
internal fun KClass<*>.findValueName(): String =
findAnnotation<ValueName>()?.value
?: qualifiedName
?: throw IllegalArgumentException("Cannot find a serial name for $this")
internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation<SerialName>()?.value ?: this.name
internal fun Int.isOdd() = this and 0b1 != 0
internal val KProperty<*>.serialName: String get() = this.findAnnotation<SerialName>()?.value ?: this.name
internal val KProperty<*>.valueName: String get() = this.findAnnotation<ValueName>()?.value ?: this.name

View File

@ -39,7 +39,7 @@ import kotlin.reflect.KType
)
internal fun serializerMirai(type: KType): KSerializer<Any?> {
fun serializerByKTypeImpl(type: KType): KSerializer<Any> {
val rootClass = type.kclass()
val rootClass = type.classifierAsKClass()
val typeArguments = type.arguments
.map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
@ -60,7 +60,7 @@ internal fun serializerMirai(type: KType): KSerializer<Any?> {
Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
/* mamoe modify */ Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer
else -> {
if (isReferenceArray(rootClass)) {
if (rootClass.java.isArray) {
return ArraySerializer(
typeArguments[0].classifier as KClass<Any>,
serializers[0]

View File

@ -87,7 +87,19 @@ public enum class PluginKind {
}
/**
* 插件的一个依赖的信息
* 插件的一个依赖的信息.
*
* YAML 格式下, 典型的插件依赖示例:
* ```yaml
* dependencies:
* - name: "依赖的插件名" # 依赖的插件名
* version: "" # 依赖的版本号, 支持 Apache Ivy 格式. null 或不指定时不限制版本
* isOptional: true # `true` 表示插件在找不到此依赖时也能正常加载
* - "SamplePlugin" # 名称为 SamplePlugin 的插件, 不限制版本, isOptional=false
* - "TestPlugin:1.0.0+" # 名称为 ExamplePlugin 的插件, 版本至少为 1.0.0, isOptional=false
* - "ExamplePlugin:1.5.0+?" # 名称为 ExamplePlugin 的插件, 版本至少为 1.5.0, 末尾 `?` 表示 isOptional=true
* - "Another test plugin:[1.0.0, 2.0.0)" # 名称为 Another test plugin 的插件, 版本要求大于等于 1.0.0, 小于 2.0.0, isOptional=false
* ```
*
* @see PluginDescription.dependencies
*/
@ -100,7 +112,7 @@ public data class PluginDependency(
*
* 版本遵循 [语义化版本 2.0 规范](https://semver.org/lang/zh-CN/),
*
* 允许 [Apache Ivy 版本号](http://ant.apache.org/ivy/history/latest-milestone/ivyfile/dependency.html)
* 允许 [Apache Ivy 格版本号表示](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html)
*/
public val version: @Serializable(SemverAsStringSerializerIvy::class) Semver? = null,
/**
@ -124,7 +136,24 @@ public data class PluginDependency(
serializer(),
Yaml.nonStrict.encodeToString<Map<*, *>>(any)
)
else -> PluginDependency(any.toString())
else -> {
var value = any.toString()
val isOptional = value.endsWith('?')
if (isOptional) {
value = value.removeSuffix("?")
}
val components = value.split(':')
when (components.size) {
1 -> PluginDependency(value, isOptional = isOptional)
2 -> PluginDependency(
components[0],
Semver(components[1], Semver.SemverType.IVY),
isOptional = isOptional
)
else -> error("Illegal plugin dependency statement: $value")
}
}
}
}
)

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllComm
import net.mamoe.mirai.console.command.description.CommandArgumentParser
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
import net.mamoe.mirai.console.initTestEnvironment
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.PlainText