summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-12-14 00:28:43 -0700
committerLuke Shumaker <lukeshu@lukeshu.com>2022-12-20 20:03:44 -0700
commit1674500388d1e0837103a870f0b96f1f1dab2206 (patch)
tree67c828d6d39bf864d6063f5a6c558f681e46583b
parentc93fbf4918b18606e50385c577ee9d6be701f370 (diff)
textui: Implement a reusable progress module
To replace all of the ad-hoc hacks
-rw-r--r--lib/textui/progress.go88
1 files changed, 88 insertions, 0 deletions
diff --git a/lib/textui/progress.go b/lib/textui/progress.go
new file mode 100644
index 0000000..7b3f63a
--- /dev/null
+++ b/lib/textui/progress.go
@@ -0,0 +1,88 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package textui
+
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+ "time"
+
+ "github.com/datawire/dlib/dlog"
+)
+
+type Stats interface {
+ comparable
+ fmt.Stringer
+}
+
+type Progress[T Stats] struct {
+ ctx context.Context
+ lvl dlog.LogLevel
+ interval time.Duration
+
+ cancel context.CancelFunc
+ done chan struct{}
+
+ cur atomic.Value // Value[T]
+ oldStat T
+ oldLine string
+}
+
+func NewProgress[T Stats](ctx context.Context, lvl dlog.LogLevel, interval time.Duration) *Progress[T] {
+ ctx, cancel := context.WithCancel(ctx)
+ ret := &Progress[T]{
+ ctx: ctx,
+ lvl: lvl,
+ interval: interval,
+
+ cancel: cancel,
+ done: make(chan struct{}),
+ }
+ return ret
+}
+
+func (p *Progress[T]) Set(val T) {
+ if p.cur.Swap(val) == nil {
+ go p.run()
+ }
+}
+
+func (p *Progress[T]) Done() {
+ p.cancel()
+ <-p.done
+}
+
+func (p *Progress[T]) flush(force bool) {
+ cur := p.cur.Load().(T)
+ if !force && cur == p.oldStat {
+ return
+ }
+ defer func() { p.oldStat = cur }()
+
+ line := cur.String()
+ if !force && line == p.oldLine {
+ return
+ }
+ defer func() { p.oldLine = line }()
+
+ dlog.Log(p.ctx, p.lvl, line)
+}
+
+func (p *Progress[T]) run() {
+ p.flush(true)
+ ticker := time.NewTicker(p.interval)
+ for {
+ select {
+ case <-p.ctx.Done():
+ ticker.Stop()
+ p.flush(false)
+ close(p.done)
+ return
+ case <-ticker.C:
+ p.flush(false)
+ }
+ }
+}