155 lines
5.3 KiB
Go
155 lines
5.3 KiB
Go
package parts
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/busstate"
|
|
)
|
|
|
|
// BusObjects is a Receiver's Objects over the holder's own object store, asked for in `requires` by local (the
|
|
// template objects, novox/hq ADR 0595 §2), through the runtime: every holder binds the same adapter, none its own.
|
|
func BusObjects(local string) Objects { return busObjects{busstate.ObjectsOf(local)} }
|
|
|
|
type busObjects struct{ o busstate.Objects }
|
|
|
|
func (b busObjects) Put(name string, data []byte) error {
|
|
_, err := b.o.Put(name, data, "")
|
|
return err
|
|
}
|
|
func (b busObjects) Get(name string) ([]byte, error) {
|
|
d, err := b.o.Get(name)
|
|
return d.Data, err
|
|
}
|
|
func (b busObjects) Delete(name string) error { return b.o.Delete(name) }
|
|
func (b busObjects) Used() (uint64, error) {
|
|
f, err := b.o.Fill()
|
|
return f.Bytes, err
|
|
}
|
|
|
|
// BusTransfers is a Receiver's Transfers over the holder's own key-value state of that name (the template state),
|
|
// through the runtime, each transfer under its id.
|
|
func BusTransfers[M any](state string) Transfers[M] { return busTransfers[M]{state} }
|
|
|
|
type busTransfers[M any] struct{ state string }
|
|
|
|
func (b busTransfers[M]) Get(id string) (Transfer[M], bool, error) {
|
|
var t Transfer[M]
|
|
e, err := stdio.State(b.state).Get(id)
|
|
if err != nil || e == nil {
|
|
return t, false, err
|
|
}
|
|
return t, true, json.Unmarshal(e.Value, &t)
|
|
}
|
|
func (b busTransfers[M]) Put(t Transfer[M]) error {
|
|
if t.ID == "" {
|
|
return errors.New("a transfer without an id cannot be kept")
|
|
}
|
|
_, err := stdio.State(b.state).Put(t.ID, t)
|
|
return err
|
|
}
|
|
func (b busTransfers[M]) Delete(id string) error { return stdio.State(b.state).Delete(id) }
|
|
func (b busTransfers[M]) List() ([]Transfer[M], error) {
|
|
keys, err := stdio.State(b.state).Keys()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []Transfer[M]
|
|
for _, k := range keys {
|
|
if t, ok, err := b.Get(k); err == nil && ok {
|
|
out = append(out, t)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// BusCap is a Receiver's Bound over the holder's own object store of that name: the cap its binding carries (the bus's
|
|
// own bound, the store's max bytes), read through the runtime once it answers one, since a binding's cap does not
|
|
// change while the bundle runs. Make it once per bundle, not per receiver.
|
|
func BusCap(local string) func() (uint64, error) {
|
|
var known atomic.Uint64
|
|
return func() (uint64, error) {
|
|
if c := known.Load(); c > 0 {
|
|
return c, nil
|
|
}
|
|
f, err := busstate.ObjectsOf(local).Fill()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s's cap cannot be read from its binding: %w", local, err)
|
|
}
|
|
if f.MaxBytes == 0 {
|
|
return 0, fmt.Errorf("%s's binding carries no cap", local)
|
|
}
|
|
known.Store(f.MaxBytes)
|
|
return f.MaxBytes, nil
|
|
}
|
|
}
|
|
|
|
// BusArm is a Receiver's Arm over the holder's own key-value state of that name, declared key-ttl: one key per open
|
|
// transfer with its own time to live; a key already there is the same bound, kept.
|
|
func BusArm(state string) func(id string, ttl time.Duration) error {
|
|
return func(id string, ttl time.Duration) error {
|
|
if _, err := stdio.State(state).CreateTTL(id, map[string]any{"transfer": id}, ttl); err != nil && !errors.Is(err, stdio.ErrChanged) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// FollowIdle follows the holder's idle state (the one BusArm writes) once the bundle serves, and hands the end of each
|
|
// key (the bus's marker, OpExpire) to expired, a Receiver's Expired: the event drops an idle transfer, no timer runs.
|
|
// Once the watch is taken it hands every transfer open (open, such as IDs of the holder's Transfers) to expired too, so
|
|
// a key that ended while the bundle was down, whose marker no watch saw, still drops its transfer, and a key lost is
|
|
// armed again. said is told each outcome (dropped, or the error that stopped it) for the holder's log. It returns
|
|
// once the start's look is done; run it in its own goroutine.
|
|
func FollowIdle(state string, open func() ([]string, error), expired func(id string) (bool, error), said func(id string, dropped bool, err error)) {
|
|
<-stdio.Served()
|
|
stdio.State(state).Follow("", func(ch stdio.StateChange) error {
|
|
if ch.Op != stdio.OpExpire {
|
|
return nil
|
|
}
|
|
dropped, err := expired(ch.Key)
|
|
if said != nil && (dropped || err != nil) {
|
|
said(ch.Key, dropped, err)
|
|
}
|
|
return err
|
|
})
|
|
expireOpen(state, open, expired, said)
|
|
}
|
|
|
|
// expireOpen is FollowIdle's look at its start: every open transfer handed to expired, as its key's end would.
|
|
func expireOpen(state string, open func() ([]string, error), expired func(id string) (bool, error), said func(id string, dropped bool, err error)) {
|
|
say := func(id string, dropped bool, err error) {
|
|
if said != nil && (dropped || err != nil) {
|
|
said(id, dropped, err)
|
|
}
|
|
}
|
|
ids, err := open()
|
|
if err != nil {
|
|
say("", false, fmt.Errorf("the open transfers cannot be read at the start of %s's watch, so one whose idle key ended while the bundle was down waits for a look by hand: %w", state, err))
|
|
return
|
|
}
|
|
for _, id := range ids {
|
|
dropped, err := expired(id)
|
|
say(id, dropped, err)
|
|
}
|
|
}
|
|
|
|
// IDs answers the ids of the transfers open in t: FollowIdle's open.
|
|
func IDs[M any](t Transfers[M]) func() ([]string, error) {
|
|
return func() ([]string, error) {
|
|
all, err := t.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]string, 0, len(all))
|
|
for _, x := range all {
|
|
out = append(out, x.ID)
|
|
}
|
|
return out, nil
|
|
}
|
|
}
|