Files
container/Sources/TerminalProgress/ProgressBar+State.swift
T
Danny Canter b671690c17 ProgressBar: Various fixes (#1025)
There's a couple things I don't think are intuitive about this.

1. Because of the internal task, render() can still be called even after
finish() completes. Ideally async defers are supported and we could just
await the final render completing after cancelling the task and setting
.finished, but alas. To fix this we can just lock across the methods for
now.
2. We always clear the screen in the destructor, even if we don't use
the
progress bar. I don't think we should honestly do anything in the
destructor.
Feels a programmer error not to defer { bar.finish() } or call it
somewhere.
3. Our spaces based line clearing. Use the ansi escape sequence for
clearing line;
I think our calculations were slightly off and it would leave trailing
output ( "s]" )
in some cases.
4. Shrinking the window until the output is smaller than the terminal
window (and vice
versa) is wonky on various term emulators. Truthfully, this is just a
hard problem,
but we can truncate our output and still provide some useful info.

This fixes some single line output (cat /etc/hostname etc.) getting
cleared in our atexit handler, as well as the need for the usleep.
2026-01-07 21:01:10 -08:00

101 lines
3.5 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
extension ProgressBar {
/// State for the progress bar.
struct State {
/// A flag indicating whether the progress bar is finished.
var finished = false
var iteration = 0
private let speedInterval: DispatchTimeInterval = .seconds(1)
var description: String
var subDescription: String
var itemsName: String
var tasks: Int
var totalTasks: Int?
var items: Int
var totalItems: Int?
private var sizeUpdateTime: DispatchTime?
private var sizeUpdateValue: Int64 = 0
var size: Int64 {
didSet {
calculateSizeSpeed()
}
}
var totalSize: Int64?
private var sizeUpdateSpeed: String?
var sizeSpeed: String? {
guard sizeUpdateTime == nil || sizeUpdateTime! > .now() - speedInterval - speedInterval else {
return Int64(0).formattedSizeSpeed(from: startTime)
}
return sizeUpdateSpeed
}
var averageSizeSpeed: String {
size.formattedSizeSpeed(from: startTime)
}
var percent: String {
var value = 0
if let totalSize, totalSize > 0 {
value = Int(size * 100 / totalSize)
} else if let totalItems, totalItems > 0 {
value = Int(items * 100 / totalItems)
}
value = min(value, 100)
return "\(value)%"
}
var startTime: DispatchTime
var output = ""
var renderTask: Task<Void, Never>?
init(
description: String = "", subDescription: String = "", itemsName: String = "", tasks: Int = 0, totalTasks: Int? = nil, items: Int = 0, totalItems: Int? = nil,
size: Int64 = 0, totalSize: Int64? = nil, startTime: DispatchTime = .now()
) {
self.description = description
self.subDescription = subDescription
self.itemsName = itemsName
self.tasks = tasks
self.totalTasks = totalTasks
self.items = items
self.totalItems = totalItems
self.size = size
self.totalSize = totalSize
self.startTime = startTime
}
private mutating func calculateSizeSpeed() {
if sizeUpdateTime == nil || sizeUpdateTime! < .now() - speedInterval {
let partSize = size - sizeUpdateValue
let partStartTime = sizeUpdateTime ?? startTime
let partSizeSpeed = partSize.formattedSizeSpeed(from: partStartTime)
self.sizeUpdateSpeed = partSizeSpeed
sizeUpdateTime = .now()
sizeUpdateValue = size
}
}
}
}