diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index a0f2805d8..a6d2f743d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -19,6 +19,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start 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.center.PluginCenter import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader @@ -87,7 +88,7 @@ public interface MiraiConsole : CoroutineScope { */ // MiraiConsole.INSTANCE.getJob() public val job: 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 列表, 但不登录. @@ -102,6 +103,7 @@ public interface MiraiConsole : CoroutineScope { Bot(id, password) { fileBasedDeviceInfo() redirectNetworkLogToDirectory() + parentCoroutineContext = MiraiConsole.childScopeContext() this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this) configuration() @@ -119,8 +121,10 @@ public val MiraiConsole.rootDir: File get() = rootPath.toFile() * * @see MiraiConsoleImplementation.start */ -public class IllegalMiraiConsoleImplementationError @JvmOverloads constructor( - public override val message: String? = null, - public override val cause: Throwable? = null -) : Error() +public class MalformedMiraiConsoleImplementationError : Error { + public constructor() : super() + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt index 816405aa1..1efa9ecac 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -81,6 +81,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { public val dataStorageForJarPluginLoader: PluginDataStorage public val configStorageForJarPluginLoader: PluginDataStorage public val dataStorageForBuiltIns: PluginDataStorage + public val configStorageForBuiltIns: PluginDataStorage /** * @see ConsoleInput 的实现 @@ -111,7 +112,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { /** 由前端调用, 初始化 [MiraiConsole] 实例, 并启动 */ @JvmStatic @ConsoleFrontEndImplementation - @Throws(IllegalMiraiConsoleImplementationError::class) + @Throws(MalformedMiraiConsoleImplementationError::class) public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock { this@Companion.instance = this MiraiConsoleImplementationBridge.doStart() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index 810bfe48b..43457b588 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -14,13 +14,20 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import net.mamoe.mirai.Bot import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.console.MiraiConsole 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.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.ConsoleInternalAPI +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.utils.secondsToMillis 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( ConsoleCommandOwner, "help", - description = "Gets help about the console." + description = "Command list" ), BuiltInCommand { @Handler public suspend fun CommandSender.handle() { @@ -63,15 +97,16 @@ public object BuiltInCommands { } } + init { + Runtime.getRuntime().addShutdownHook(thread(false) { + MiraiConsole.cancel() + }) + } + public object Stop : SimpleCommand( ConsoleCommandOwner, "stop", "shutdown", "exit", description = "Stop the whole world." ), BuiltInCommand { - init { - Runtime.getRuntime().addShutdownHook(thread(false) { - MiraiConsole.cancel() - }) - } private val closingLock = Mutex() @@ -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}") + } +} + /* /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt index 383bd7164..6a85777d9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.* import net.mamoe.mirai.console.internal.plugin.updateWhen import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI +import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.currentTimeMillis /** @@ -39,7 +40,11 @@ public open class AutoSavePluginData private constructor( override fun onStored(owner: PluginDataHolder, storage: PluginDataStorage) { 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.owner_ = owner @@ -90,10 +95,16 @@ public open class AutoSavePluginData private constructor( @Suppress("RedundantVisibilityModifier") @ConsoleInternalAPI public final override fun onValueChanged(value: Value<*>) { + debuggingLogger1.error("onValueChanged: $value") if (::owner_.isInitialized) { lastAutoSaveJob_ = owner_.launch(block = updaterBlock) } } - private fun doSave() = storage_.store(owner_, this) -} \ No newline at end of file + private fun doSave() { + debuggingLogger1.error("doSave: ${this::class.qualifiedName}") + storage_.store(owner_, this) + } +} + +internal val debuggingLogger1 = DefaultLogger("debug") \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index b4e70316d..7f3970128 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -76,7 +76,7 @@ import kotlin.reflect.full.findAnnotation * @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. * @see PluginDataStorage [PluginData] 存储仓库 */ -public interface PluginData { +public interface PluginData : PluginDataExtensions { /** * 添加了追踪的 [ValueNode] 列表 (即使用 `by value()` 委托的属性), 即通过 `by value` 初始化的属性列表. * diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataExtensions.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataExtensions.kt new file mode 100644 index 000000000..5b0120859 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataExtensions.kt @@ -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 SerializerAwareValue>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue> { + return (this as SerializerAwareValue>).withDefault(defaultValueComputer) as SerializerAwareValue> + } + + @JvmName("withDefaultImmutableMap") + public fun , V : Map<*, *>, K> SerializerAwareValue.withEmptyDefault(): SerializerAwareValue { + return this.withDefault { LinkedHashMap() as V } + } + + @JvmName("withDefaultImmutableSet") + public fun , V : Set<*>, K> SerializerAwareValue.withEmptyDefault(): SerializerAwareValue { + return this.withDefault { LinkedHashSet() as V } + } + + @JvmName("withDefaultImmutableList") + public fun , V : List<*>, K> SerializerAwareValue.withEmptyDefault(): SerializerAwareValue { + return this.withDefault { ArrayList() as V } + } + + public fun , V, K> SerializerAwareValue.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue { + val pluginData = this@PluginDataExtensions.castOrInternalError() + + + val origin = (this as SerializableValue).delegate.castOrInternalError>() + + return SerializableValue( + object : CompositeMapValue { + private val instance = object : MutableMap { + 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> get() = origin.value.entries as MutableSet> + override val keys: MutableSet get() = origin.value.keys as MutableSet + override val values: MutableCollection get() = origin.value.values as MutableCollection + override fun clear() = (origin.value as MutableMap).clear() + override fun putAll(from: Map) = (origin.value as MutableMap).putAll(from) + override fun remove(key: K): V? = (origin.value as MutableMap).remove(key) + override fun put(key: K, value: V): V? = (origin.value as MutableMap).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 + get() = instance + set(value) { + origin.value = value + } + } as Value, + this.serializer + ) + return pluginData.createCompositeMapValueImpl( + kToValue = origin.kToValue, + vToValue = origin.vToValue, + applyToShadowedMap = { theMap -> + object : MutableMap 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 + } + +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt index 392214c16..9719df5a9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt @@ -40,7 +40,7 @@ public interface Value { * 可被序列化的 [Value]. */ public class SerializableValue( - private val delegate: Value, + @JvmField internal val delegate: Value, /** * 用于更新和保存 [delegate] 的序列化器 */ @@ -56,7 +56,9 @@ public class SerializableValue( ): SerializableValue { return SerializableValue( this, - serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) }) + serializer.map(serializer = { + this.value + }, deserializer = { this.setValueBySerializer(it) }) ) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index a1540dcfa..362304152 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -15,7 +15,7 @@ import com.vdurmont.semver4j.Semver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job 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.MiraiConsoleFrontEndDescription 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.plugin.CuiPluginCenter 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.PluginManager 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 configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns + override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns override val consoleInput: ConsoleInput by instance::consoleInput override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = @@ -83,15 +84,18 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI mainLogger.info { frontEndDescription.render() } 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 { Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } } + mainLogger.info { "Reloading configurations..." } + ConsoleDataScope.reloadAll() + 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 mainLogger.info { "Loading plugins..." } @@ -99,7 +103,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." } mainLogger.info { "mirai-console started successfully." } - ConsoleBuiltInPluginDataStorage // init // Only for initialize } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index f592523e7..11fa903e2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -130,11 +130,11 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor( removeSubName: Boolean ) { val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0) - if (args == null || !onCommand( - sender, - args - ) - ) { + if (!this.permission.testPermission(sender)) { + sender.sendMessage(usage) // TODO: 2020/8/26 #127 + return + } + if (args == null || !onCommand(sender, args)) { sender.sendMessage(usage) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/CompositeValueImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/CompositeValueImpl.kt index 69fc19338..10fcfea07 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/CompositeValueImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/CompositeValueImpl.kt @@ -110,9 +110,12 @@ internal abstract class CompositeListValueImpl( // workaround to a type inference bug internal fun PluginData.createCompositeMapValueImpl( kToValue: (K) -> Value, - vToValue: (V) -> Value + vToValue: (V) -> Value, + valueToK: (Value) -> K = Value::value, + valueToV: (Value) -> V = Value::value, + applyToShadowedMap: ((MutableMap) -> (MutableMap))? = null ): CompositeMapValueImpl { - return object : CompositeMapValueImpl(kToValue, vToValue) { + return object : CompositeMapValueImpl(kToValue, vToValue, valueToK, valueToV, applyToShadowedMap) { override fun onChanged() = this@createCompositeMapValueImpl.onValueChanged(this) } } @@ -120,13 +123,20 @@ internal fun PluginData.createCompositeMapValueImpl( // TODO: 2020/6/24 在一个 Value 被删除后停止追踪其更新. internal abstract class CompositeMapValueImpl( - kToValue: (K) -> Value, // should override onChanged - vToValue: (V) -> Value // should override onChanged + @JvmField internal val kToValue: (K) -> Value, // should override onChanged + @JvmField internal val vToValue: (V) -> Value, // should override onChanged + @JvmField internal val valueToK: (Value) -> K = Value::value, + @JvmField internal val valueToV: (Value) -> V = Value::value, + applyToShadowedMap: ((MutableMap) -> (MutableMap))? = null ) : CompositeMapValue, AbstractValueImpl>() { - private val internalList: MutableMap, Value> = mutableMapOf() + @JvmField + internal val internalList: MutableMap, Value> = mutableMapOf() private var _value: MutableMap = - 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 get() = _value set(v) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/collectionUtil.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/collectionUtil.kt index 1e81e2303..0218f3a3e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/collectionUtil.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/collectionUtil.kt @@ -18,73 +18,94 @@ import kotlin.reflect.KClass // TODO: 2020/6/24 优化性能: 引入一个 comparator 之类来替代将 Int 包装为 Value 后进行 containsKey 比较的方法 -internal inline fun MutableMap.shadowMap( - crossinline kTransform: (K) -> KR, - crossinline kTransformBack: (KR) -> K, - crossinline vTransform: (V) -> VR, - crossinline vTransformBack: (VR) -> V -): MutableMap { - return object : MutableMap { - override val size: Int get() = this@shadowMap.size - override fun containsKey(key: KR): Boolean = this@shadowMap.containsKey(key.let(kTransformBack)) - override fun containsValue(value: VR): Boolean = this@shadowMap.containsValue(value.let(vTransformBack)) - override fun get(key: KR): VR? = this@shadowMap[key.let(kTransformBack)]?.let(vTransform) - override fun isEmpty(): Boolean = this@shadowMap.isEmpty() - override val entries: MutableSet> - get() = this@shadowMap.entries.shadowMap( - transform = { entry: MutableMap.MutableEntry -> - object : MutableMap.MutableEntry { - override val key: KR get() = entry.key.let(kTransform) - override val value: VR get() = entry.value.let(vTransform) - override fun setValue(newValue: VR): VR = - entry.setValue(newValue.let(vTransformBack)).let(vTransform) +internal open class ShadowMap( + private val originMap: MutableMap, + private val kTransform: (K) -> KR, + private val kTransformBack: (KR) -> K, + private val vTransform: (V) -> VR, + private val vTransformBack: (VR) -> V +) : MutableMap { + override val size: Int get() = originMap.size + override fun containsKey(key: KR): Boolean = originMap.containsKey(key.let(kTransformBack)) + override fun containsValue(value: VR): Boolean = originMap.containsValue(value.let(vTransformBack)) + override fun get(key: KR): VR? = originMap[key.let(kTransformBack)]?.let(vTransform) + override fun isEmpty(): Boolean = originMap.isEmpty() - override fun hashCode(): Int = 17 * 31 + (key?.hashCode() ?: 0) + (value?.hashCode() ?: 0) - override fun toString(): String = "$key=$value" - override fun equals(other: Any?): Boolean { - if (other == null || other !is Map.Entry<*, *>) return false - return other.key == key && other.value == value - } - } - } as ((MutableMap.MutableEntry) -> MutableMap.MutableEntry), // type inference bug - transformBack = { entry -> - object : MutableMap.MutableEntry { - override val key: K get() = entry.key.let(kTransformBack) - override val value: V get() = entry.value.let(vTransformBack) - override fun setValue(newValue: V): V = - entry.setValue(newValue.let(vTransform)).let(vTransformBack) + override val entries: MutableSet> + get() = originMap.entries.shadowMap( + transform = { entry: MutableMap.MutableEntry -> + object : MutableMap.MutableEntry { + override val key: KR get() = entry.key.let(kTransform) + override val value: VR get() = entry.value.let(vTransform) + override fun setValue(newValue: VR): VR = + entry.setValue(newValue.let(vTransformBack)).let(vTransform) - override fun hashCode(): Int = 17 * 31 + (key?.hashCode() ?: 0) + (value?.hashCode() ?: 0) - override fun toString(): String = "$key=$value" - override fun equals(other: Any?): Boolean { - if (other == null || other !is Map.Entry<*, *>) return false - return other.key == key && other.value == value - } + override fun hashCode(): Int = 17 * 31 + (key?.hashCode() ?: 0) + (value?.hashCode() ?: 0) + override fun toString(): String = "$key=$value" + override fun equals(other: Any?): Boolean { + if (other == null || other !is Map.Entry<*, *>) return false + return other.key == key && other.value == value } } - ) - override val keys: MutableSet - get() = this@shadowMap.keys.shadowMap(kTransform, kTransformBack) - override val values: MutableCollection - get() = this@shadowMap.values.shadowMap(vTransform, vTransformBack) + } as ((MutableMap.MutableEntry) -> MutableMap.MutableEntry), // type inference bug + transformBack = { entry -> + object : MutableMap.MutableEntry { + override val key: K get() = entry.key.let(kTransformBack) + override val value: V get() = entry.value.let(vTransformBack) + override fun setValue(newValue: V): V = + entry.setValue(newValue.let(vTransform)).let(vTransformBack) - override fun clear() = this@shadowMap.clear() - override fun put(key: KR, value: VR): VR? = - this@shadowMap.put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) - - override fun putAll(from: Map) { - from.forEach { (kr, vr) -> - this@shadowMap[kr.let(kTransformBack)] = vr.let(vTransformBack) + override fun hashCode(): Int = 17 * 31 + (key?.hashCode() ?: 0) + (value?.hashCode() ?: 0) + override fun toString(): String = "$key=$value" + override fun equals(other: Any?): Boolean { + if (other == null || other !is Map.Entry<*, *>) return false + return other.key == key && other.value == value + } + } } - } + ) + override val keys: MutableSet + get() = originMap.keys.shadowMap(kTransform, kTransformBack) + override val values: MutableCollection + get() = originMap.values.shadowMap(vTransform, vTransformBack) - override fun remove(key: KR): VR? = this@shadowMap.remove(key.let(kTransformBack))?.let(vTransform) - override fun toString(): String = this@shadowMap.toString() - override fun hashCode(): Int = this@shadowMap.hashCode() + override fun clear() = originMap.clear() + override fun put(key: KR, value: VR): VR? = + originMap.put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) + + override fun putAll(from: Map) { + from.forEach { (kr, vr) -> + originMap[kr.let(kTransformBack)] = vr.let(vTransformBack) + } + } + + override fun remove(key: KR): VR? = originMap.remove(key.let(kTransformBack))?.let(vTransform) + override fun toString(): String = originMap.toString() + 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 MutableMap.shadowMap( + kTransform: (K) -> KR, + kTransformBack: (KR) -> K, + vTransform: (V) -> VR, + vTransformBack: (VR) -> V +): MutableMap = ShadowMap(this, kTransform, kTransformBack, vTransform, vTransformBack) + internal inline fun MutableCollection.shadowMap( crossinline transform: (E) -> R, crossinline transformBack: (R) -> E diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt index 3d601626a..72f5bd28d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt @@ -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.valueFromKType import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import kotlin.contracts.contract import kotlin.reflect.KClass import kotlin.reflect.KType @@ -148,4 +149,17 @@ internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean { @PublishedApi @Suppress("UNCHECKED_CAST") -internal inline fun Any.cast(): R = this as R +internal inline fun Any.cast(): R { + contract { + returns() implies (this@cast is R) + } + return this as R +} + +@Suppress("UNCHECKED_CAST") +internal inline fun 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}") +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt index 2e76603bb..6c4515c2b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt @@ -42,22 +42,47 @@ internal object BotManagerImpl : BotManager { } } -internal object ManagersConfig : AutoSavePluginData() { - private val managers: MutableMap> by value() - - internal operator fun get(bot: Bot): MutableSet = managers.getOrPut(bot.id, ::mutableSetOf) +internal object ManagersConfig : AutoSavePluginConfig() { + private val managers by value>>().withEmptyDefault() + internal operator fun get(bot: Bot): MutableSet = managers[bot.id]!! } internal fun CoroutineContext.overrideWithSupervisorJob(): CoroutineContext = this + SupervisorJob(this[Job]) 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 = arrayOf() + private val configs: Array = arrayOf(ManagersConfig) + + fun reloadAll() { + data.forEach { dt -> + ConsoleBuiltInPluginDataStorage.load(ConsoleBuiltInPluginDataHolder, dt) + } + configs.forEach { config -> + ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config) + } + } +} internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder, - CoroutineScope by MiraiConsole.childScope() { - override val autoSaveIntervalMillis: LongRange = 30.minutesToMillis..60.minutesToMillis + CoroutineScope by ConsoleDataScope.childScope() { + 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" } internal object ConsoleBuiltInPluginDataStorage : - PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns \ No newline at end of file + PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns + +internal object ConsoleBuiltInPluginConfigStorage : + PluginDataStorage by MiraiConsoleImplementationBridge.configStorageForBuiltIns \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index 56499652e..77d8c016e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.plugin.jvm 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 kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -28,5 +28,5 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor( ) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) { 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 } \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index ad9cdb1c5..51cc0eadd 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -48,9 +48,11 @@ fun initTestEnvironment() { override val consoleCommandSender: ConsoleCommandSender = object : ConsoleCommandSender() { override suspend fun sendMessage(message: Message) = println(message) } - override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage() - override val configStorageForJarPluginLoader: PluginDataStorage get() = TODO("Not yet implemented") - override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage() + override val dataStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage() + override val configStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage() + override val dataStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage() + override val configStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage() + override val consoleInput: ConsoleInput = object : ConsoleInput { override suspend fun requestInput(hint: String): String { println(hint) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt index 38362b70c..bdfaaf869 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt @@ -10,7 +10,6 @@ package net.mamoe.mirai.console.data 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.util.ConsoleInternalAPI import org.junit.jupiter.api.Test @@ -22,7 +21,7 @@ internal class PluginDataTest { object MyPlugin : KotlinPlugin() - class MyPluginData : PluginData by MyPlugin.createPluginData() { + class MyPluginData : AutoSavePluginData() { var int by value(1) val map: MutableMap by value() val map2: MutableMap> by value() diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt index 4a2186cde..fc0b69e7a 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt @@ -58,7 +58,6 @@ import java.util.* /** * mirai-console-pure 后端实现 * - * @see MiraiConsoleFrontEndPure 前端实现 * @see MiraiConsolePureLoader CLI 入口点 */ internal class MiraiConsoleImplementationPure @@ -71,7 +70,8 @@ internal class MiraiConsoleImplementationPure override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl, override val dataStorageForJarPluginLoader: 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()) { override val mainLogger: MiraiLogger by lazy { MiraiConsole.newLogger("main")