Files
mesh-tools/node-tools/cmd/node-tools/seatlist.go
T

74 lines
2.9 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatdef"
"git.novox.be/novox/mesh-sdk/go/seatlist"
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
// The tool runner reads the seat list as every part does (novox/hq ADR 0635 §1, ADR 0636): through the seat mesh-catalog,
// from the bootstrapped definitions until it has been read, and following the seat's events. What it holds is given to
// catalogue at every change; the console describes the seats from it, so the descriptions are the mesh's own and not a
// list compiled into the runner.
// busOfTheRunner is the reader's bus over the runner's connection: a verb of the seat asked on its subject, an event heard
// on the seat's event subject.
type busOfTheRunner struct{ conn *bus.Conn }
func (b busOfTheRunner) Ask(key string, body any) (json.RawMessage, error) {
seat, verb, _ := strings.Cut(strings.TrimPrefix(key, "seat:"), ".")
answered, err := b.conn.Ask(key, body, "mesh.seat."+seat+".tool."+verb)
if err != nil {
return nil, err
}
return answered.Result, nil
}
func (b busOfTheRunner) Subscribe(pattern string, handle func(string, json.RawMessage) error) error {
seat, event, _ := strings.Cut(pattern, ".")
_, err := b.conn.Raw(seatdef.EventSubject(seat, event), func(_ string, data []byte) []byte {
_ = handle(pattern, data)
return nil // an event is answered by nobody
})
return err
}
// seatListWithin is how long the runner waits for the first read of the seat list before it serves; the read goes on
// after it, and the descriptions are the bootstrapped ones until it ends (ADR 0636 §1).
const seatListWithin = 5 * time.Second
// startSeatList starts the runner's reader and gives catalogue its bootstrapped seats at once, then waits for the first
// read to end (the reader's Ready) for the bound, so the runner's first descriptions are of the list in the common case.
func startSeatList(conn *bus.Conn) *seatlist.Reader {
timer := time.NewTimer(seatListWithin)
defer timer.Stop()
return startSeatListOn(busOfTheRunner{conn}, timer.C, log.Printf)
}
// startSeatListOn is startSeatList over any bus, with the bound as a channel the caller gives.
func startSeatListOn(b seatlist.Bus, bound <-chan time.Time, logf func(string, ...any)) *seatlist.Reader {
reader, err := seatlist.Start(seatlist.Config{Bus: b, Bootstrap: seatlist.Bootstrapped(),
OnChange: func(seats []catalogue.Seat) { catalogue.UseSeats(seats) }})
if err != nil {
logf("node-tools: %v", fmt.Errorf("the tool runner cannot follow the seat list: %w", err))
}
if reader == nil {
return nil
}
catalogue.UseSeats(reader.Seats())
select {
case <-reader.Ready():
catalogue.UseSeats(reader.Seats())
case <-bound:
logf("node-tools: the seat list is not read yet; the runner serves with the bootstrapped seats and reads on")
}
return reader
}