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
package progress
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/opt"
)
const (
DefaultInterval = 5 * time.Second
MinBytes = 8 << 20
barWidth = 28
)
func IsTTYWriter(w io.Writer) bool {
f, ok := w.(*os.File)
if !ok {
return false
}
fi, err := f.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}
func Bytes(n int64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
}
units := []string{"KiB", "MiB", "GiB", "TiB", "PiB"}
div, i := float64(unit), 0
for v := float64(n) / unit; v >= unit && i < len(units)-1; v /= unit {
div *= unit
i++
}
return fmt.Sprintf("%.2f %s", float64(n)/div, units[i])
}
func Bar(done, total int64, width int) string {
if total <= 0 {
return Bytes(done)
}
frac := float64(done) / float64(total)
if frac > 1 {
frac = 1
}
filled := int(frac * float64(width))
return fmt.Sprintf("[%s%s] %3d%% %s/%s", strings.Repeat("#", filled),
strings.Repeat(" ", width-filled), int(frac*100), Bytes(done), Bytes(total))
}
func Line(name string, done, total int64, tty bool) string {
if tty {
return name + " " + Bar(done, total, barWidth)
}
return fmt.Sprintf("fetching %s: %s/%s", name, Bytes(done), Bytes(total))
}
func Elapsed(d time.Duration) string { return d.Round(time.Second).String() }
type Progress struct {
name string
total int64
out io.Writer
tty bool
every time.Duration
done int64
rendered int64
last time.Time
quiet bool
finished bool
lastLine string
}
func New(name string, total int64, out io.Writer, tty bool, every time.Duration) *Progress {
every = opt.Positive(every, DefaultInterval)
return &Progress{name: name, total: total, out: out, tty: tty, every: every, last: time.Now(),
quiet: out == nil || total < MinBytes}
}
func (p *Progress) Write(b []byte) (int, error) {
p.done += int64(len(b))
if p.quiet {
return len(b), nil
}
if now := time.Now(); now.Sub(p.last) >= p.every {
p.last = now
p.render()
}
return len(b), nil
}
func (p *Progress) Finish() {
if p.quiet || p.finished {
return
}
p.finished = true
if p.rendered != p.done {
p.render()
}
if p.tty {
fmt.Fprintln(p.out)
}
}
func (p *Progress) render() {
p.rendered = p.done
line := Line(p.name, p.done, p.total, p.tty)
if p.tty {
if n := len(p.lastLine) - len(line); n > 0 {
line += strings.Repeat(" ", n)
}
p.lastLine = line
fmt.Fprintf(p.out, "\r%s", line)
return
}
fmt.Fprintln(p.out, line)
}