diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index b907012ac..91ec3a314 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -153,6 +153,9 @@ public interface CommandManager { override suspend fun CommandSender.executeCommand(message: MessageChain): Command? = CommandManagerImpl.run { executeCommand(message) } + override val commandPrefix: String + get() = CommandManagerImpl.commandPrefix + override suspend fun Command.execute( sender: CommandSender, args: MessageChain, diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt index c88c8039c..269b03312 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt @@ -15,7 +15,7 @@ import java.io.OutputStream private const val LN = 10.toByte() internal class BufferedOutputStream @JvmOverloads constructor( - private val size: Int = 1024 * 1024 * 1024, + private val size: Int = 1024 * 1024, private val logger: (String?) -> Unit ) : ByteArrayOutputStream(size + 1) { override fun write(b: Int) { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt new file mode 100644 index 000000000..5447636f8 --- /dev/null +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.pure + +import kotlinx.coroutines.* +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.command.BuiltInCommands +import net.mamoe.mirai.console.command.Command.Companion.primaryName +import net.mamoe.mirai.console.command.CommandExecuteStatus +import net.mamoe.mirai.console.command.CommandManager +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed +import net.mamoe.mirai.console.util.ConsoleInternalAPI +import net.mamoe.mirai.utils.DefaultLogger +import org.fusesource.jansi.Ansi +import java.util.* +import java.util.concurrent.Executors +import kotlin.concurrent.thread + +@OptIn(ConsoleInternalAPI::class) +internal fun startupConsoleThread() { + val service = Executors.newSingleThreadExecutor { code -> + thread(start = false, isDaemon = false, name = "Console Input", block = code::run) + } + val dispatch = service.asCoroutineDispatcher() + ConsoleUtils.miraiLineReader = { hint -> + withContext(dispatch) { + ConsoleUtils.lineReader.readLine( + if (hint.isNotEmpty()) { + ConsoleUtils.lineReader.printAbove( + Ansi.ansi() + .fgCyan().a(MiraiConsoleFrontEndPure.sdf.format(Date())).a(" ") + .fgMagenta().a(hint) + .reset() + .toString() + ) + "$hint > " + } else "> " + ) + } + } + + CoroutineScope(dispatch).launch { + val consoleLogger = DefaultLogger("Console") + while (isActive) { + try { + val next = MiraiConsoleFrontEndPure.requestInput("").let { + when { + it.startsWith(CommandManager.commandPrefix) -> { + it + } + it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName + else -> CommandManager.commandPrefix + it + } + } + if (next.isBlank()) { + continue + } + consoleLogger.debug("INPUT> $next") + val result = ConsoleCommandSenderImpl.executeCommandDetailed(next) + when (result.status) { + CommandExecuteStatus.SUCCESSFUL -> { + } + CommandExecuteStatus.EXECUTION_EXCEPTION -> { + result.exception?.printStackTrace() + } + CommandExecuteStatus.COMMAND_NOT_FOUND -> { + consoleLogger.warning("Unknown command: ${result.commandName}") + } + CommandExecuteStatus.PERMISSION_DENIED -> { + consoleLogger.warning("Permission denied.") + } + } + } catch (e: InterruptedException) { + return@launch + } catch (e: Throwable) { + consoleLogger.error("Unhandled exception", e) + } + } + }.let { consoleJob -> + MiraiConsole.job.invokeOnCompletion { + runCatching { + consoleJob.cancel() + }.exceptionOrNull()?.printStackTrace() + runCatching { + service.shutdownNow() + }.exceptionOrNull()?.printStackTrace() + runCatching { + ConsoleUtils.terminal.close() + }.exceptionOrNull()?.printStackTrace() + } + } +} diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt index 596db154e..91c44ecc5 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt @@ -19,6 +19,7 @@ internal object ConsoleUtils { val lineReader: LineReader val terminal: Terminal + lateinit var miraiLineReader: suspend (String) -> String init { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt index 6143fa96a..f6cf70256 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt @@ -22,6 +22,7 @@ package net.mamoe.mirai.console.pure //import net.mamoe.mirai.console.command.CommandManager //import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd +import io.ktor.utils.io.concurrent.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot @@ -71,9 +72,15 @@ object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { const val COLOR_RESET = "\u001b[39;49m" // } - private val sdf by lazy { + internal val sdf by ThreadLocal.withInitial { + // SimpleDateFormat not thread safe. SimpleDateFormat("HH:mm:ss") } + + private operator fun ThreadLocal.getValue(thiz: Any, property: Any): T { + return this.get() + } + override val name: String get() = "Pure" override val version: String @@ -90,28 +97,19 @@ object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { } override suspend fun requestInput(hint: String): String { - if (hint.isNotEmpty()) { - ConsoleUtils.lineReader.printAbove( - Ansi.ansi() - .fgCyan().a(sdf.format(Date())) - .fgMagenta().a(hint) - .toString() - ) - } - return withContext(Dispatchers.IO) { - ConsoleUtils.lineReader.readLine("> ") - } + return ConsoleUtils.miraiLineReader(hint) } override fun createLoginSolver(): LoginSolver { return DefaultLoginSolver( input = suspend { - requestInput("") + requestInput("LOGIN> ") } ) } } + /* class MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { private var requesting = false diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index 318a8b10b..1bfb4421e 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -51,7 +51,7 @@ internal fun startup() { DefaultLogger = { MiraiConsoleFrontEndPure.loggerFor(it) } overrideSTD() MiraiConsoleImplementationPure().start() - startConsoleThread() + startupConsoleThread() } internal fun overrideSTD() { @@ -71,56 +71,6 @@ internal fun overrideSTD() { ) } -internal fun startConsoleThread() { - thread(name = "Console Input") { - val consoleLogger = DefaultLogger("Console") - try { - kotlinx.coroutines.runBlocking { - while (isActive) { - val next = MiraiConsoleFrontEndPure.requestInput("").let { - when { - it.startsWith(CommandManager.commandPrefix) -> { - it - } - it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName - else -> CommandManager.commandPrefix + it - } - } - if (next.isBlank()) { - continue - } - consoleLogger.debug("INPUT> $next") - val result = ConsoleCommandSenderImpl.executeCommandDetailed(next) - when (result.status) { - CommandExecuteStatus.SUCCESSFUL -> { - } - CommandExecuteStatus.EXECUTION_EXCEPTION -> { - result.exception?.printStackTrace() - } - CommandExecuteStatus.COMMAND_NOT_FOUND -> { - consoleLogger.warning("Unknown command: ${result.commandName}") - } - CommandExecuteStatus.PERMISSION_DENIED -> { - consoleLogger.warning("Permission denied.") - } - - } - } - } - } catch (e: InterruptedException) { - return@thread - } - }.let { thread -> - MiraiConsole.job.invokeOnCompletion { - runCatching { - thread.interrupt() - }.exceptionOrNull()?.printStackTrace() - runCatching { - ConsoleUtils.terminal.close() - }.exceptionOrNull()?.printStackTrace() - } - } -} internal object ConsoleCommandSenderImpl : ConsoleCommandSender() { override suspend fun sendMessage(message: Message) {