File: progress.go

package info (click to toggle)
incus 6.0.5-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 25,788 kB
  • sloc: sh: 16,313; ansic: 3,121; python: 457; makefile: 337; ruby: 51; sql: 50; lisp: 6
file content (194 lines) | stat: -rw-r--r-- 3,501 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package cmd

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/lxc/incus/v6/shared/api"
	"github.com/lxc/incus/v6/shared/ioprogress"
	"github.com/lxc/incus/v6/shared/termios"
)

// ProgressRenderer tracks the progress information.
type ProgressRenderer struct {
	Format string
	Quiet  bool

	maxLength int
	wait      time.Time
	done      bool
	lock      sync.Mutex
	terminal  int
}

func (p *ProgressRenderer) truncate(msg string) string {
	width, _, err := termios.GetSize(int(os.Stdout.Fd()))
	if err != nil {
		return msg
	}

	newSize := len(msg)
	if width < newSize {
		return ""
	}

	return msg
}

// Done prints the final status and prevents any update.
func (p *ProgressRenderer) Done(msg string) {
	// Acquire rendering lock
	p.lock.Lock()
	defer p.lock.Unlock()

	// Check if we're already done
	if p.done {
		return
	}

	// Mark this renderer as done
	p.done = true

	// Handle quiet mode
	if p.Quiet {
		msg = ""
	}

	// Truncate msg to terminal length
	msg = p.truncate(msg)

	// If we're not printing a completion message and nothing was printed before just return
	if msg == "" && p.maxLength == 0 {
		return
	}

	// Print the new message
	if msg != "" {
		msg += "\n"
	}

	if len(msg) > p.maxLength {
		p.maxLength = len(msg)
	} else {
		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
	}

	fmt.Print("\r")
	fmt.Print(msg)
}

// Update changes the status message to the provided string.
func (p *ProgressRenderer) Update(status string) {
	// Wait if needed
	timeout := time.Until(p.wait)
	if timeout.Seconds() > 0 {
		time.Sleep(timeout)
	}

	// Acquire rendering lock
	p.lock.Lock()
	defer p.lock.Unlock()

	// Check if we're already done
	if p.done {
		return
	}

	// Handle quiet mode
	if p.Quiet {
		return
	}

	// Skip status updates when not dealing with a terminal
	if p.terminal == 0 {
		if !termios.IsTerminal(int(os.Stdout.Fd())) {
			p.terminal = -1
		}

		p.terminal = 1
	}

	if p.terminal != 1 {
		return
	}

	// Print the new message
	msg := "%s"
	if p.Format != "" {
		msg = p.Format
	}

	msg = fmt.Sprintf(msg, status)

	// Truncate msg to terminal length
	msg = "\r" + p.truncate(msg)

	// Don't print if empty and never printed
	if len(msg) == 1 && p.maxLength == 0 {
		return
	}

	if len(msg) > p.maxLength {
		p.maxLength = len(msg)
	} else {
		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
	}

	fmt.Print(msg)
}

// Warn shows a temporary message instead of the status.
func (p *ProgressRenderer) Warn(status string, timeout time.Duration) {
	// Acquire rendering lock
	p.lock.Lock()
	defer p.lock.Unlock()

	// Check if we're already done
	if p.done {
		return
	}

	// Render the new message
	p.wait = time.Now().Add(timeout)
	msg := status

	// Truncate msg to terminal length
	msg = "\r" + p.truncate(msg)

	// Don't print if empty and never printed
	if len(msg) == 1 && p.maxLength == 0 {
		return
	}

	if len(msg) > p.maxLength {
		p.maxLength = len(msg)
	} else {
		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
	}

	fmt.Print(msg)
}

// UpdateProgress is a helper to update the status using an iopgress instance.
func (p *ProgressRenderer) UpdateProgress(progress ioprogress.ProgressData) {
	p.Update(progress.Text)
}

// UpdateOp is a helper to update the status using a REST API operation.
func (p *ProgressRenderer) UpdateOp(op api.Operation) {
	if op.Metadata == nil {
		return
	}

	for key, value := range op.Metadata {
		if !strings.HasSuffix(key, "_progress") {
			continue
		}

		p.Update(value.(string))
		break
	}
}