Create module mirai-console-frontend-base

This commit is contained in:
Karlatemp 2022-08-31 22:40:27 +08:00
parent af2b38d476
commit 5ac74a336f
No known key found for this signature in database
GPG Key ID: BA173CA2B9956C59
12 changed files with 799 additions and 2 deletions

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
import BinaryCompatibilityConfigurator.configureBinaryValidator
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
`maven-publish`
}
kotlin {
explicitApiWarning()
}
dependencies {
compileAndTestRuntime(project(":mirai-core-utils"))
compileAndTestRuntime(project(":mirai-console"))
compileAndTestRuntime(project(":mirai-core-api"))
compileAndTestRuntime(project(":mirai-core-utils"))
compileAndTestRuntime(`kotlin-stdlib-jdk8`)
}
version = Versions.consoleTerminal
description = "Console frontend abstract"
configurePublishing("mirai-console-frontend-base")
configureBinaryValidator(null)

View File

@ -0,0 +1,83 @@
public abstract class net/mamoe/mirai/console/frontendbase/AbstractMiraiConsoleFrontendImplementation : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/MiraiConsoleImplementation {
public fun <init> (Ljava/lang/String;)V
public fun createLogger (Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
public fun createLoggerFactory (Lnet/mamoe/mirai/console/MiraiConsoleImplementation$FrontendLoggingInitContext;)Lnet/mamoe/mirai/utils/MiraiLogger$Factory;
public fun getBuiltInPluginLoaders ()Ljava/util/List;
public fun getCommandManager ()Lnet/mamoe/mirai/console/command/CommandManager;
public fun getConfigStorageForBuiltIns ()Lnet/mamoe/mirai/console/data/PluginDataStorage;
public fun getConfigStorageForJvmPluginLoader ()Lnet/mamoe/mirai/console/data/PluginDataStorage;
public fun getConsoleDataScope ()Lnet/mamoe/mirai/console/MiraiConsoleImplementation$ConsoleDataScope;
public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
public fun getDataStorageForBuiltIns ()Lnet/mamoe/mirai/console/data/PluginDataStorage;
public fun getDataStorageForJvmPluginLoader ()Lnet/mamoe/mirai/console/data/PluginDataStorage;
protected abstract fun getFrontendBase ()Lnet/mamoe/mirai/console/frontendbase/FrontendBase;
public fun getJvmPluginLoader ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginLoader;
}
public abstract class net/mamoe/mirai/console/frontendbase/FrontendBase {
public fun <init> ()V
public fun getDaemonThreadGroup ()Ljava/lang/ThreadGroup;
public fun getLogDropAnsi ()Z
public fun getLoggingDirectory ()Ljava/nio/file/Path;
public fun getLoggingRecorder ()Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;
public abstract fun getScope ()Lkotlinx/coroutines/CoroutineScope;
public fun getThreadGroup ()Ljava/lang/ThreadGroup;
public abstract fun getWorkingDirectory ()Ljava/nio/file/Path;
protected fun initLogRecorder ()Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;
protected fun initScreen_forwardStdToMiraiLogger ()V
protected fun initScreen_forwardStdToScreen ()V
public fun newDaemon (Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread;
public fun newThread (Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread;
public fun newThreadFactory (Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Ljava/util/concurrent/ThreadFactory;
public static synthetic fun newThreadFactory$default (Lnet/mamoe/mirai/console/frontendbase/FrontendBase;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/concurrent/ThreadFactory;
public abstract fun printToScreenDirectly (Ljava/lang/String;)V
public fun recordToLogging (Ljava/lang/String;)V
}
public final class net/mamoe/mirai/console/frontendbase/logging/AllDroppedLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder {
public static final field INSTANCE Lnet/mamoe/mirai/console/frontendbase/logging/AllDroppedLogRecorder;
public fun record (Ljava/lang/String;)V
}
public abstract class net/mamoe/mirai/console/frontendbase/logging/AsyncLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder {
public fun <init> (Lnet/mamoe/mirai/console/frontendbase/FrontendBase;I)V
public synthetic fun <init> (Lnet/mamoe/mirai/console/frontendbase/FrontendBase;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
protected abstract fun asyncRecord (Ljava/lang/String;)V
protected final fun getChannel ()Lkotlinx/coroutines/channels/Channel;
protected final fun getDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;
protected final fun getSubscope ()Lkotlinx/coroutines/CoroutineScope;
protected final fun getThreadPool ()Ljava/util/concurrent/ScheduledExecutorService;
public fun record (Ljava/lang/String;)V
}
public class net/mamoe/mirai/console/frontendbase/logging/AsyncLogRecorderForwarded : net/mamoe/mirai/console/frontendbase/logging/AsyncLogRecorder {
public fun <init> (Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;I)V
public synthetic fun <init> (Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
protected fun asyncRecord (Ljava/lang/String;)V
protected final fun getDelegate ()Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;
}
public class net/mamoe/mirai/console/frontendbase/logging/DailySplitLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder {
protected field lastDate I
protected field writer Ljava/io/Writer;
public fun <init> (Ljava/nio/file/Path;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;Ljava/time/format/DateTimeFormatter;)V
public synthetic fun <init> (Ljava/nio/file/Path;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;Ljava/time/format/DateTimeFormatter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
protected final fun acquireFileWriter ()V
protected final fun getBase ()Lnet/mamoe/mirai/console/frontendbase/FrontendBase;
protected final fun getDateFormatter ()Ljava/time/format/DateTimeFormatter;
protected final fun getDirectory ()Ljava/nio/file/Path;
public fun record (Ljava/lang/String;)V
}
public abstract class net/mamoe/mirai/console/frontendbase/logging/LogRecorder {
public fun <init> ()V
public abstract fun record (Ljava/lang/String;)V
}
public class net/mamoe/mirai/console/frontendbase/logging/WriterLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder {
public fun <init> (Ljava/io/Writer;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;)V
protected final fun getBase ()Lnet/mamoe/mirai/console/frontendbase/FrontendBase;
protected final fun getWriter ()Ljava/io/Writer;
public fun record (Ljava/lang/String;)V
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.frontendbase
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlatformLogger
import kotlin.coroutines.CoroutineContext
/**
* [MiraiConsoleImplementation] 的基本抽象实现
*
* @param frontendCoroutineName 该前端的名字, `"MiraiConsoleImplementationTerminal"`
* @see FrontendBase
*/
public abstract class AbstractMiraiConsoleFrontendImplementation(
frontendCoroutineName: String,
) : MiraiConsoleImplementation, CoroutineScope {
// region 此 region 的 字段 / 方法 为 console 默认/内部 实现, 如无必要不建议修改
private val delegateCoroutineScope by lazy {
CoroutineScope(
SupervisorJob() +
CoroutineName(frontendCoroutineName) +
CoroutineExceptionHandler { coroutineContext, throwable ->
if (throwable is CancellationException) {
return@CoroutineExceptionHandler
}
val coroutineName = coroutineContext[CoroutineName]?.name ?: "<unnamed>"
MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable)
}
)
}
override val coroutineContext: CoroutineContext get() = delegateCoroutineScope.coroutineContext
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader })
override val jvmPluginLoader: JvmPluginLoader by lazy { backendAccess.createDefaultJvmPluginLoader(coroutineContext) }
override val commandManager: CommandManager by lazy { backendAccess.createDefaultCommandManager(coroutineContext) }
override val consoleDataScope: MiraiConsoleImplementation.ConsoleDataScope by lazy {
MiraiConsoleImplementation.ConsoleDataScope.createDefault(
coroutineContext, dataStorageForBuiltIns, configStorageForBuiltIns
)
}
override val dataStorageForJvmPluginLoader: PluginDataStorage by lazy {
MultiFilePluginDataStorage(rootPath.resolve("data"))
}
override val dataStorageForBuiltIns: PluginDataStorage by lazy {
MultiFilePluginDataStorage(rootPath.resolve("data"))
}
override val configStorageForJvmPluginLoader: PluginDataStorage by lazy {
MultiFilePluginDataStorage(rootPath.resolve("config"))
}
override val configStorageForBuiltIns: PluginDataStorage by lazy {
MultiFilePluginDataStorage(rootPath.resolve("config"))
}
// endregion
// region
protected abstract val frontendBase: FrontendBase
// endregion
// region Logging
@Deprecated(
"Deprecated for removal. Implement the other overload, or use MiraiConsole.createLogger instead.",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith(
"MiraiLogger.Factory.create(javaClass, identity)",
"net.mamoe.mirai.utils.MiraiLogger"
)
)
override fun createLogger(identity: String?): MiraiLogger {
return MiraiLogger.Factory.create(javaClass, identity)
}
override fun createLoggerFactory(context: MiraiConsoleImplementation.FrontendLoggingInitContext): MiraiLogger.Factory {
@Suppress("INVISIBLE_MEMBER")
frontendBase.initScreen_forwardStdToScreen()
// region Default Fallback Implementation
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge.defaultLoggerFactory = {
class DefaultMiraiConsoleFactory : MiraiLogger.Factory {
// Don't directly use ::println
// ::println will query System.out every time.
private val stdout: ((String) -> Unit) = System.out::println
override fun create(requester: Class<*>, identity: String?): MiraiLogger {
return PlatformLogger(identity ?: requester.kotlin.simpleName ?: requester.simpleName, stdout)
}
}
DefaultMiraiConsoleFactory()
}
// endregion
val factoryImpl = context.acquirePlatformImplementation()
context.invokeAfterInitialization {
@Suppress("INVISIBLE_MEMBER")
frontendBase.initScreen_forwardStdToMiraiLogger()
}
return factoryImpl
}
// endregion
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.frontendbase
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.frontendbase.logging.AsyncLogRecorderForwarded
import net.mamoe.mirai.console.frontendbase.logging.DailySplitLogRecorder
import net.mamoe.mirai.console.frontendbase.logging.LogRecorder
import net.mamoe.mirai.utils.MiraiLogger
import java.io.PrintStream
import java.nio.file.Path
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicInteger
/**
* 前端的基本实现
*
* @see AbstractMiraiConsoleFrontendImplementation
*/
public abstract class FrontendBase {
/**
* 所属前端的 [CoroutineScope]
*
* Implementation note: 直接返回前端实例
*/
public abstract val scope: CoroutineScope
public open val threadGroup: ThreadGroup by lazy {
ThreadGroup("Mirai Console FrontEnd Threads")
}
public open val daemonThreadGroup: ThreadGroup by lazy {
ThreadGroup(threadGroup, "Mirai Console FrontEnd Daemon Threads")
}
public open val loggingRecorder: LogRecorder by lazy { initLogRecorder() }
/**
* Console 的运行目录
*
* Implementation note: 返回 [MiraiConsoleImplementation.rootPath]
*/
public abstract val workingDirectory: Path
/**
* 日志存放目录
*/
public open val loggingDirectory: Path by lazy { workingDirectory.resolve("logs") }
/**
* 存储的日志是否需要去除 ansi 标志
*/
public open val logDropAnsi: Boolean get() = true
/**
* 创建一个新的非守护线程, 此线程不会预先启动
*/
public open fun newThread(name: String, task: Runnable): Thread {
return Thread(threadGroup, task, name)
}
/**
* 创建一个新的守护线程, 此线程不会预先启动
*/
public open fun newDaemon(name: String, task: Runnable): Thread {
return Thread(daemonThreadGroup, task, name).apply {
isDaemon = true
}
}
/**
* 创建一个新的 [ThreadFactory], 创建的线程的名字为 `$name#{counter}`
*/
public open fun newThreadFactory(name: String, isDemon: Boolean, postSetup: (Thread) -> Unit = {}): ThreadFactory {
return object : ThreadFactory {
private val group = ThreadGroup(if (isDemon) daemonThreadGroup else threadGroup, name)
private val counter = AtomicInteger()
override fun newThread(r: Runnable): Thread {
return Thread(group, r, "$name#${counter.getAndIncrement()}").also {
it.isDaemon = isDemon
}.also(postSetup)
}
}
}
/**
* 将一条信息直接打印到前端的屏幕上
*
* Implementation note: 打印时不需要添加任何修饰, [msg] 已经是格式化好的信息
*/
public abstract fun printToScreenDirectly(msg: String)
@Suppress("FunctionName")
protected open fun initScreen_forwardStdToScreen() {
val forwarder = RepipedMessageForward { msg ->
printToScreenDirectly(msg)
recordToLogging(msg)
}
val printer = PrintStream(forwarder.pipedOutputStream, true, "UTF-8")
System.setOut(printer)
// stderr is reserved for printing fatal errors when something crashed in logger factory initialization
}
@Suppress("FunctionName")
protected open fun initScreen_forwardStdToMiraiLogger() {
val logStdout = MiraiLogger.Factory.create(javaClass, "stdout")
val logStderr = MiraiLogger.Factory.create(javaClass, "stderr")
val forwarderStdout = RepipedMessageForward(logStdout::info)
val forwarderStderr = RepipedMessageForward(logStderr::warning)
System.setOut(PrintStream(forwarderStdout.pipedOutputStream, true, "UTF-8"))
System.setErr(PrintStream(forwarderStderr.pipedOutputStream, true, "UTF-8"))
}
/**
* 将一条消息记录至日志 (不会显示至屏幕)
*/
public open fun recordToLogging(msg: String) {
loggingRecorder.record(msg)
}
protected open fun initLogRecorder(): LogRecorder {
return AsyncLogRecorderForwarded(
DailySplitLogRecorder(
loggingDirectory,
this
),
this
)
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.frontendbase
import java.io.ByteArrayOutputStream
import java.io.OutputStream
internal class RepipedMessageForward(
private val output: (String) -> Unit,
) : ByteArrayOutputStream(1024 * 1024) {
internal val pipedOutputStream: OutputStream get() = this
private var lastCheckIndex = 0
@Synchronized
override fun write(b: ByteArray?, off: Int, len: Int) {
super.write(b, off, len)
flush()
}
@Synchronized
override fun write(b: Int) {
super.write(b)
flush()
}
@Synchronized
override fun write(b: ByteArray?) {
super.write(b)
flush()
}
@Synchronized
override fun flush() {
topLoop@
while (true) {
var index = lastCheckIndex
val end = this.count
var lastIsLr = false // last char is '\r'
while (index < end) {
val c = buf[index].toInt() and 0xFF
when (c shr 4) {
in 0..7 -> {
/* 0xxxxxxx*/
if (c == '\r'.code) {
lastIsLr = true
} else if (c == 10) {
// NEW LINE: \n
val strend = if (lastIsLr) {
index - 1
} else {
index
}
val strx = String(buf, 0, strend, Charsets.UTF_8)
index++
System.arraycopy(
buf, index, buf, 0, end - index
)
// A \n
// index = 1
// string with ln = 2
count -= index
lastCheckIndex = 0
output(strx)
continue@topLoop // same as return flush()
} else {
lastIsLr = false
}
index++
}
12, 13 -> {
/* 110x xxxx 10xx xxxx*/
index += 2
lastIsLr = false
}
14 -> {
/* 1110 xxxx 10xx xxxx 10xx xxxx */
index += 3
lastIsLr = false
}
else -> {
/* 10xx xxxx, 1111 xxxx */
index++// Ignored
lastIsLr = false
}
}
}
lastCheckIndex = index
break
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.frontendbase.logging
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.onFailure
import net.mamoe.mirai.console.frontendbase.FrontendBase
import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi
import net.mamoe.mirai.utils.childScope
import java.io.Writer
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
public abstract class LogRecorder {
public abstract fun record(msg: String)
}
public object AllDroppedLogRecorder : LogRecorder() {
override fun record(msg: String) {
}
}
public abstract class AsyncLogRecorder(
private val base: FrontendBase,
pipelineSize: Int = 2048,
) : LogRecorder() {
protected val channel: Channel<String> = Channel(pipelineSize)
protected val threadPool: ScheduledExecutorService = Executors.newScheduledThreadPool(
3,
base.newThreadFactory("Mirai Console Logging", true)
)
protected val dispatcher: CoroutineDispatcher = threadPool.asCoroutineDispatcher()
protected val subscope: CoroutineScope = base.scope.childScope(name = "Mirai Console Async Logging", dispatcher)
init {
base.scope.coroutineContext.job.invokeOnCompletion {
channel.close()
threadPool.shutdown()
}
subscope.launch {
while (isActive) {
val nextLine = channel.receive()
asyncRecord(nextLine)
}
}
}
override fun record(msg: String) {
if (!subscope.isActive) return // Died
channel.trySend(msg).onFailure {
base.scope.launch(start = CoroutineStart.UNDISPATCHED) {
channel.send(msg)
}
}
}
protected abstract fun asyncRecord(msg: String)
}
public open class AsyncLogRecorderForwarded(
protected val delegate: LogRecorder,
base: FrontendBase,
pipelineSize: Int = 2048,
) : AsyncLogRecorder(base, pipelineSize) {
override fun asyncRecord(msg: String) {
delegate.record(msg)
}
}
public open class WriterLogRecorder(
protected val writer: Writer,
protected val base: FrontendBase,
) : LogRecorder() {
override fun record(msg: String) {
try {
writer.append(
if (base.logDropAnsi) {
msg.dropAnsi()
} else msg
).append('\n').flush()
} catch (e: Throwable) {
base.printToScreenDirectly(e.stackTraceToString())
}
}
}
public open class DailySplitLogRecorder(
protected val directory: Path,
protected val base: FrontendBase,
protected val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern(
"YYYY-MM-dd'.log'"
),
) : LogRecorder() {
@JvmField
protected var writer: Writer? = null
@JvmField
protected var lastDate: Int = -1
protected fun acquireFileWriter() {
val instantNow = Instant.now()
.atZone(ZoneId.systemDefault())
val dayNow = instantNow.dayOfYear
if (dayNow != lastDate) {
lastDate = dayNow
writer?.close()
val logPath = directory.resolve(dateFormatter.format(instantNow))
logPath.parent?.let { pt ->
if (!Files.isDirectory(pt)) {
Files.createDirectories(pt)
}
}
writer = Files.newBufferedWriter(
logPath, Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND
)
}
}
override fun record(msg: String) {
try {
acquireFileWriter()
(writer ?: error("Writer not setup")).append(
if (base.logDropAnsi) {
msg.dropAnsi()
} else msg
).append('\n').flush()
} catch (e: Throwable) {
base.printToScreenDirectly(e.stackTraceToString())
}
}
}

View File

@ -0,0 +1,10 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.frontendbase

View File

@ -0,0 +1,112 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.frontendbase
import java.io.OutputStreamWriter
import java.io.PrintStream
import kotlin.test.Test
import kotlin.test.assertEquals
internal class RepipedMessageForwardTest {
private val pendingMsg = mutableListOf<String>()
private val ouptut = RepipedMessageForward(pendingMsg::add).pipedOutputStream
@Test
fun testPrintStream() {
val ps = PrintStream(ouptut)
ps.println("ABC")
ps.append("D").append("E").append("F").println("G")
ps.println("LLOO")
assertEquals(3, pendingMsg.size)
assertEquals("ABC", pendingMsg.removeAt(0))
assertEquals("DEFG", pendingMsg.removeAt(0))
assertEquals("LLOO", pendingMsg.removeAt(0))
}
@Test
fun testCRLF() {
OutputStreamWriter(ouptut).use { writer ->
writer.append("LINE").append("AAA").append("OOOO").append("\r\n")
writer.append("Line1125744\r\n")
writer.append("AFFXZ\r\n")
}
assertEquals(3, pendingMsg.size)
assertEquals("LINEAAAOOOO", pendingMsg.removeAt(0))
assertEquals("Line1125744", pendingMsg.removeAt(0))
assertEquals("AFFXZ", pendingMsg.removeAt(0))
}
@Test
fun testLF() {
OutputStreamWriter(ouptut).use { writer ->
writer.append("LINE").append("\n")
writer.append("Line5\n")
writer.append("AFFXZ\n")
writer.append("NO\rCR REMOVED\n")
}
assertEquals(4, pendingMsg.size)
assertEquals("LINE", pendingMsg.removeAt(0))
assertEquals("Line5", pendingMsg.removeAt(0))
assertEquals("AFFXZ", pendingMsg.removeAt(0))
assertEquals("NO\rCR REMOVED", pendingMsg.removeAt(0))
}
@Test
fun testCRLFMixing() {
OutputStreamWriter(ouptut).use { writer ->
writer.append("LF\n")
writer.append("CRLF\r\n")
writer.append("LFLF\n\n")
}
assertEquals(4, pendingMsg.size)
assertEquals("LF", pendingMsg.removeAt(0))
assertEquals("CRLF", pendingMsg.removeAt(0))
assertEquals("LFLF", pendingMsg.removeAt(0))
assertEquals("", pendingMsg.removeAt(0))
}
@Test
fun testEmptyLines_LF() {
OutputStreamWriter(ouptut).use { writer ->
repeat(7) {
writer.append("\n")
}
}
assertEquals(7, pendingMsg.size)
repeat(7) {
assertEquals("", pendingMsg.removeAt(0))
}
}
@Test
fun testEmptyLines_CRLF() {
OutputStreamWriter(ouptut).use { writer ->
repeat(7) {
writer.append("\r\n")
}
}
assertEquals(7, pendingMsg.size)
repeat(7) {
assertEquals("", pendingMsg.removeAt(0))
}
}
}

View File

@ -0,0 +1,10 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.frontendbase

View File

@ -22,6 +22,7 @@ dependencies {
compileAndTestRuntime(project(":mirai-console"))
compileAndTestRuntime(project(":mirai-core-api"))
compileAndTestRuntime(project(":mirai-core-utils"))
compileAndTestRuntime(project(":mirai-console-frontend-base"))
compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib)) // must specify `compileOnly` explicitly
testApi(project(":mirai-core"))

View File

@ -266,11 +266,18 @@ public actual interface MiraiLogger {
*/
internal object MiraiLoggerFactoryImplementationBridge : MiraiLogger.Factory {
@Suppress("ObjectPropertyName")
private var _instance by lateinitMutableProperty { createPlatformInstance() }
private var _instance by lateinitMutableProperty {
createPlatformInstance()
}
internal val instance get() = _instance
fun createPlatformInstance() = loadService(MiraiLogger.Factory::class) { DefaultFactory() }
// It is required for MiraiConsole because default implementation
// queries stdout on every message printing
// It creates an infinite loop (StackOverflowError)
internal var defaultLoggerFactory: (() -> MiraiLogger.Factory) = ::DefaultFactory
fun createPlatformInstance() = loadService(MiraiLogger.Factory::class, defaultLoggerFactory)
private val frozen = atomic(false)
@ -280,6 +287,7 @@ internal object MiraiLoggerFactoryImplementationBridge : MiraiLogger.Factory {
@TestOnly
fun reinit() {
defaultLoggerFactory = ::DefaultFactory
frozen.loop { value ->
_instance = createPlatformInstance()
if (frozen.compareAndSet(value, false)) return

View File

@ -77,6 +77,7 @@ includeConsoleProject(":mirai-console-compiler-annotations", "tools/compiler-ann
if (getLocalProperty("projects.mirai-console.enabled", true)) {
includeConsoleProject(":mirai-console", "backend/mirai-console")
includeConsoleProject(":mirai-console.codegen", "backend/codegen")
includeConsoleProject(":mirai-console-frontend-base", "frontend/mirai-console-frontend-base")
includeConsoleProject(":mirai-console-terminal", "frontend/mirai-console-terminal")
includeConsoleIntegrationTestProjects()