ytui/pubsub/pt.go

126 lines
2.3 KiB
Go
Raw Normal View History

2022-06-26 19:30:26 -04:00
package pubsub
import (
"fmt"
"sync"
2022-06-26 23:13:57 -04:00
"time"
2022-06-26 19:30:26 -04:00
)
2022-06-26 23:37:08 -04:00
//go:generate stringer -type=Step
2022-06-26 19:30:26 -04:00
type Step int
const (
NotStarted Step = iota
Publishing
Done
)
type InvalidState struct{ Step Step }
func (i InvalidState) Error() string {
return fmt.Sprintf("Invalid state: %v", i.Step)
}
// One thread publishes progress, one or more threads subscribes to watch the progress
// Subscribers may not get all updates. They will get the latest status when waiting on the channel
type ProgressTracker interface {
// Only one publisher sends update. Should close when done
// Error if there is/was existing publisher
2022-06-27 13:05:00 -04:00
Publish() (chan<- string, error)
2022-06-26 19:30:26 -04:00
// Can subscribe even if there no publisher yet
// If already done, nil channel is returned
// channel will be closed when done
2022-06-27 13:05:00 -04:00
Subscribe() <-chan string
}
type subChan struct {
c chan<- string
lastUpdateIndex int
2022-06-26 19:30:26 -04:00
}
type progressTracker struct {
2022-06-27 13:05:00 -04:00
step Step
m sync.Mutex
sc []subChan
2022-06-26 19:30:26 -04:00
}
func NewProgressTracker() ProgressTracker {
return &progressTracker{
2022-06-27 13:05:00 -04:00
step: NotStarted,
2022-06-26 19:30:26 -04:00
}
}
2022-06-27 13:05:00 -04:00
func (pt *progressTracker) Publish() (chan<- string, error) {
err := func() error {
2022-06-26 19:30:26 -04:00
pt.m.Lock()
defer pt.m.Unlock()
2022-06-27 13:05:00 -04:00
if pt.step != NotStarted {
return InvalidState{pt.step}
2022-06-26 19:30:26 -04:00
}
2022-06-27 13:05:00 -04:00
pt.step = Publishing
2022-06-26 19:30:26 -04:00
return nil
}()
2022-06-27 13:05:00 -04:00
2022-06-26 19:30:26 -04:00
if err != nil {
return nil, err
}
2022-06-26 22:34:53 -04:00
prodChan := make(chan string, 100)
2022-06-26 19:30:26 -04:00
go func() {
2022-06-26 22:34:53 -04:00
var update string
prodChanOpen := true
2022-06-26 23:13:57 -04:00
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
2022-06-27 11:47:54 -04:00
lastUpdateIndex := 0
2022-06-26 23:13:57 -04:00
for range ticker.C {
2022-06-27 11:47:54 -04:00
LoopReader:
for prodChanOpen {
2022-06-26 19:30:26 -04:00
select {
2022-06-26 22:34:53 -04:00
case update, prodChanOpen = <-prodChan:
2022-06-27 11:47:54 -04:00
lastUpdateIndex++
2022-06-26 19:30:26 -04:00
default:
2022-06-27 11:47:54 -04:00
break LoopReader
2022-06-26 19:30:26 -04:00
}
}
2022-06-27 11:47:54 -04:00
var scs []subChan
2022-06-26 19:30:26 -04:00
func() {
2022-06-27 13:05:00 -04:00
pt.m.Lock()
defer pt.m.Unlock()
scs = pt.sc
2022-06-26 22:34:53 -04:00
if !prodChanOpen {
2022-06-27 13:05:00 -04:00
pt.step = Done
2022-06-26 19:30:26 -04:00
}
}()
2022-06-26 22:34:53 -04:00
if !prodChanOpen {
2022-06-27 13:05:00 -04:00
for _, subChan := range scs {
close(subChan.c)
}
2022-06-26 19:30:26 -04:00
return
}
2022-06-26 22:34:53 -04:00
for _, subChan := range scs {
2022-06-27 11:47:54 -04:00
if subChan.lastUpdateIndex != lastUpdateIndex {
select {
case subChan.c <- update:
subChan.lastUpdateIndex = lastUpdateIndex
default:
}
2022-06-26 19:30:26 -04:00
}
}
}
}()
2022-06-26 22:34:53 -04:00
return prodChan, nil
2022-06-26 19:30:26 -04:00
}
2022-06-27 13:05:00 -04:00
func (pt *progressTracker) Subscribe() <-chan string {
2022-06-26 22:34:53 -04:00
c := make(chan string, 1)
2022-06-27 11:47:54 -04:00
sc := subChan{c: c}
2022-06-27 13:05:00 -04:00
pt.m.Lock()
defer pt.m.Unlock()
if pt.step == Done {
2022-06-26 19:30:26 -04:00
return nil
}
2022-06-27 13:05:00 -04:00
pt.sc = append(pt.sc, sc)
2022-06-26 19:30:26 -04:00
return c
}