74 lines
2.9 KiB
Go
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
|
|
}
|