-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathprogress.go
145 lines (135 loc) · 3.7 KB
/
progress.go
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
// Package progress provides io.Reader and io.Writer with progress and remaining time estimation.
// ctx := context.Background()
//
// // get a reader and the total expected number of bytes
// s := `Now that's what I call progress`
// size := len(s)
// r := progress.NewReader(strings.NewReader(s))
//
// // Start a goroutine printing progress
// go func() {
// ctx := context.Background()
// progressChan := progress.NewTicker(ctx, r, size, 1*time.Second)
// for p := range progressChan {
// fmt.Printf("\r%v remaining...", p.Remaining().Round(time.Second))
// }
// fmt.Println("\rdownload is completed")
// }()
//
// // use the Reader as normal
// if _, err := io.Copy(dest, r); err != nil {
// log.Fatalln(err)
// }
package progress
import (
"context"
"io"
"time"
)
// Counter counts bytes.
// Both Reader and Writer are Counter types.
type Counter interface {
// N gets the current count value.
// For readers and writers, this is the number of bytes
// read or written.
// For other contexts, the number may be anything.
N() int64
// Err gets the last error from the Reader or Writer.
// When the process is finished, this will be io.EOF.
Err() error
}
// Progress represents a moment of progress.
type Progress struct {
n float64
size float64
estimated time.Time
err error
}
// N gets the total number of bytes read or written
// so far.
func (p Progress) N() int64 {
return int64(p.n)
}
// Size gets the total number of bytes that are expected to
// be read or written.
func (p Progress) Size() int64 {
return int64(p.size)
}
// Complete gets whether the operation is complete or not.
// Always returns false if the Size is unknown (-1).
func (p Progress) Complete() bool {
if p.err == io.EOF {
return true
}
if p.size == -1 {
return false
}
return p.n >= p.size
}
// Percent calculates the percentage complete.
func (p Progress) Percent() float64 {
if p.n == 0 {
return 0
}
if p.n >= p.size {
return 100
}
return 100.0 / (p.size / p.n)
}
// Remaining gets the amount of time until the operation is
// expected to be finished. Use Estimated to get a fixed completion time.
// Returns -1 if no estimate is available.
func (p Progress) Remaining() time.Duration {
if p.estimated.IsZero() {
return -1
}
return time.Until(p.estimated)
}
// Estimated gets the time at which the operation is expected
// to finish. Use Remaining to get a Duration.
// Estimated().IsZero() is true if no estimate is available.
func (p Progress) Estimated() time.Time {
return p.estimated
}
// NewTicker gets a channel on which ticks of Progress are sent
// at duration d intervals until the operation is complete at which point
// the channel is closed.
// The counter is either a Reader or Writer (or any type that can report its progress).
// The size is the total number of expected bytes being read or written.
// If the context cancels the operation, the channel is closed.
func NewTicker(ctx context.Context, counter Counter, size int64, d time.Duration) <-chan Progress {
var (
started = time.Now()
ch = make(chan Progress)
)
go func() {
defer close(ch)
for {
select {
case <-ctx.Done():
// context has finished - exit
return
case <-time.After(d):
progress := Progress{
n: float64(counter.N()),
size: float64(size),
err: counter.Err(),
}
ratio := progress.n / progress.size
past := float64(time.Since(started))
if progress.n > 0.0 {
total := time.Duration(past / ratio)
if total < 168*time.Hour {
// don't send estimates that are beyond a week
progress.estimated = started.Add(total)
}
}
ch <- progress
if progress.Complete() {
return
}
}
}
}()
return ch
}