cmd/streamdata: simple progress indicator
For downloading stream VODs, since they are pretty large. Signed-off-by: Yonah <contrib@gensokyo.uk>
This commit is contained in:
90
cmd/streamdata/progress.go
Normal file
90
cmd/streamdata/progress.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/ext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pendingMessage is written to the current line every tick if non-nil.
|
||||||
|
var pendingMessage atomic.Pointer[string]
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ticker := time.NewTicker(time.Second / 100)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
|
||||||
|
p := pendingMessage.Swap(nil)
|
||||||
|
if p == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString("\x1b[2K" + *p + "\r")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// progressWriter writes a progress bar via writeCurrent.
|
||||||
|
type progressWriter struct {
|
||||||
|
// For progress indication. Has no effect unless greater than zero.
|
||||||
|
contentLength int64
|
||||||
|
// Accumulated bytes.
|
||||||
|
n int64
|
||||||
|
|
||||||
|
// Increments with n, but resets periodically.
|
||||||
|
segment int64
|
||||||
|
// Reference time since segment was reset.
|
||||||
|
ref time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureInterval is the interval at which progressWriter.segment is reset.
|
||||||
|
const measureInterval = 5 * time.Second
|
||||||
|
|
||||||
|
func (pw *progressWriter) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
pw.n += int64(n)
|
||||||
|
pw.segment += int64(n)
|
||||||
|
d := time.Now().Sub(pw.ref)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
if pw.contentLength > 0 {
|
||||||
|
s += " " + strconv.FormatInt(pw.n/(1<<20), 10) + " / " +
|
||||||
|
strconv.FormatInt(pw.contentLength/(1<<20), 10) + " MiB |"
|
||||||
|
}
|
||||||
|
s += " " + strconv.FormatFloat(
|
||||||
|
float64(pw.segment)/d.Seconds()/float64(1<<20),
|
||||||
|
'f', 2, 64,
|
||||||
|
) + " MiB/s"
|
||||||
|
pendingMessage.Store(&s)
|
||||||
|
|
||||||
|
if d > measureInterval {
|
||||||
|
pw.segment = 0
|
||||||
|
pw.ref = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyProgress is like [io.Copy], but prints progress to [os.Stderr] if it is
|
||||||
|
// attached to a terminal.
|
||||||
|
//
|
||||||
|
// Percentage is emitted if contentLength is greater than zero.
|
||||||
|
func copyProgress(dst io.Writer, src io.Reader, contentLength int64) (
|
||||||
|
written int64,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
if !ext.Isatty(syscall.Stderr) {
|
||||||
|
return io.Copy(dst, src)
|
||||||
|
}
|
||||||
|
defer os.Stderr.WriteString("\n")
|
||||||
|
return io.Copy(io.MultiWriter(dst, &progressWriter{
|
||||||
|
contentLength: contentLength,
|
||||||
|
|
||||||
|
ref: time.Now(),
|
||||||
|
}), src)
|
||||||
|
}
|
||||||
4
go.mod
4
go.mod
@@ -1,3 +1,5 @@
|
|||||||
module git.gensokyo.uk/yonah/streamdata
|
module git.gensokyo.uk/yonah/streamdata
|
||||||
|
|
||||||
go 1.25.7
|
go 1.26
|
||||||
|
|
||||||
|
require hakurei.app v0.3.8-0.20260317115839-6cdb6a652b63
|
||||||
|
|||||||
Reference in New Issue
Block a user