Skip to content

Commit 206f3cc

Browse files
authored
Improve accuracy of progress updates (#144)
This PR resolves the problem of dropped progress updates and ensures the accuracy of the information provided in the progress bar. Additionally, it adds the displaying of the finished state to the progress bar. Requires merging and tagging apple/containerization#91.
1 parent 8f2d4d7 commit 206f3cc

File tree

7 files changed

+336
-75
lines changed

7 files changed

+336
-75
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ if let path = ProcessInfo.processInfo.environment["CONTAINERIZATION_PATH"] {
2626
scDependency = .package(path: path)
2727
scVersion = "latest"
2828
} else {
29-
scVersion = "0.1.0"
29+
scVersion = "0.1.1"
3030
scDependency = .package(url: "https://github.com/apple/containerization.git", exact: Version(stringLiteral: scVersion))
3131
}
3232

Sources/ContainerXPC/XPCClient.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import ContainerizationError
1919
import Foundation
2020

2121
public struct XPCClient: Sendable {
22-
// Access to `connection` is protected by a lock
2322
private nonisolated(unsafe) let connection: xpc_connection_t
2423
private let q: DispatchQueue?
2524
private let service: String

Sources/TerminalProgress/ProgressBar+Add.swift

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,24 @@ extension ProgressBar {
6161

6262
/// Performs a check to see if the progress bar should be finished.
6363
public func checkIfFinished() {
64-
if let totalTasks = state.totalTasks {
64+
var finished = true
65+
var defined = false
66+
if let totalTasks = state.totalTasks, totalTasks > 0 {
6567
// For tasks, we're showing the current task rather then the number of completed tasks.
66-
guard state.tasks > totalTasks else {
67-
return
68-
}
68+
finished = finished && state.tasks == totalTasks
69+
defined = true
6970
}
70-
if let totalItems = state.totalItems {
71-
guard state.items == totalItems else {
72-
return
73-
}
71+
if let totalItems = state.totalItems, totalItems > 0 {
72+
finished = finished && state.items == totalItems
73+
defined = true
7474
}
75-
if let totalSize = state.totalSize {
76-
guard state.size == totalSize else {
77-
return
78-
}
75+
if let totalSize = state.totalSize, totalSize > 0 {
76+
finished = finished && state.size == totalSize
77+
defined = true
78+
}
79+
if defined && finished {
80+
finish()
7981
}
80-
finish()
8182
}
8283

8384
/// Sets the current tasks.
@@ -92,9 +93,14 @@ extension ProgressBar {
9293

9394
/// Performs an addition to the current tasks.
9495
/// - Parameter tasks: The tasks to add to the current tasks.
95-
public func add(tasks toAdd: Int, render: Bool = true) {
96-
let newTasks = state.tasks + toAdd
97-
set(tasks: newTasks, render: render)
96+
public func add(tasks delta: Int, render: Bool = true) {
97+
_state.withLock {
98+
let newTasks = $0.tasks + delta
99+
$0.tasks = newTasks
100+
}
101+
if render {
102+
self.render()
103+
}
98104
}
99105

100106
/// Sets the total tasks.
@@ -108,10 +114,15 @@ extension ProgressBar {
108114

109115
/// Performs an addition to the total tasks.
110116
/// - Parameter totalTasks: The tasks to add to the total tasks.
111-
public func add(totalTasks toAdd: Int, render: Bool = true) {
112-
let totalTasks = state.totalTasks ?? 0
113-
let newTotalTasks = totalTasks + toAdd
114-
set(totalTasks: newTotalTasks, render: render)
117+
public func add(totalTasks delta: Int, render: Bool = true) {
118+
_state.withLock {
119+
let totalTasks = $0.totalTasks ?? 0
120+
let newTotalTasks = totalTasks + delta
121+
$0.totalTasks = newTotalTasks
122+
}
123+
if render {
124+
self.render()
125+
}
115126
}
116127

117128
/// Sets the items name.
@@ -134,9 +145,14 @@ extension ProgressBar {
134145

135146
/// Performs an addition to the current items.
136147
/// - Parameter items: The items to add to the current items.
137-
public func add(items toAdd: Int, render: Bool = true) {
138-
let newItems = state.items + toAdd
139-
set(items: newItems, render: render)
148+
public func add(items delta: Int, render: Bool = true) {
149+
_state.withLock {
150+
let newItems = $0.items + delta
151+
$0.items = newItems
152+
}
153+
if render {
154+
self.render()
155+
}
140156
}
141157

142158
/// Sets the total items.
@@ -150,10 +166,15 @@ extension ProgressBar {
150166

151167
/// Performs an addition to the total items.
152168
/// - Parameter totalItems: The items to add to the total items.
153-
public func add(totalItems toAdd: Int, render: Bool = true) {
154-
let totalItems = state.totalItems ?? 0
155-
let newTotalItems = totalItems + toAdd
156-
set(totalItems: newTotalItems, render: render)
169+
public func add(totalItems delta: Int, render: Bool = true) {
170+
_state.withLock {
171+
let totalItems = $0.totalItems ?? 0
172+
let newTotalItems = totalItems + delta
173+
$0.totalItems = newTotalItems
174+
}
175+
if render {
176+
self.render()
177+
}
157178
}
158179

159180
/// Sets the current size.
@@ -167,9 +188,14 @@ extension ProgressBar {
167188

168189
/// Performs an addition to the current size.
169190
/// - Parameter size: The size to add to the current size.
170-
public func add(size toAdd: Int64, render: Bool = true) {
171-
let newSize = state.size + toAdd
172-
set(size: newSize, render: render)
191+
public func add(size delta: Int64, render: Bool = true) {
192+
_state.withLock {
193+
let newSize = $0.size + delta
194+
$0.size = newSize
195+
}
196+
if render {
197+
self.render()
198+
}
173199
}
174200

175201
/// Sets the total size.
@@ -183,9 +209,14 @@ extension ProgressBar {
183209

184210
/// Performs an addition to the total size.
185211
/// - Parameter totalSize: The size to add to the total size.
186-
public func add(totalSize toAdd: Int64, render: Bool = true) {
187-
let totalSize = state.totalSize ?? 0
188-
let newTotalSize = totalSize + toAdd
189-
set(totalSize: newTotalSize, render: render)
212+
public func add(totalSize delta: Int64, render: Bool = true) {
213+
_state.withLock {
214+
let totalSize = $0.totalSize ?? 0
215+
let newTotalSize = totalSize + delta
216+
$0.totalSize = newTotalSize
217+
}
218+
if render {
219+
self.render()
220+
}
190221
}
191222
}

Sources/TerminalProgress/ProgressBar.swift

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import SendableProperty
2020
/// A progress bar that updates itself as tasks are completed.
2121
public final class ProgressBar: Sendable {
2222
let config: ProgressConfig
23+
// `@SendableProperty` adds `_state: Synchronized<State>`, which can be updated inside a lock using `_state.withLock()`.
2324
@SendableProperty
24-
var state: State
25+
var state = State()
2526
@SendableProperty
2627
var printedWidth = 0
2728
let term: FileHandle?
@@ -97,7 +98,7 @@ public final class ProgressBar: Sendable {
9798
printFullDescription()
9899
}
99100

100-
while !isFinished {
101+
while !state.finished {
101102
let intervalNanoseconds = UInt64(intervalSeconds * 1_000_000_000)
102103
render()
103104
state.iteration += 1
@@ -117,11 +118,15 @@ public final class ProgressBar: Sendable {
117118

118119
/// Finishes the progress bar.
119120
public func finish() {
120-
guard !isFinished else {
121+
guard !state.finished else {
121122
return
122123
}
123124

124125
state.finished = true
126+
127+
// The last render.
128+
render(force: true)
129+
125130
if !config.disableProgressUpdates && !config.clearOnFinish {
126131
displayText(state.output, terminating: "\n")
127132
}
@@ -143,8 +148,8 @@ extension ProgressBar {
143148
return timeDifferenceSeconds
144149
}
145150

146-
func render() {
147-
guard term != nil && !config.disableProgressUpdates && !isFinished else {
151+
func render(force: Bool = false) {
152+
guard term != nil && !config.disableProgressUpdates && (force || !state.finished) else {
148153
return
149154
}
150155
let output = draw()
@@ -154,8 +159,12 @@ extension ProgressBar {
154159
func draw() -> String {
155160
var components = [String]()
156161
if config.showSpinner && !config.showProgressBar {
157-
let spinnerIcon = config.theme.getSpinnerIcon(state.iteration)
158-
components.append("\(spinnerIcon)")
162+
if !state.finished {
163+
let spinnerIcon = config.theme.getSpinnerIcon(state.iteration)
164+
components.append("\(spinnerIcon)")
165+
} else {
166+
components.append("\(config.theme.done)")
167+
}
159168
}
160169

161170
if config.showTasks, let totalTasks = state.totalTasks {
@@ -176,13 +185,13 @@ extension ProgressBar {
176185
let total = state.totalSize ?? Int64(state.totalItems ?? 0)
177186

178187
if config.showPercent && total > 0 && allowProgress {
179-
components.append("\(state.percent)")
188+
components.append("\(state.finished ? "100%" : state.percent)")
180189
}
181190

182191
if config.showProgressBar, total > 0, allowProgress {
183192
let usedWidth = components.joined(separator: " ").count + 45 /* the maximum number of characters we may need */
184193
let remainingWidth = max(config.width - usedWidth, 1 /* the minumum width of a progress bar */)
185-
let barLength = Int(Int64(remainingWidth) * value / total)
194+
let barLength = state.finished ? remainingWidth : Int(Int64(remainingWidth) * value / total)
186195
let barPaddingLength = remainingWidth - barLength
187196
let bar = "\(String(repeating: config.theme.bar, count: barLength))\(String(repeating: " ", count: barPaddingLength))"
188197
components.append("|\(bar)|")
@@ -195,40 +204,56 @@ extension ProgressBar {
195204
if !state.itemsName.isEmpty {
196205
itemsName = " \(state.itemsName)"
197206
}
198-
if let totalItems = state.totalItems {
199-
additionalComponents.append("\(state.items.formattedNumber()) of \(totalItems.formattedNumber())\(itemsName)")
207+
if state.finished {
208+
if let totalItems = state.totalItems {
209+
additionalComponents.append("\(totalItems.formattedNumber())\(itemsName)")
210+
}
200211
} else {
201-
additionalComponents.append("\(state.items.formattedNumber())\(itemsName)")
212+
if let totalItems = state.totalItems {
213+
additionalComponents.append("\(state.items.formattedNumber()) of \(totalItems.formattedNumber())\(itemsName)")
214+
} else {
215+
additionalComponents.append("\(state.items.formattedNumber())\(itemsName)")
216+
}
202217
}
203218
}
204219

205220
if state.size > 0 && allowProgress {
206-
var formattedCombinedSize = ""
207-
if config.showSize {
208-
var formattedSize = state.size.formattedSize()
209-
formattedSize = adjustFormattedSize(formattedSize)
210-
if let totalSize = state.totalSize {
211-
var formattedTotalSize = totalSize.formattedSize()
212-
formattedTotalSize = adjustFormattedSize(formattedTotalSize)
213-
formattedCombinedSize = combineSize(size: formattedSize, totalSize: formattedTotalSize)
214-
} else {
215-
formattedCombinedSize = formattedSize
221+
if state.finished {
222+
if config.showSize {
223+
if let totalSize = state.totalSize {
224+
var formattedTotalSize = totalSize.formattedSize()
225+
formattedTotalSize = adjustFormattedSize(formattedTotalSize)
226+
additionalComponents.append(formattedTotalSize)
227+
}
228+
}
229+
} else {
230+
var formattedCombinedSize = ""
231+
if config.showSize {
232+
var formattedSize = state.size.formattedSize()
233+
formattedSize = adjustFormattedSize(formattedSize)
234+
if let totalSize = state.totalSize {
235+
var formattedTotalSize = totalSize.formattedSize()
236+
formattedTotalSize = adjustFormattedSize(formattedTotalSize)
237+
formattedCombinedSize = combineSize(size: formattedSize, totalSize: formattedTotalSize)
238+
} else {
239+
formattedCombinedSize = formattedSize
240+
}
216241
}
217-
}
218242

219-
var formattedSpeed = ""
220-
if config.showSpeed {
221-
formattedSpeed = "\(state.sizeSpeed ?? state.averageSizeSpeed)"
222-
formattedSpeed = adjustFormattedSize(formattedSpeed)
223-
}
243+
var formattedSpeed = ""
244+
if config.showSpeed {
245+
formattedSpeed = "\(state.sizeSpeed ?? state.averageSizeSpeed)"
246+
formattedSpeed = adjustFormattedSize(formattedSpeed)
247+
}
224248

225-
if config.showSize && config.showSpeed {
226-
additionalComponents.append(formattedCombinedSize)
227-
additionalComponents.append(formattedSpeed)
228-
} else if config.showSize {
229-
additionalComponents.append(formattedCombinedSize)
230-
} else if config.showSpeed {
231-
additionalComponents.append(formattedSpeed)
249+
if config.showSize && config.showSpeed {
250+
additionalComponents.append(formattedCombinedSize)
251+
additionalComponents.append(formattedSpeed)
252+
} else if config.showSize {
253+
additionalComponents.append(formattedCombinedSize)
254+
} else if config.showSpeed {
255+
additionalComponents.append(formattedSpeed)
256+
}
232257
}
233258
}
234259

Sources/TerminalProgress/ProgressTheme.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
public protocol ProgressTheme: Sendable {
1919
/// The icons used to represent a spinner.
2020
var spinner: [String] { get }
21-
/// The icons used to represent a progress bar.
21+
/// The icon used to represent a progress bar.
2222
var bar: String { get }
23+
/// The icon used to indicate that a progress bar finished.
24+
var done: String { get }
2325
}
2426

2527
public struct DefaultProgressTheme: ProgressTheme {
2628
public let spinner = ["", "", "", "", "", "", "", "", "", ""]
2729
public let bar = ""
30+
public let done = ""
2831
}
2932

3033
extension ProgressTheme {

0 commit comments

Comments
 (0)