Fix delegation in Values;

Rename IllegalMiraiConsoleImplementationError to MalformedMiraiConsoleImplementationError;
Support Managers command
Misc improvements
This commit is contained in:
Him188 2020-08-26 21:27:28 +08:00
parent 71b1464e25
commit a6aa160a97
17 changed files with 331 additions and 108 deletions

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsole.INSTANCE
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.util.childScopeContext
import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
@ -87,7 +88,7 @@ public interface MiraiConsole : CoroutineScope {
*/ // MiraiConsole.INSTANCE.getJob() */ // MiraiConsole.INSTANCE.getJob()
public val job: Job public val job: Job
get() = MiraiConsole.coroutineContext[Job] get() = MiraiConsole.coroutineContext[Job]
?: throw IllegalMiraiConsoleImplementationError("Internal error: Job not found in MiraiConsole.coroutineContext") ?: throw MalformedMiraiConsoleImplementationError("Internal error: Job not found in MiraiConsole.coroutineContext")
/** /**
* 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录. * 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录.
@ -102,6 +103,7 @@ public interface MiraiConsole : CoroutineScope {
Bot(id, password) { Bot(id, password) {
fileBasedDeviceInfo() fileBasedDeviceInfo()
redirectNetworkLogToDirectory() redirectNetworkLogToDirectory()
parentCoroutineContext = MiraiConsole.childScopeContext()
this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this) this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this)
configuration() configuration()
@ -119,8 +121,10 @@ public val MiraiConsole.rootDir: File get() = rootPath.toFile()
* *
* @see MiraiConsoleImplementation.start * @see MiraiConsoleImplementation.start
*/ */
public class IllegalMiraiConsoleImplementationError @JvmOverloads constructor( public class MalformedMiraiConsoleImplementationError : Error {
public override val message: String? = null, public constructor() : super()
public override val cause: Throwable? = null public constructor(message: String?) : super(message)
) : Error() public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -81,6 +81,7 @@ public interface MiraiConsoleImplementation : CoroutineScope {
public val dataStorageForJarPluginLoader: PluginDataStorage public val dataStorageForJarPluginLoader: PluginDataStorage
public val configStorageForJarPluginLoader: PluginDataStorage public val configStorageForJarPluginLoader: PluginDataStorage
public val dataStorageForBuiltIns: PluginDataStorage public val dataStorageForBuiltIns: PluginDataStorage
public val configStorageForBuiltIns: PluginDataStorage
/** /**
* @see ConsoleInput 的实现 * @see ConsoleInput 的实现
@ -111,7 +112,7 @@ public interface MiraiConsoleImplementation : CoroutineScope {
/** 由前端调用, 初始化 [MiraiConsole] 实例, 并启动 */ /** 由前端调用, 初始化 [MiraiConsole] 实例, 并启动 */
@JvmStatic @JvmStatic
@ConsoleFrontEndImplementation @ConsoleFrontEndImplementation
@Throws(IllegalMiraiConsoleImplementationError::class) @Throws(MalformedMiraiConsoleImplementationError::class)
public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock { public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock {
this@Companion.instance = this this@Companion.instance = this
MiraiConsoleImplementationBridge.doStart() MiraiConsoleImplementationBridge.doStart()

View File

@ -14,13 +14,20 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.util.BotManager.INSTANCE.addManager
import net.mamoe.mirai.console.util.BotManager.INSTANCE.managers
import net.mamoe.mirai.console.util.BotManager.INSTANCE.removeManager
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.message.nextMessageOrNull
import net.mamoe.mirai.utils.secondsToMillis import net.mamoe.mirai.utils.secondsToMillis
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -51,9 +58,36 @@ public object BuiltInCommands {
} }
} }
public object Managers : CompositeCommand(
ConsoleCommandOwner, "managers",
description = "Manage the managers for each bot",
permission = CommandPermission.Console or CommandPermission.Manager
), BuiltInCommand {
@Permission(CommandPermission.Console::class)
@SubCommand
public suspend fun CommandSender.add(target: User) {
target.bot.addManager(target.id)
sendMessage("已成功添加 ${target.render()}${target.bot.render()} 的管理员")
}
@Permission(CommandPermission.Console::class)
@SubCommand
public suspend fun CommandSender.remove(target: User) {
target.bot.removeManager(target.id)
sendMessage("已成功取消 ${target.render()}${target.bot.render()} 的管理员权限")
}
@SubCommand
public suspend fun CommandSender.list(bot: Bot) {
sendMessage("$bot 的管理员列表:\n" + bot.managers.joinToString("\n") {
bot.getFriendOrNull(it)?.render() ?: it.toString()
})
}
}
public object Help : SimpleCommand( public object Help : SimpleCommand(
ConsoleCommandOwner, "help", ConsoleCommandOwner, "help",
description = "Gets help about the console." description = "Command list"
), BuiltInCommand { ), BuiltInCommand {
@Handler @Handler
public suspend fun CommandSender.handle() { public suspend fun CommandSender.handle() {
@ -63,16 +97,17 @@ public object BuiltInCommands {
} }
} }
public object Stop : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "Stop the whole world."
), BuiltInCommand {
init { init {
Runtime.getRuntime().addShutdownHook(thread(false) { Runtime.getRuntime().addShutdownHook(thread(false) {
MiraiConsole.cancel() MiraiConsole.cancel()
}) })
} }
public object Stop : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "Stop the whole world."
), BuiltInCommand {
private val closingLock = Mutex() private val closingLock = Mutex()
@Handler @Handler
@ -122,6 +157,16 @@ public object BuiltInCommands {
} }
} }
internal fun ContactOrBot.render(): String {
return when (this) {
is Bot -> "Bot $nick($id)"
is Group -> "Group $name($id)"
is Friend -> "Friend $nick($id)"
is Member -> "Friend $nameCardOrNick($id)"
else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}")
}
}
/* /*
/** /**

View File

@ -16,6 +16,7 @@ import kotlinx.coroutines.*
import net.mamoe.mirai.console.internal.plugin.updateWhen import net.mamoe.mirai.console.internal.plugin.updateWhen
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.currentTimeMillis
/** /**
@ -39,7 +40,11 @@ public open class AutoSavePluginData private constructor(
override fun onStored(owner: PluginDataHolder, storage: PluginDataStorage) { override fun onStored(owner: PluginDataHolder, storage: PluginDataStorage) {
check(owner is AutoSavePluginDataHolder) { "owner must be AutoSavePluginDataHolder for AutoSavePluginData" } check(owner is AutoSavePluginDataHolder) { "owner must be AutoSavePluginDataHolder for AutoSavePluginData" }
check(!this::storage_.isInitialized) { "storage is already initialized" }
if (this::storage_.isInitialized) {
check(storage == this.storage_) { "AutoSavePluginData is already initialized with one storage and cannot be reinitialized with another." }
}
this.storage_ = storage this.storage_ = storage
this.owner_ = owner this.owner_ = owner
@ -90,10 +95,16 @@ public open class AutoSavePluginData private constructor(
@Suppress("RedundantVisibilityModifier") @Suppress("RedundantVisibilityModifier")
@ConsoleInternalAPI @ConsoleInternalAPI
public final override fun onValueChanged(value: Value<*>) { public final override fun onValueChanged(value: Value<*>) {
debuggingLogger1.error("onValueChanged: $value")
if (::owner_.isInitialized) { if (::owner_.isInitialized) {
lastAutoSaveJob_ = owner_.launch(block = updaterBlock) lastAutoSaveJob_ = owner_.launch(block = updaterBlock)
} }
} }
private fun doSave() = storage_.store(owner_, this) private fun doSave() {
debuggingLogger1.error("doSave: ${this::class.qualifiedName}")
storage_.store(owner_, this)
} }
}
internal val debuggingLogger1 = DefaultLogger("debug")

View File

@ -76,7 +76,7 @@ import kotlin.reflect.full.findAnnotation
* @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. * @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
* @see PluginDataStorage [PluginData] 存储仓库 * @see PluginDataStorage [PluginData] 存储仓库
*/ */
public interface PluginData { public interface PluginData : PluginDataExtensions {
/** /**
* 添加了追踪的 [ValueNode] 列表 (即使用 `by value()` 委托的属性), 即通过 `by value` 初始化的属性列表. * 添加了追踪的 [ValueNode] 列表 (即使用 `by value()` 委托的属性), 即通过 `by value` 初始化的属性列表.
* *

View File

@ -0,0 +1,86 @@
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.internal.data.CompositeMapValueImpl
import net.mamoe.mirai.console.internal.data.castOrInternalError
import net.mamoe.mirai.console.internal.data.createCompositeMapValueImpl
@Suppress("INAPPLICABLE_JVM_NAME", "UNCHECKED_CAST")
public interface PluginDataExtensions {
@JvmName("withDefaultImmutable")
public fun <V, K> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<Map<K, V>> {
return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<Map<K, V>>
}
@JvmName("withDefaultImmutableMap")
public fun <M : Map<K, V>, V : Map<*, *>, K> SerializerAwareValue<M>.withEmptyDefault(): SerializerAwareValue<M> {
return this.withDefault { LinkedHashMap<Any?, Any?>() as V }
}
@JvmName("withDefaultImmutableSet")
public fun <M : Map<K, V>, V : Set<*>, K> SerializerAwareValue<M>.withEmptyDefault(): SerializerAwareValue<M> {
return this.withDefault { LinkedHashSet<Any?>() as V }
}
@JvmName("withDefaultImmutableList")
public fun <M : Map<K, V>, V : List<*>, K> SerializerAwareValue<M>.withEmptyDefault(): SerializerAwareValue<M> {
return this.withDefault { ArrayList<Any?>() as V }
}
public fun <M : Map<K, V>, V, K> SerializerAwareValue<M>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<M> {
val pluginData = this@PluginDataExtensions.castOrInternalError<PluginData>()
val origin = (this as SerializableValue<M>).delegate.castOrInternalError<CompositeMapValueImpl<K, V>>()
return SerializableValue(
object : CompositeMapValue<K, V> {
private val instance = object : MutableMap<K, V> {
override val size: Int get() = origin.value.size
override fun containsKey(key: K): Boolean = origin.value.containsKey(key)
override fun containsValue(value: V): Boolean = origin.value.containsValue(value)
override fun isEmpty(): Boolean = origin.value.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = origin.value.entries as MutableSet<MutableMap.MutableEntry<K, V>>
override val keys: MutableSet<K> get() = origin.value.keys as MutableSet<K>
override val values: MutableCollection<V> get() = origin.value.values as MutableCollection<V>
override fun clear() = (origin.value as MutableMap<K, V>).clear()
override fun putAll(from: Map<out K, V>) = (origin.value as MutableMap<K, V>).putAll(from)
override fun remove(key: K): V? = (origin.value as MutableMap<K, V>).remove(key)
override fun put(key: K, value: V): V? = (origin.value as MutableMap<K, V>).put(key, value)
override fun get(key: K): V? {
// the only difference
val result = origin.value[key]
if (result != null) {
return result
}
put(key, defaultValueComputer(key))
return origin.value[key]
}
}
override var value: Map<K, V>
get() = instance
set(value) {
origin.value = value
}
} as Value<M>,
this.serializer
)
return pluginData.createCompositeMapValueImpl(
kToValue = origin.kToValue,
vToValue = origin.vToValue,
applyToShadowedMap = { theMap ->
object : MutableMap<K, V> by theMap {
override fun get(key: K): V? {
val result = theMap[key]
if (result != null) return result
theMap[key] = defaultValueComputer(key)
return theMap[key]
}
}
}
).let { SerializableValue(it, serializer) } as SerializerAwareValue<M>
}
}

View File

@ -40,7 +40,7 @@ public interface Value<T> {
* 可被序列化的 [Value]. * 可被序列化的 [Value].
*/ */
public class SerializableValue<T>( public class SerializableValue<T>(
private val delegate: Value<T>, @JvmField internal val delegate: Value<T>,
/** /**
* 用于更新和保存 [delegate] 的序列化器 * 用于更新和保存 [delegate] 的序列化器
*/ */
@ -56,7 +56,9 @@ public class SerializableValue<T>(
): SerializableValue<T> { ): SerializableValue<T> {
return SerializableValue( return SerializableValue(
this, this,
serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) }) serializer.map(serializer = {
this.value
}, deserializer = { this.setValueBySerializer(it) })
) )
} }
} }

View File

@ -15,7 +15,7 @@ import com.vdurmont.semver4j.Semver
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.IllegalMiraiConsoleImplementationError import net.mamoe.mirai.console.MalformedMiraiConsoleImplementationError
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation
@ -26,7 +26,7 @@ import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.ConsoleBuiltInPluginDataStorage import net.mamoe.mirai.console.internal.util.ConsoleDataScope
import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.center.PluginCenter
@ -62,6 +62,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
override val dataStorageForJarPluginLoader: PluginDataStorage by instance::dataStorageForJarPluginLoader override val dataStorageForJarPluginLoader: PluginDataStorage by instance::dataStorageForJarPluginLoader
override val configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader override val configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader
override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
override val consoleInput: ConsoleInput by instance::consoleInput override val consoleInput: ConsoleInput by instance::consoleInput
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
@ -83,15 +84,18 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.info { frontEndDescription.render() } mainLogger.info { frontEndDescription.render() }
if (coroutineContext[Job] == null) { if (coroutineContext[Job] == null) {
throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
} }
MiraiConsole.job.invokeOnCompletion { MiraiConsole.job.invokeOnCompletion {
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
} }
mainLogger.info { "Reloading configurations..." }
ConsoleDataScope.reloadAll()
BuiltInCommands.registerAll() BuiltInCommands.registerAll()
mainLogger.info { "Preparing built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
CommandManagerImpl.commandListener // start CommandManagerImpl.commandListener // start
mainLogger.info { "Loading plugins..." } mainLogger.info { "Loading plugins..." }
@ -99,7 +103,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." } mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." }
mainLogger.info { "mirai-console started successfully." } mainLogger.info { "mirai-console started successfully." }
ConsoleBuiltInPluginDataStorage // init
// Only for initialize // Only for initialize
} }
} }

View File

@ -130,11 +130,11 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
removeSubName: Boolean removeSubName: Boolean
) { ) {
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0) val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0)
if (args == null || !onCommand( if (!this.permission.testPermission(sender)) {
sender, sender.sendMessage(usage) // TODO: 2020/8/26 #127
args return
) }
) { if (args == null || !onCommand(sender, args)) {
sender.sendMessage(usage) sender.sendMessage(usage)
} }
} }

View File

@ -110,9 +110,12 @@ internal abstract class CompositeListValueImpl<T>(
// workaround to a type inference bug // workaround to a type inference bug
internal fun <K, V> PluginData.createCompositeMapValueImpl( internal fun <K, V> PluginData.createCompositeMapValueImpl(
kToValue: (K) -> Value<K>, kToValue: (K) -> Value<K>,
vToValue: (V) -> Value<V> vToValue: (V) -> Value<V>,
valueToK: (Value<K>) -> K = Value<K>::value,
valueToV: (Value<V>) -> V = Value<V>::value,
applyToShadowedMap: ((MutableMap<K, V>) -> (MutableMap<K, V>))? = null
): CompositeMapValueImpl<K, V> { ): CompositeMapValueImpl<K, V> {
return object : CompositeMapValueImpl<K, V>(kToValue, vToValue) { return object : CompositeMapValueImpl<K, V>(kToValue, vToValue, valueToK, valueToV, applyToShadowedMap) {
override fun onChanged() = this@createCompositeMapValueImpl.onValueChanged(this) override fun onChanged() = this@createCompositeMapValueImpl.onValueChanged(this)
} }
} }
@ -120,13 +123,20 @@ internal fun <K, V> PluginData.createCompositeMapValueImpl(
// TODO: 2020/6/24 在一个 Value 被删除后停止追踪其更新. // TODO: 2020/6/24 在一个 Value 被删除后停止追踪其更新.
internal abstract class CompositeMapValueImpl<K, V>( internal abstract class CompositeMapValueImpl<K, V>(
kToValue: (K) -> Value<K>, // should override onChanged @JvmField internal val kToValue: (K) -> Value<K>, // should override onChanged
vToValue: (V) -> Value<V> // should override onChanged @JvmField internal val vToValue: (V) -> Value<V>, // should override onChanged
@JvmField internal val valueToK: (Value<K>) -> K = Value<K>::value,
@JvmField internal val valueToV: (Value<V>) -> V = Value<V>::value,
applyToShadowedMap: ((MutableMap<K, V>) -> (MutableMap<K, V>))? = null
) : CompositeMapValue<K, V>, AbstractValueImpl<Map<K, V>>() { ) : CompositeMapValue<K, V>, AbstractValueImpl<Map<K, V>>() {
private val internalList: MutableMap<Value<K>, Value<V>> = mutableMapOf() @JvmField
internal val internalList: MutableMap<Value<K>, Value<V>> = mutableMapOf()
private var _value: MutableMap<K, V> = private var _value: MutableMap<K, V> =
internalList.shadowMap({ it.value }, kToValue, { it.value }, vToValue).observable { onChanged() } internalList.shadowMap(valueToK, kToValue, valueToV, vToValue).let {
applyToShadowedMap?.invoke(it) ?: it
}.observable { onChanged() }
override var value: Map<K, V> override var value: Map<K, V>
get() = _value get() = _value
set(v) { set(v) {

View File

@ -18,21 +18,22 @@ import kotlin.reflect.KClass
// TODO: 2020/6/24 优化性能: 引入一个 comparator 之类来替代将 Int 包装为 Value<Int> 后进行 containsKey 比较的方法 // TODO: 2020/6/24 优化性能: 引入一个 comparator 之类来替代将 Int 包装为 Value<Int> 后进行 containsKey 比较的方法
internal inline fun <K, V, KR, VR> MutableMap<K, V>.shadowMap(
crossinline kTransform: (K) -> KR, internal open class ShadowMap<K, V, KR, VR>(
crossinline kTransformBack: (KR) -> K, private val originMap: MutableMap<K, V>,
crossinline vTransform: (V) -> VR, private val kTransform: (K) -> KR,
crossinline vTransformBack: (VR) -> V private val kTransformBack: (KR) -> K,
private val vTransform: (V) -> VR,
private val vTransformBack: (VR) -> V
) : MutableMap<KR, VR> { ) : MutableMap<KR, VR> {
return object : MutableMap<KR, VR> { override val size: Int get() = originMap.size
override val size: Int get() = this@shadowMap.size override fun containsKey(key: KR): Boolean = originMap.containsKey(key.let(kTransformBack))
override fun containsKey(key: KR): Boolean = this@shadowMap.containsKey(key.let(kTransformBack)) override fun containsValue(value: VR): Boolean = originMap.containsValue(value.let(vTransformBack))
override fun containsValue(value: VR): Boolean = this@shadowMap.containsValue(value.let(vTransformBack)) override fun get(key: KR): VR? = originMap[key.let(kTransformBack)]?.let(vTransform)
override fun get(key: KR): VR? = this@shadowMap[key.let(kTransformBack)]?.let(vTransform) override fun isEmpty(): Boolean = originMap.isEmpty()
override fun isEmpty(): Boolean = this@shadowMap.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<KR, VR>> override val entries: MutableSet<MutableMap.MutableEntry<KR, VR>>
get() = this@shadowMap.entries.shadowMap( get() = originMap.entries.shadowMap(
transform = { entry: MutableMap.MutableEntry<K, V> -> transform = { entry: MutableMap.MutableEntry<K, V> ->
object : MutableMap.MutableEntry<KR, VR> { object : MutableMap.MutableEntry<KR, VR> {
override val key: KR get() = entry.key.let(kTransform) override val key: KR get() = entry.key.let(kTransform)
@ -65,26 +66,46 @@ internal inline fun <K, V, KR, VR> MutableMap<K, V>.shadowMap(
} }
) )
override val keys: MutableSet<KR> override val keys: MutableSet<KR>
get() = this@shadowMap.keys.shadowMap(kTransform, kTransformBack) get() = originMap.keys.shadowMap(kTransform, kTransformBack)
override val values: MutableCollection<VR> override val values: MutableCollection<VR>
get() = this@shadowMap.values.shadowMap(vTransform, vTransformBack) get() = originMap.values.shadowMap(vTransform, vTransformBack)
override fun clear() = this@shadowMap.clear() override fun clear() = originMap.clear()
override fun put(key: KR, value: VR): VR? = override fun put(key: KR, value: VR): VR? =
this@shadowMap.put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) originMap.put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform)
override fun putAll(from: Map<out KR, VR>) { override fun putAll(from: Map<out KR, VR>) {
from.forEach { (kr, vr) -> from.forEach { (kr, vr) ->
this@shadowMap[kr.let(kTransformBack)] = vr.let(vTransformBack) originMap[kr.let(kTransformBack)] = vr.let(vTransformBack)
} }
} }
override fun remove(key: KR): VR? = this@shadowMap.remove(key.let(kTransformBack))?.let(vTransform) override fun remove(key: KR): VR? = originMap.remove(key.let(kTransformBack))?.let(vTransform)
override fun toString(): String = this@shadowMap.toString() override fun toString(): String = originMap.toString()
override fun hashCode(): Int = this@shadowMap.hashCode() override fun hashCode(): Int = originMap.hashCode()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ShadowMap<*, *, *, *>
if (originMap != other.originMap) return false
if (kTransform != other.kTransform) return false
if (kTransformBack != other.kTransformBack) return false
if (vTransform != other.vTransform) return false
if (vTransformBack != other.vTransformBack) return false
return true
} }
} }
internal fun <K, V, KR, VR> MutableMap<K, V>.shadowMap(
kTransform: (K) -> KR,
kTransformBack: (KR) -> K,
vTransform: (V) -> VR,
vTransformBack: (VR) -> V
): MutableMap<KR, VR> = ShadowMap(this, kTransform, kTransformBack, vTransform, vTransformBack)
internal inline fun <E, R> MutableCollection<E>.shadowMap( internal inline fun <E, R> MutableCollection<E>.shadowMap(
crossinline transform: (E) -> R, crossinline transform: (E) -> R,
crossinline transformBack: (R) -> E crossinline transformBack: (R) -> E

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.console.data.SerializableValue.Companion.serializableValu
import net.mamoe.mirai.console.data.SerializerAwareValue import net.mamoe.mirai.console.data.SerializerAwareValue
import net.mamoe.mirai.console.data.valueFromKType import net.mamoe.mirai.console.data.valueFromKType
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import kotlin.contracts.contract
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KType import kotlin.reflect.KType
@ -148,4 +149,17 @@ internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean {
@PublishedApi @PublishedApi
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal inline fun <R> Any.cast(): R = this as R internal inline fun <reified R> Any.cast(): R {
contract {
returns() implies (this@cast is R)
}
return this as R
}
@Suppress("UNCHECKED_CAST")
internal inline fun <reified R> Any.castOrInternalError(): R {
contract {
returns() implies (this@castOrInternalError is R)
}
return (this as? R) ?: error("Internal error: ${this::class} cannot be casted to ${R::class}")
}

View File

@ -42,22 +42,47 @@ internal object BotManagerImpl : BotManager {
} }
} }
internal object ManagersConfig : AutoSavePluginData() { internal object ManagersConfig : AutoSavePluginConfig() {
private val managers: MutableMap<Long, MutableSet<Long>> by value() private val managers by value<MutableMap<Long, MutableSet<Long>>>().withEmptyDefault()
internal operator fun get(bot: Bot): MutableSet<Long> = managers[bot.id]!!
internal operator fun get(bot: Bot): MutableSet<Long> = managers.getOrPut(bot.id, ::mutableSetOf)
} }
internal fun CoroutineContext.overrideWithSupervisorJob(): CoroutineContext = this + SupervisorJob(this[Job]) internal fun CoroutineContext.overrideWithSupervisorJob(): CoroutineContext = this + SupervisorJob(this[Job])
internal fun CoroutineScope.childScope(context: CoroutineContext = EmptyCoroutineContext): CoroutineScope = internal fun CoroutineScope.childScope(context: CoroutineContext = EmptyCoroutineContext): CoroutineScope =
CoroutineScope(this.coroutineContext.overrideWithSupervisorJob() + context) CoroutineScope(this.childScopeContext(context))
internal fun CoroutineScope.childScopeContext(context: CoroutineContext = EmptyCoroutineContext): CoroutineContext =
this.coroutineContext.overrideWithSupervisorJob() + context
internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope() {
private val data: Array<out PluginData> = arrayOf()
private val configs: Array<out PluginConfig> = arrayOf(ManagersConfig)
fun reloadAll() {
data.forEach { dt ->
ConsoleBuiltInPluginDataStorage.load(ConsoleBuiltInPluginDataHolder, dt)
}
configs.forEach { config ->
ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config)
}
}
}
internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder, internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
CoroutineScope by MiraiConsole.childScope() { CoroutineScope by ConsoleDataScope.childScope() {
override val autoSaveIntervalMillis: LongRange = 30.minutesToMillis..60.minutesToMillis override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val name: String get() = "ConsoleBuiltIns"
}
internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope() {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val name: String get() = "ConsoleBuiltIns" override val name: String get() = "ConsoleBuiltIns"
} }
internal object ConsoleBuiltInPluginDataStorage : internal object ConsoleBuiltInPluginDataStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns
internal object ConsoleBuiltInPluginConfigStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.configStorageForBuiltIns

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.plugin.jvm package net.mamoe.mirai.console.plugin.jvm
import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal
import net.mamoe.mirai.utils.minutesToSeconds import net.mamoe.mirai.utils.minutesToMillis
import net.mamoe.mirai.utils.secondsToMillis import net.mamoe.mirai.utils.secondsToMillis
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@ -28,5 +28,5 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor(
) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) { ) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) {
public final override val name: String get() = this.description.name public final override val name: String get() = this.description.name
public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToSeconds public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToMillis
} }

View File

@ -48,9 +48,11 @@ fun initTestEnvironment() {
override val consoleCommandSender: ConsoleCommandSender = object : ConsoleCommandSender() { override val consoleCommandSender: ConsoleCommandSender = object : ConsoleCommandSender() {
override suspend fun sendMessage(message: Message) = println(message) override suspend fun sendMessage(message: Message) = println(message)
} }
override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage() override val dataStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
override val configStorageForJarPluginLoader: PluginDataStorage get() = TODO("Not yet implemented") override val configStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage() override val dataStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage()
override val configStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage()
override val consoleInput: ConsoleInput = object : ConsoleInput { override val consoleInput: ConsoleInput = object : ConsoleInput {
override suspend fun requestInput(hint: String): String { override suspend fun requestInput(hint: String): String {
println(hint) println(hint)

View File

@ -10,7 +10,6 @@
package net.mamoe.mirai.console.data package net.mamoe.mirai.console.data
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder.Companion.createPluginData
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -22,7 +21,7 @@ internal class PluginDataTest {
object MyPlugin : KotlinPlugin() object MyPlugin : KotlinPlugin()
class MyPluginData : PluginData by MyPlugin.createPluginData() { class MyPluginData : AutoSavePluginData() {
var int by value(1) var int by value(1)
val map: MutableMap<String, String> by value() val map: MutableMap<String, String> by value()
val map2: MutableMap<String, MutableMap<String, String>> by value() val map2: MutableMap<String, MutableMap<String, String>> by value()

View File

@ -58,7 +58,6 @@ import java.util.*
/** /**
* mirai-console-pure 后端实现 * mirai-console-pure 后端实现
* *
* @see MiraiConsoleFrontEndPure 前端实现
* @see MiraiConsolePureLoader CLI 入口点 * @see MiraiConsolePureLoader CLI 入口点
*/ */
internal class MiraiConsoleImplementationPure internal class MiraiConsoleImplementationPure
@ -71,7 +70,8 @@ internal class MiraiConsoleImplementationPure
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl, override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,
override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")) override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config"))
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) { ) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) {
override val mainLogger: MiraiLogger by lazy { override val mainLogger: MiraiLogger by lazy {
MiraiConsole.newLogger("main") MiraiConsole.newLogger("main")