@@ -20,11 +20,13 @@ import (
2020 "context"
2121 "fmt"
2222 "io"
23+ "iter"
2324 "strings"
2425 "sync"
2526 "time"
2627
2728 "github.com/buger/goterm"
29+ "github.com/docker/compose/v5/pkg/utils"
2830 "github.com/docker/go-units"
2931 "github.com/morikuni/aec"
3032
@@ -60,7 +62,8 @@ type ttyWriter struct {
6062
6163type task struct {
6264 ID string
63- parentID string
65+ parent string // the resource this task receives updates from - other parents will be ignored
66+ parents utils.Set [string ] // all resources to depend on this task
6467 startTime time.Time
6568 endTime time.Time
6669 text string
@@ -72,6 +75,64 @@ type task struct {
7275 spinner * Spinner
7376}
7477
78+ func newTask (e api.Resource ) task {
79+ t := task {
80+ ID : e .ID ,
81+ parents : utils .NewSet [string ](),
82+ startTime : time .Now (),
83+ text : e .Text ,
84+ details : e .Details ,
85+ status : e .Status ,
86+ current : e .Current ,
87+ percent : e .Percent ,
88+ total : e .Total ,
89+ spinner : NewSpinner (),
90+ }
91+ if e .ParentID != "" {
92+ t .parent = e .ParentID
93+ t .parents .Add (e .ParentID )
94+ }
95+ if e .Status == api .Done || e .Status == api .Error {
96+ t .stop ()
97+ }
98+ return t
99+ }
100+
101+ // update adjusts task state based on last received event
102+ func (t * task ) update (e api.Resource ) {
103+ if e .ParentID != "" {
104+ t .parents .Add (e .ParentID )
105+ // we may receive same event from distinct parents (typically: images sharing layers)
106+ // to avoid status to flicker, only accept updates from our first declared parent
107+ if t .parent != e .ParentID {
108+ return
109+ }
110+ }
111+
112+ // update task based on received event
113+ switch e .Status {
114+ case api .Done , api .Error , api .Warning :
115+ if t .status != e .Status {
116+ t .stop ()
117+ }
118+ case api .Working :
119+ t .hasMore ()
120+ }
121+ t .status = e .Status
122+ t .text = e .Text
123+ t .details = e .Details
124+ // progress can only go up
125+ if e .Total > t .total {
126+ t .total = e .Total
127+ }
128+ if e .Current > t .current {
129+ t .current = e .Current
130+ }
131+ if e .Percent > t .percent {
132+ t .percent = e .Percent
133+ }
134+ }
135+
75136func (t * task ) stop () {
76137 t .endTime = time .Now ()
77138 t .spinner .Stop ()
@@ -81,6 +142,15 @@ func (t *task) hasMore() {
81142 t .spinner .Restart ()
82143}
83144
145+ func (t * task ) Completed () bool {
146+ switch t .status {
147+ case api .Done , api .Error , api .Warning :
148+ return true
149+ default :
150+ return false
151+ }
152+ }
153+
84154func (w * ttyWriter ) Start (ctx context.Context , operation string ) {
85155 w .ticker = time .NewTicker (100 * time .Millisecond )
86156 w .operation = operation
@@ -136,49 +206,13 @@ func (w *ttyWriter) event(e api.Resource) {
136206 w .suspended = false
137207 }
138208
139- if last , ok := w .tasks [e .ID ]; ok {
140- switch e .Status {
141- case api .Done , api .Error , api .Warning :
142- if last .status != e .Status {
143- last .stop ()
144- }
145- case api .Working :
146- last .hasMore ()
147- }
148- last .status = e .Status
149- last .text = e .Text
150- last .details = e .Details
151- // progress can only go up
152- if e .Total > last .total {
153- last .total = e .Total
154- }
155- if e .Current > last .current {
156- last .current = e .Current
157- }
158- if e .Percent > last .percent {
159- last .percent = e .Percent
160- }
161- // allow set/unset of parent, but not swapping otherwise prompt is flickering
162- if last .parentID == "" || e .ParentID == "" {
163- last .parentID = e .ParentID
164- }
209+ last , knownEvent := w .tasks [e .ID ]
210+ switch {
211+ case knownEvent :
212+ last .update (e )
165213 w .tasks [e .ID ] = last
166- } else {
167- t := task {
168- ID : e .ID ,
169- parentID : e .ParentID ,
170- startTime : time .Now (),
171- text : e .Text ,
172- details : e .Details ,
173- status : e .Status ,
174- current : e .Current ,
175- percent : e .Percent ,
176- total : e .Total ,
177- spinner : NewSpinner (),
178- }
179- if e .Status == api .Done || e .Status == api .Error {
180- t .stop ()
181- }
214+ default :
215+ t := newTask (e )
182216 w .tasks [e .ID ] = t
183217 w .ids = append (w .ids , e .ID )
184218 }
@@ -205,6 +239,28 @@ func (w *ttyWriter) printEvent(e api.Resource) {
205239 _ , _ = fmt .Fprintf (w .out , "%s %s %s\n " , e .ID , color (e .Text ), e .Details )
206240}
207241
242+ func (w * ttyWriter ) parentTasks () iter.Seq [task ] {
243+ return func (yield func (task ) bool ) {
244+ for _ , id := range w .ids { // iterate on ids to enforce a consistent order
245+ t := w .tasks [id ]
246+ if len (t .parents ) == 0 {
247+ yield (t )
248+ }
249+ }
250+ }
251+ }
252+
253+ func (w * ttyWriter ) childrenTasks (parent string ) iter.Seq [task ] {
254+ return func (yield func (task ) bool ) {
255+ for _ , id := range w .ids { // iterate on ids to enforce a consistent order
256+ t := w .tasks [id ]
257+ if t .parents .Has (parent ) {
258+ yield (t )
259+ }
260+ }
261+ }
262+ }
263+
208264func (w * ttyWriter ) print () {
209265 w .mtx .Lock ()
210266 defer w .mtx .Unlock ()
@@ -233,20 +289,25 @@ func (w *ttyWriter) print() {
233289 var statusPadding int
234290 for _ , t := range w .tasks {
235291 l := len (t .ID )
236- if t . parentID == "" && statusPadding < l {
292+ if len ( t . parents ) == 0 && statusPadding < l {
237293 statusPadding = l
238294 }
239295 }
240296
297+ skipChildEvents := len (w .tasks ) > goterm .Height ()- 2
241298 numLines := 0
242- for _ , id := range w .ids { // iterate on ids to enforce a consistent order
243- t := w .tasks [id ]
244- if t .parentID != "" {
245- continue
246- }
299+ for t := range w .parentTasks () {
247300 line := w .lineText (t , "" , terminalWidth , statusPadding , w .dryRun )
248301 _ , _ = fmt .Fprint (w .out , line )
249302 numLines ++
303+ if skipChildEvents {
304+ continue
305+ }
306+ for child := range w .childrenTasks (t .ID ) {
307+ line := w .lineText (child , " " , terminalWidth , statusPadding - 2 , w .dryRun )
308+ _ , _ = fmt .Fprint (w .out , line )
309+ numLines ++
310+ }
250311 }
251312 for i := numLines ; i < w .numLines ; i ++ {
252313 if numLines < goterm .Height ()- 2 {
@@ -281,18 +342,15 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
281342
282343 // only show the aggregated progress while the root operation is in-progress
283344 if parent := t ; parent .status == api .Working {
284- for _ , id := range w .ids {
285- child := w .tasks [id ]
286- if child .parentID == parent .ID {
287- if child .status == api .Working && child .total == 0 {
288- // we don't have totals available for all the child events
289- // so don't show the total progress yet
290- hideDetails = true
291- }
292- total += child .total
293- current += child .current
294- completion = append (completion , percentChars [(len (percentChars )- 1 )* child .percent / 100 ])
345+ for child := range w .childrenTasks (parent .ID ) {
346+ if child .status == api .Working && child .total == 0 {
347+ // we don't have totals available for all the child events
348+ // so don't show the total progress yet
349+ hideDetails = true
295350 }
351+ total += child .total
352+ current += child .current
353+ completion = append (completion , percentChars [(len (percentChars )- 1 )* child .percent / 100 ])
296354 }
297355 }
298356
0 commit comments