mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-24 14:30:09 +08:00
[console] Console artifacts downloading progress (#2140)
* Console artifacts downloading progress * Add missing code * Update kdoc & naming * make `terminalDisplay` internal * improve performance * Update DownloadingProgress.kt [skip ci] * Rename DownloadingProgress to ProcessProgress * make ProcessProgress stable
This commit is contained in:
parent
a843729f53
commit
85d81efc4a
@ -1719,6 +1719,17 @@ public final class net/mamoe/mirai/console/extensions/SingletonExtensionSelector
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/console/fontend/ProcessProgress : java/io/Closeable {
|
||||
public abstract fun close ()V
|
||||
public abstract fun markFailed ()V
|
||||
public abstract fun rerender ()V
|
||||
public abstract fun setTotalSize (J)V
|
||||
public abstract fun update (J)V
|
||||
public abstract fun update (JJ)V
|
||||
public fun updateText (Ljava/lang/CharSequence;)V
|
||||
public abstract fun updateText (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/console/logging/AbstractLoggerController$LogPriority$Companion {
|
||||
public final fun by (Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;)Lnet/mamoe/mirai/console/logging/AbstractLoggerController$LogPriority;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import net.mamoe.mirai.BotFactory
|
||||
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
|
||||
import net.mamoe.mirai.console.fontend.ProcessProgress
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
@ -254,6 +255,25 @@ public interface MiraiConsole : CoroutineScope {
|
||||
MiraiConsoleImplementation.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的处理进度, 此进度将会在前端显示, 并且此进度需要[手动关闭][ProcessProgress.close]
|
||||
*
|
||||
* 注: 此 API 应该只在以下情况使用
|
||||
*
|
||||
* - 插件初始化 (包括 onLoad, onEnable)
|
||||
* - 命令执行中 (控制台)
|
||||
*
|
||||
* 在其他情况使用可能会导致意外的情况
|
||||
*
|
||||
* // implementation note:
|
||||
* 在 Terminal 前端中, 有处理进度存在时会停止命令输入 (即停止命令执行)
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@JvmStatic
|
||||
public fun newProcessProgress(): ProcessProgress {
|
||||
return MiraiConsoleImplementation.getInstance().createNewProcessProgress()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,13 +22,14 @@ import net.mamoe.mirai.console.data.PluginConfig
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.extension.ComponentStorage
|
||||
import net.mamoe.mirai.console.fontend.DefaultLoggingProcessProgress
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScopeImpl
|
||||
import net.mamoe.mirai.console.fontend.ProcessProgress
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl
|
||||
import net.mamoe.mirai.console.internal.pluginManagerImpl
|
||||
import net.mamoe.mirai.console.internal.shutdown.ShutdownDaemon
|
||||
import net.mamoe.mirai.console.logging.LoggerController
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
@ -220,6 +221,11 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
*/
|
||||
public fun createLogger(identity: String?): MiraiLogger
|
||||
|
||||
/** @see [MiraiConsole.newProcessProgress] */
|
||||
public fun createNewProcessProgress(): ProcessProgress {
|
||||
return DefaultLoggingProcessProgress()
|
||||
}
|
||||
|
||||
/**
|
||||
* 该前端是否支持使用 Ansi 输出彩色信息
|
||||
*
|
||||
|
@ -47,10 +47,7 @@ import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.plugin.version
|
||||
import net.mamoe.mirai.console.util.AnsiMessageBuilder
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.sendAnsiMessage
|
||||
import net.mamoe.mirai.console.util.*
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import java.lang.management.ManagementFactory
|
||||
@ -58,7 +55,6 @@ import java.lang.management.MemoryMXBean
|
||||
import java.lang.management.MemoryUsage
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.floor
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
@ -687,35 +683,6 @@ public object BuiltInCommands {
|
||||
}
|
||||
}
|
||||
|
||||
private const val MEM_B = 1024L
|
||||
private const val MEM_KB = 1024L shl 10
|
||||
private const val MEM_MB = 1024L shl 20
|
||||
private const val MEM_GB = 1024L shl 30
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
|
||||
append(floor(number * 100) / 100)
|
||||
|
||||
private fun renderMemoryUsageNumber(num: Long) = buildString {
|
||||
when {
|
||||
num == -1L -> {
|
||||
append(num)
|
||||
}
|
||||
num < MEM_B -> {
|
||||
append(num).append("B")
|
||||
}
|
||||
num < MEM_KB -> {
|
||||
appendDouble(num / 1024.0).append("KB")
|
||||
}
|
||||
num < MEM_MB -> {
|
||||
appendDouble((num ushr 10) / 1024.0).append("MB")
|
||||
}
|
||||
else -> {
|
||||
appendDouble((num ushr 20) / 1024.0).append("GB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnsiMessageBuilder.renderMemoryUsage(usage: MUsage) = arrayOf(
|
||||
renderMemoryUsageNumber(usage.committed),
|
||||
renderMemoryUsageNumber(usage.init),
|
||||
@ -728,23 +695,6 @@ public object BuiltInCommands {
|
||||
usage.max,
|
||||
)
|
||||
|
||||
private var emptyLine = " ".repeat(10)
|
||||
private fun Appendable.emptyLine(size: Int) {
|
||||
if (emptyLine.length <= size) {
|
||||
emptyLine = String(CharArray(size) { ' ' })
|
||||
}
|
||||
append(emptyLine, 0, size)
|
||||
}
|
||||
|
||||
private inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) {
|
||||
val s = size - contentLength
|
||||
val left = s / 2
|
||||
val right = s - left
|
||||
emptyLine(left)
|
||||
code()
|
||||
emptyLine(right)
|
||||
}
|
||||
|
||||
private fun calculateMax(
|
||||
vararg lines: Array<String>
|
||||
): IntArray = IntArray(lines[0].size) { r ->
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.fontend
|
||||
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
|
||||
/**
|
||||
* [ProcessProgress] 的简单实现, 前端应该自行实现 [ProcessProgress]
|
||||
*
|
||||
* 此类为前端未实现 [ProcessProgress] 时的缺省实现
|
||||
*/
|
||||
internal class DefaultLoggingProcessProgress : ProcessProgress {
|
||||
private var message: String = ""
|
||||
private var lastDisplay = 0L
|
||||
private var changed: Boolean = false
|
||||
private var failed: Boolean = false
|
||||
|
||||
private companion object {
|
||||
private val logger by lazy { MiraiConsole.createLogger("ProcessProgress") }
|
||||
}
|
||||
|
||||
override fun updateText(txt: String) {
|
||||
this.message = txt
|
||||
changed = true
|
||||
}
|
||||
|
||||
override fun setTotalSize(totalSize: Long) {
|
||||
}
|
||||
|
||||
override fun update(processed: Long) {
|
||||
}
|
||||
|
||||
override fun update(processed: Long, totalSize: Long) {
|
||||
}
|
||||
|
||||
override fun markFailed() {
|
||||
failed = true
|
||||
}
|
||||
|
||||
override fun rerender() {
|
||||
if (!changed) return
|
||||
changed = false
|
||||
val crtTime = System.currentTimeMillis()
|
||||
if (crtTime - lastDisplay < 1000) {
|
||||
return
|
||||
}
|
||||
lastDisplay = crtTime
|
||||
if (failed) {
|
||||
logger.error(message)
|
||||
} else {
|
||||
logger.info(message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (failed) {
|
||||
logger.error(message)
|
||||
} else {
|
||||
logger.info(message)
|
||||
}
|
||||
changed = false
|
||||
message = ""
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.fontend
|
||||
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* 一个下载进度
|
||||
*
|
||||
* @see MiraiConsole.newProcessProgress
|
||||
*/
|
||||
// @ConsoleFrontEndImplementation
|
||||
public interface ProcessProgress : Closeable {
|
||||
/**
|
||||
* 更新当前下载进度的文本
|
||||
*/
|
||||
public fun updateText(txt: String)
|
||||
|
||||
/**
|
||||
* 更新当前下载进度的文本
|
||||
*/
|
||||
public fun updateText(txt: CharSequence) {
|
||||
updateText(txt.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此进度的最终大小
|
||||
*/
|
||||
public fun setTotalSize(totalSize: Long)
|
||||
|
||||
/**
|
||||
* 更新下载进度的进度
|
||||
*
|
||||
* 在更新进度后需要[刷新显示][rerender]
|
||||
*/
|
||||
public fun update(processed: Long)
|
||||
|
||||
/**
|
||||
* 更新下载进度的进度
|
||||
*
|
||||
* 在更新进度后需要[刷新显示][rerender]
|
||||
*/
|
||||
public fun update(processed: Long, totalSize: Long)
|
||||
|
||||
/**
|
||||
* 将该进度标记为 已失败 / 出错
|
||||
*
|
||||
* 注: 即使此进度被标记为失败, 也需要[手动释放][close]
|
||||
*/
|
||||
public fun markFailed()
|
||||
|
||||
/**
|
||||
* 立即重新渲染此进度
|
||||
*/
|
||||
public fun rerender()
|
||||
|
||||
/**
|
||||
* 释放此进度, 相关资源和 UI 将会更新
|
||||
*/
|
||||
override fun close()
|
||||
}
|
@ -9,11 +9,14 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal.plugin
|
||||
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get
|
||||
import net.mamoe.mirai.console.fontend.ProcessProgress
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleBuildDependencies
|
||||
import net.mamoe.mirai.console.internal.data.builtins.DataScope
|
||||
import net.mamoe.mirai.console.internal.data.builtins.PluginDependenciesConfig
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.util.renderMemoryUsageNumber
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.debug
|
||||
import net.mamoe.mirai.utils.verbose
|
||||
@ -41,6 +44,7 @@ import org.eclipse.aether.transfer.AbstractTransferListener
|
||||
import org.eclipse.aether.transfer.TransferEvent
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
||||
@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
|
||||
@ -96,14 +100,60 @@ internal class JvmPluginDependencyDownloader(
|
||||
session, LocalRepository(PluginManager.pluginLibrariesFolder)
|
||||
)
|
||||
session.transferListener = object : AbstractTransferListener() {
|
||||
private val dwnProgresses: MutableMap<File, ProcessProgress> = ConcurrentHashMap()
|
||||
|
||||
override fun transferStarted(event: TransferEvent) {
|
||||
logger.verbose {
|
||||
"Downloading ${event.resource?.repositoryUrl}${event.resource?.resourceName}"
|
||||
}
|
||||
val nw = MiraiConsoleImplementation.getInstance().createNewProcessProgress()
|
||||
dwnProgresses.put(
|
||||
event.resource.file, nw
|
||||
)?.close()
|
||||
nw.setTotalSize(event.resource.contentLength)
|
||||
nw.updateText("Downloading ${event.resource.resourceName}....")
|
||||
}
|
||||
|
||||
override fun transferSucceeded(event: TransferEvent) {
|
||||
dwnProgresses.remove(event.resource.file)?.let { dp ->
|
||||
dp.updateText(buildString {
|
||||
append("Downloaded ")
|
||||
append(event.resource.resourceName)
|
||||
append(" (")
|
||||
renderMemoryUsageNumber(this@buildString, event.resource.contentLength)
|
||||
append(")")
|
||||
})
|
||||
dp.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun transferProgressed(event: TransferEvent) {
|
||||
dwnProgresses[event.resource.file]?.let { pg ->
|
||||
pg.update(event.transferredBytes)
|
||||
pg.updateText(buildString bs@{
|
||||
append("Downloading ")
|
||||
append(event.resource.resourceName)
|
||||
append(" (")
|
||||
val sz = this@bs.length
|
||||
renderMemoryUsageNumber(this@bs, event.transferredBytes)
|
||||
repeat(kotlin.math.max(0, 7 - (this@bs.length - sz))) {
|
||||
append(' ')
|
||||
}
|
||||
|
||||
append(" / ")
|
||||
renderMemoryUsageNumber(this@bs, event.resource.contentLength)
|
||||
append(")")
|
||||
})
|
||||
pg.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
override fun transferFailed(event: TransferEvent) {
|
||||
logger.warning(event.exception)
|
||||
dwnProgresses.remove(event.resource.file)?.let {
|
||||
it.markFailed()
|
||||
it.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
val userHome = System.getProperty("user.home")
|
||||
|
63
mirai-console/backend/mirai-console/src/util/MemoryFormat.kt
Normal file
63
mirai-console/backend/mirai-console/src/util/MemoryFormat.kt
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
private const val MEM_B = 1024L
|
||||
private const val MEM_KB = 1024L shl 10
|
||||
private const val MEM_MB = 1024L shl 20
|
||||
private const val MEM_GB = 1024L shl 30
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
|
||||
append(floor(number * 100) / 100)
|
||||
|
||||
internal fun renderMemoryUsageNumber(num: Long) = buildString {
|
||||
renderMemoryUsageNumber(this, num)
|
||||
}
|
||||
|
||||
internal fun renderMemoryUsageNumber(builder: StringBuilder, num: Long) {
|
||||
when {
|
||||
num == -1L -> {
|
||||
builder.append(num)
|
||||
}
|
||||
num < MEM_B -> {
|
||||
builder.append(num).append("B")
|
||||
}
|
||||
num < MEM_KB -> {
|
||||
builder.appendDouble(num / 1024.0).append("KB")
|
||||
}
|
||||
num < MEM_MB -> {
|
||||
builder.appendDouble((num ushr 10) / 1024.0).append("MB")
|
||||
}
|
||||
else -> {
|
||||
builder.appendDouble((num ushr 20) / 1024.0).append("GB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyLine = " ".repeat(10)
|
||||
internal fun Appendable.emptyLine(size: Int) {
|
||||
if (emptyLine.length <= size) {
|
||||
emptyLine = String(CharArray(size) { ' ' })
|
||||
}
|
||||
append(emptyLine, 0, size)
|
||||
}
|
||||
|
||||
internal inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) {
|
||||
val s = size - contentLength
|
||||
val left = s / 2
|
||||
val right = s - left
|
||||
emptyLine(left)
|
||||
code()
|
||||
emptyLine(right)
|
||||
}
|
@ -42,8 +42,10 @@ internal object ConsoleInputImpl : ConsoleInput {
|
||||
kotlin.runCatching {
|
||||
thread.submit {
|
||||
kotlin.runCatching {
|
||||
waitDownloadingProgressEmpty()
|
||||
lineReader.readLine(
|
||||
if (hint.isNotEmpty()) {
|
||||
prePrintNewLog()
|
||||
lineReader.printAbove(
|
||||
Ansi.ansi()
|
||||
.fgCyan()
|
||||
|
@ -30,6 +30,7 @@ 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.fontend.ProcessProgress
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.terminal.ConsoleInputImpl.requestInput
|
||||
@ -43,12 +44,18 @@ import net.mamoe.mirai.utils.*
|
||||
import org.fusesource.jansi.Ansi
|
||||
import org.jline.reader.LineReader
|
||||
import org.jline.reader.LineReaderBuilder
|
||||
import org.jline.reader.impl.LineReaderImpl
|
||||
import org.jline.reader.impl.completer.NullCompleter
|
||||
import org.jline.terminal.Terminal
|
||||
import org.jline.terminal.TerminalBuilder
|
||||
import org.jline.terminal.impl.AbstractWindowsTerminal
|
||||
import org.jline.utils.AttributedString
|
||||
import org.jline.utils.Display
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* mirai-console-terminal 后端实现
|
||||
@ -86,6 +93,7 @@ open class MiraiConsoleImplementationTerminal
|
||||
configStorageForBuiltIns
|
||||
)
|
||||
}
|
||||
|
||||
// used in test
|
||||
internal val logService: LoggingService
|
||||
|
||||
@ -97,7 +105,9 @@ open class MiraiConsoleImplementationTerminal
|
||||
override fun createLogger(identity: String?): MiraiLogger {
|
||||
return PlatformLogger(identity = identity, output = { line ->
|
||||
val text = line + ANSI_RESET
|
||||
prePrintNewLog()
|
||||
lineReader.printAbove(text)
|
||||
postPrintNewLog()
|
||||
logService.pushLine(text)
|
||||
})
|
||||
}
|
||||
@ -122,6 +132,21 @@ open class MiraiConsoleImplementationTerminal
|
||||
override fun preStart() {
|
||||
registerSignalHandler()
|
||||
overrideSTD(this)
|
||||
launch(CoroutineName("Mirai Console Terminal Downloading Progress Bar Updater")) {
|
||||
while (isActive) {
|
||||
downloadingProgressDaemonStub()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createNewProcessProgress(): ProcessProgress {
|
||||
if (terminal is NoConsole) return super.createNewProcessProgress()
|
||||
|
||||
containDownloadingProgress = true
|
||||
kotlin.runCatching {
|
||||
downloadingProgressCoroutine?.resumeWith(Result.success(Unit))
|
||||
}
|
||||
return TerminalProcessProgress(lineReader).also { terminalDownloadingProgresses.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +160,147 @@ val lineReader: LineReader by lazy {
|
||||
.build()
|
||||
}
|
||||
|
||||
internal val terminalDisplay: Display by object : kotlin.properties.ReadOnlyProperty<Any?, Display> {
|
||||
val delegate: () -> Display by lazy {
|
||||
val terminal = terminal
|
||||
if (terminal is NoConsole) {
|
||||
val display = Display(terminal, false)
|
||||
return@lazy { display }
|
||||
}
|
||||
|
||||
val lr = lineReader
|
||||
val field = LineReaderImpl::class.java.declaredFields.first { it.type == Display::class.java }
|
||||
field.isAccessible = true
|
||||
return@lazy { field[lr] as Display }
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Display {
|
||||
return delegate()
|
||||
}
|
||||
}
|
||||
|
||||
internal val terminalExecuteLock: java.util.concurrent.locks.Lock by lazy {
|
||||
val terminal = terminal
|
||||
if (terminal is NoConsole) return@lazy java.util.concurrent.locks.ReentrantLock()
|
||||
val lr = lineReader
|
||||
val field = LineReaderImpl::class.java.declaredFields.first {
|
||||
java.util.concurrent.locks.Lock::class.java.isAssignableFrom(it.type)
|
||||
}
|
||||
field.isAccessible = true
|
||||
field[lr].cast()
|
||||
}
|
||||
private val terminalDownloadingProgressesNoticer = Object()
|
||||
private var containDownloadingProgress: Boolean = false
|
||||
get() = field || terminalDownloadingProgresses.isNotEmpty()
|
||||
|
||||
internal val terminalDownloadingProgresses = mutableListOf<TerminalProcessProgress>()
|
||||
private var downloadingProgressCoroutine: Continuation<Unit>? = null
|
||||
private suspend fun downloadingProgressDaemonStub() {
|
||||
delay(500L)
|
||||
if (containDownloadingProgress) {
|
||||
updateTerminalDownloadingProgresses()
|
||||
} else {
|
||||
suspendCancellableCoroutine<Unit> { cp ->
|
||||
downloadingProgressCoroutine = cp
|
||||
}
|
||||
downloadingProgressCoroutine = null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updateTerminalDownloadingProgresses() {
|
||||
if (!containDownloadingProgress) return
|
||||
|
||||
runCatching { downloadingProgressCoroutine?.resumeWith(Result.success(Unit)) }
|
||||
|
||||
terminalExecuteLock.withLock {
|
||||
if (terminalDownloadingProgresses.isNotEmpty()) {
|
||||
val wid = terminal.width
|
||||
if (wid == 0) { // Run in idea
|
||||
if (terminalDownloadingProgresses.removeIf { it.pendingErase }) {
|
||||
updateTerminalDownloadingProgresses()
|
||||
return
|
||||
}
|
||||
terminalDisplay.update(listOf(AttributedString.EMPTY), 0, false)
|
||||
// Error in idea when more than one bar displaying
|
||||
terminalDisplay.update(listOf(terminalDownloadingProgresses[0].let {
|
||||
it.updateTxt(0); it.ansiMsg
|
||||
}), 0)
|
||||
} else {
|
||||
if (terminalDownloadingProgresses.size > 4) {
|
||||
// to mush. delete some completed status
|
||||
var allowToDelete = terminalDownloadingProgresses.size - 4
|
||||
terminalDownloadingProgresses.removeIf { pg ->
|
||||
if (allowToDelete == 0) {
|
||||
return@removeIf false
|
||||
}
|
||||
if (pg.pendingErase) {
|
||||
allowToDelete--
|
||||
return@removeIf true
|
||||
}
|
||||
return@removeIf false
|
||||
}
|
||||
}
|
||||
terminalDisplay.update(terminalDownloadingProgresses.map {
|
||||
it.updateTxt(wid); it.ansiMsg
|
||||
}, 0)
|
||||
cleanupErase()
|
||||
}
|
||||
} else {
|
||||
terminalDisplay.update(emptyList(), 0)
|
||||
(lineReader as LineReaderImpl).let { lr ->
|
||||
if (lr.isReading) {
|
||||
lr.redisplay()
|
||||
}
|
||||
}
|
||||
noticeDownloadingProgressEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun prePrintNewLog() {
|
||||
if (!containDownloadingProgress) return
|
||||
if (terminalDownloadingProgresses.isNotEmpty()) {
|
||||
terminalExecuteLock.withLock {
|
||||
terminalDisplay.update(emptyList(), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun cleanupErase() {
|
||||
val now = currentTimeMillis()
|
||||
terminalDownloadingProgresses.removeIf { pg ->
|
||||
if (!pg.pendingErase) return@removeIf false
|
||||
if (now > pg.eraseTimestamp) {
|
||||
pg.ansiMsg = AttributedString.EMPTY
|
||||
return@removeIf true
|
||||
}
|
||||
return@removeIf false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun postPrintNewLog() {
|
||||
if (!containDownloadingProgress) return
|
||||
updateTerminalDownloadingProgresses()
|
||||
cleanupErase()
|
||||
}
|
||||
|
||||
private fun noticeDownloadingProgressEmpty() {
|
||||
synchronized(terminalDownloadingProgressesNoticer) {
|
||||
containDownloadingProgress = false
|
||||
if (terminalDownloadingProgresses.isEmpty()) {
|
||||
terminalDownloadingProgressesNoticer.notifyAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun waitDownloadingProgressEmpty() {
|
||||
synchronized(terminalDownloadingProgressesNoticer) {
|
||||
if (containDownloadingProgress) {
|
||||
terminalDownloadingProgressesNoticer.wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val terminal: Terminal = run {
|
||||
if (ConsoleTerminalSettings.noConsole) return@run NoConsole
|
||||
|
||||
|
@ -308,7 +308,9 @@ internal fun overrideSTD(terminal: MiraiConsoleImplementation) {
|
||||
internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
|
||||
override suspend fun sendMessage(message: String) {
|
||||
kotlin.runCatching {
|
||||
prePrintNewLog()
|
||||
lineReader.printAbove(message + ANSI_RESET)
|
||||
postPrintNewLog()
|
||||
}.onFailure { exception ->
|
||||
// If failed. It means JLine Terminal not working...
|
||||
PrintStream(FileOutputStream(FileDescriptor.err)).use {
|
||||
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.terminal
|
||||
|
||||
import net.mamoe.mirai.console.fontend.ProcessProgress
|
||||
import org.jline.utils.AttributedString
|
||||
import org.jline.utils.AttributedStringBuilder
|
||||
import org.jline.utils.AttributedStyle
|
||||
|
||||
internal class TerminalProcessProgress(
|
||||
private val reader: org.jline.reader.LineReader,
|
||||
) : ProcessProgress {
|
||||
private var totalSize: Long = 1
|
||||
private var processed: Long = 0
|
||||
private val txt: StringBuilder = StringBuilder()
|
||||
private val renderedTxt: StringBuilder = StringBuilder()
|
||||
private var failed: Boolean = false
|
||||
private var disposed: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var pendingErase: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var eraseTimestamp: Long = 0
|
||||
|
||||
@JvmField
|
||||
var ansiMsg: AttributedString = AttributedString.EMPTY
|
||||
|
||||
private var lastTerminalWidth = 0
|
||||
private var needRerender: Boolean = true
|
||||
private var needUpdateTxt: Boolean = true
|
||||
|
||||
override fun updateText(txt: CharSequence) {
|
||||
this.txt.setLength(0)
|
||||
this.txt.append(txt)
|
||||
needUpdateTxt = true
|
||||
needRerender = true
|
||||
}
|
||||
|
||||
override fun updateText(txt: String) {
|
||||
updateText(txt as CharSequence)
|
||||
}
|
||||
|
||||
override fun setTotalSize(totalSize: Long) {
|
||||
this.totalSize = totalSize
|
||||
needRerender = true
|
||||
}
|
||||
|
||||
override fun update(processed: Long) {
|
||||
this.processed = processed
|
||||
needRerender = true
|
||||
}
|
||||
|
||||
override fun update(processed: Long, totalSize: Long) {
|
||||
this.processed = processed
|
||||
this.totalSize = totalSize
|
||||
needRerender = true
|
||||
}
|
||||
|
||||
override fun markFailed() {
|
||||
failed = true
|
||||
needRerender = true
|
||||
}
|
||||
|
||||
internal fun updateTxt(terminalWidth: Int) {
|
||||
|
||||
// region check need to update
|
||||
if (needUpdateTxt || lastTerminalWidth != terminalWidth) {
|
||||
// <text changed / screen width changed>
|
||||
lastTerminalWidth = terminalWidth
|
||||
synchronized(renderedTxt) {
|
||||
renderedTxt.setLength(0)
|
||||
renderedTxt.append(txt)
|
||||
// paddings
|
||||
if (renderedTxt.length < terminalWidth) {
|
||||
repeat(terminalWidth - renderedTxt.length) {
|
||||
renderedTxt.append(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!needRerender) {
|
||||
// nothing changed
|
||||
return
|
||||
} /* else { <api require rerender> } */
|
||||
|
||||
lastTerminalWidth = terminalWidth
|
||||
// endregion
|
||||
|
||||
val renderedTextWidth = when (terminalWidth) {
|
||||
0 -> renderedTxt.length
|
||||
else -> terminalWidth
|
||||
}
|
||||
|
||||
val finalAnsiLineBuilder = AttributedStringBuilder()
|
||||
|
||||
if (failed) {
|
||||
finalAnsiLineBuilder.style(
|
||||
AttributedStyle.DEFAULT
|
||||
.background(AttributedStyle.RED)
|
||||
.foreground(AttributedStyle.BLACK)
|
||||
)
|
||||
finalAnsiLineBuilder.append(renderedTxt, 0, renderedTextWidth)
|
||||
} else {
|
||||
val downpcent = (renderedTextWidth * processed / totalSize).toInt()
|
||||
if (downpcent > 0) {
|
||||
finalAnsiLineBuilder.style(
|
||||
AttributedStyle.DEFAULT
|
||||
.background(AttributedStyle.GREEN)
|
||||
.foreground(AttributedStyle.BLACK)
|
||||
)
|
||||
finalAnsiLineBuilder.append(renderedTxt, 0, downpcent)
|
||||
}
|
||||
if (downpcent < renderedTextWidth) {
|
||||
finalAnsiLineBuilder.style(
|
||||
AttributedStyle.DEFAULT
|
||||
.background(AttributedStyle.WHITE)
|
||||
.foreground(AttributedStyle.BLACK)
|
||||
)
|
||||
finalAnsiLineBuilder.append(renderedTxt, downpcent, renderedTextWidth)
|
||||
}
|
||||
}
|
||||
ansiMsg = finalAnsiLineBuilder.toAttributedString()
|
||||
needUpdateTxt = false
|
||||
needRerender = false
|
||||
}
|
||||
|
||||
override fun rerender() {
|
||||
updateTerminalDownloadingProgresses()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (disposed) return
|
||||
disposed = true
|
||||
|
||||
totalSize = 1
|
||||
processed = 1
|
||||
needUpdateTxt = true
|
||||
updateTxt(reader.terminal.width)
|
||||
if (failed) {
|
||||
terminalDownloadingProgresses.remove(this)
|
||||
prePrintNewLog()
|
||||
reader.printAbove(ansiMsg)
|
||||
ansiMsg = AttributedString.EMPTY
|
||||
postPrintNewLog()
|
||||
return
|
||||
}
|
||||
// terminalDownloadingProgresses.remove(this)
|
||||
pendingErase = true
|
||||
eraseTimestamp = System.currentTimeMillis() + 1500L
|
||||
|
||||
updateTerminalDownloadingProgresses()
|
||||
|
||||
// prePrintNewLog()
|
||||
// reader.printAbove(ansiMsg)
|
||||
// ansiMsg = AttributedString.EMPTY
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user