-
Notifications
You must be signed in to change notification settings - Fork 3
/
status.go
183 lines (162 loc) · 5.2 KB
/
status.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
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
package swiftlygo
import (
"fmt"
"time"
)
// status monitors the current status of an upload.
type currentStatus struct {
uploadSize uint
totalUploads uint
numberUploaded uint
uploadStarted time.Time
uploadDuration time.Duration
}
// percentComplete computes the percentage of the data that has finished uploading.
func (s *currentStatus) percentComplete() float64 {
if s.totalUploads <= 0 {
return 0.0
}
return float64(s.numberUploaded) / float64(s.totalUploads) * 100
}
// timeRemaining computes the amount of upload time that remains based upon the
// observed upload rate and the amount of data remaining to be uploaded.
func (s *currentStatus) timeRemaining() time.Duration {
finishedIn := int(float64((s.totalUploads-s.numberUploaded)*s.uploadSize) / s.rate())
timeRemaining := time.Duration(finishedIn) * time.Second
return timeRemaining
}
// rate computes the upload rate of the observed upload in bytes per second.
func (s *currentStatus) rate() float64 {
if s.uploadStarted == (time.Time{}) {
return 0.0
} else if s.uploadDuration != (time.Duration(0)) {
return float64(s.totalUploads*s.uploadSize) / float64(s.uploadDuration.Seconds())
}
elapsed := time.Since(s.uploadStarted)
rate := float64(s.numberUploaded*s.uploadSize) / elapsed.Seconds()
return rate
}
// String generates a status message out of the currentStatus struct
func (s *currentStatus) String() string {
if s.uploadStarted == (time.Time{}) {
return "Upload not started yet"
} else if s.uploadDuration != time.Duration(0) {
return fmt.Sprintf(
"Upload finished in %s at approximately %2.2f MB/sec",
s.uploadDuration,
s.rate()/(1000*1000))
}
return fmt.Sprintf(
"[%s] %3.2f%% Uploaded\tAverage Upload Speed %03.2f MB/sec\t%s Remaining",
time.Now(),
s.percentComplete(),
s.rate()/(1000*1000),
s.timeRemaining())
}
type Status struct {
current currentStatus
outputChannel chan string
chunkCompleted chan struct{}
requestStatus chan chan *currentStatus
signalStart chan struct{}
signalStop chan struct{}
}
// NewStatus creates a new Status with the number of individual
// uploads and the size of each upload.
func NewStatus(numberUploads, uploadSize uint, output chan string) *Status {
completed := make(chan struct{})
requestStatus := make(chan chan *currentStatus)
signalStart, signalStop := make(chan struct{}), make(chan struct{})
stat := &Status{
chunkCompleted: completed,
requestStatus: requestStatus,
outputChannel: output,
signalStart: signalStart,
signalStop: signalStop,
current: currentStatus{
uploadSize: uploadSize,
totalUploads: numberUploads,
numberUploaded: 0,
},
}
go func(s *Status) {
for {
select {
case <-s.signalStart:
s.current.uploadStarted = time.Now()
s.signalStart = nil
case <-s.signalStop:
s.current.uploadDuration = time.Since(s.current.uploadStarted)
s.signalStop = nil
case <-s.chunkCompleted:
s.current.numberUploaded++
case sendBack := <-s.requestStatus:
sendBack <- ¤tStatus{
uploadSize: s.current.uploadSize,
totalUploads: s.current.totalUploads,
numberUploaded: s.current.numberUploaded,
uploadStarted: s.current.uploadStarted,
uploadDuration: s.current.uploadDuration,
}
}
}
}(stat)
return stat
}
// Start begins timing the upload. Only call this once.
func (s *Status) Start() {
s.signalStart <- struct{}{}
}
// Stop finalizes the duration of the upload. Only call this once.
func (s *Status) Stop() {
s.signalStop <- struct{}{}
}
// UploadComplete marks that one chunk has been uploaded. Call this
// each time an upload succeeds.
func (s *Status) UploadComplete() {
s.chunkCompleted <- struct{}{}
}
// getCurrent retrieves a pointer to a copy of the current upload status.
func (s *Status) getCurrent() *currentStatus {
stat := make(chan *currentStatus)
defer close(stat)
s.requestStatus <- stat
return <-stat
}
// NumberUploaded returns how many file chunks have been uploaded.
func (s *Status) NumberUploaded() uint {
return s.getCurrent().numberUploaded
}
// TotalUploads returns how many file chunks need to be uploaded total.
func (s *Status) TotalUploads() uint {
return s.getCurrent().totalUploads
}
// UploadSize returns the size of each file chunk (with the exception of the
// last file chunk, which can be any size less than this).
func (s *Status) UploadSize() uint {
return s.getCurrent().uploadSize
}
// Rate computes the observed rate of upload in bytes / second.
func (s *Status) Rate() float64 {
return s.getCurrent().rate()
}
// RateMBPS computes the observed rate of upload in megabytes / second.
func (s *Status) RateMBPS() float64 {
return s.Rate() / 1e6
}
// TimeRemaining estimates the amount of time remaining in the upload.
func (s *Status) TimeRemaining() time.Duration {
return s.getCurrent().timeRemaining()
}
// PercentComplete returns much of the upload is complete.
func (s *Status) PercentComplete() float64 {
return s.getCurrent().percentComplete()
}
// String creates a status message from the current state of the status.
func (s *Status) String() string {
return s.getCurrent().String()
}
// Print sends the current status of the upload to the output channel.
func (s *Status) Print() {
s.outputChannel <- s.String()
}