[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:
微莹·纤绫 2022-08-20 15:58:03 +08:00 committed by GitHub
parent a843729f53
commit 85d81efc4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 627 additions and 52 deletions

View File

@ -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;
}

View File

@ -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()
}
}

View File

@ -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 输出彩色信息
*

View File

@ -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 ->

View File

@ -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 = ""
}
}

View File

@ -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()
}

View File

@ -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")

View 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)
}

View File

@ -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()

View File

@ -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

View File

@ -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 {

View File

@ -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
}
}