mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-27 08:50:15 +08:00
Create module mirai-console-frontend-base
This commit is contained in:
parent
af2b38d476
commit
5ac74a336f
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user