feat(sense): establish M1 offline intake skeleton
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled

This commit is contained in:
QiuSW
2026-08-04 15:45:50 +08:00
parent bafd8d983e
commit 5208891e02
36 changed files with 17728 additions and 22 deletions
+79
View File
@@ -0,0 +1,79 @@
// Package probe maps MediaMTX runtime path health into the Sense actual state.
package probe
import (
"context"
"errors"
"fmt"
"time"
"yovision/sense/internal/device"
)
const defaultBatchSize = 128
type Repository interface {
ListEnabledVideoDevices(ctx context.Context, limit int) ([]device.Device, error)
UpdateActualState(ctx context.Context, id string, state device.ActualState, now time.Time) error
}
type RuntimePaths interface {
PathReady(ctx context.Context, name string) (bool, error)
}
type Checker struct {
repository Repository
media RuntimePaths
now func() time.Time
batchSize int
}
func New(repository Repository, media RuntimePaths) *Checker {
return &Checker{repository: repository, media: media, now: time.Now, batchSize: defaultBatchSize}
}
func (c *Checker) RunOnce(ctx context.Context) error {
devices, err := c.repository.ListEnabledVideoDevices(ctx, c.batchSize)
if err != nil {
return fmt.Errorf("list probe candidates: %w", err)
}
var runErrors []error
for _, value := range devices {
if err := ctx.Err(); err != nil {
return err
}
ready, probeErr := c.media.PathReady(ctx, value.PathName)
if ctx.Err() != nil {
return ctx.Err()
}
state := device.ActualOffline
if probeErr == nil && ready {
state = device.ActualOnline
}
if updateErr := c.repository.UpdateActualState(ctx, value.ID, state, c.now().UTC()); updateErr != nil {
runErrors = append(runErrors, fmt.Errorf("update device %s health: %w", value.ID, updateErr))
}
if probeErr != nil {
runErrors = append(runErrors, fmt.Errorf("probe device %s: %w", value.ID, probeErr))
}
}
return errors.Join(runErrors...)
}
func (c *Checker) Run(ctx context.Context, interval time.Duration, report func(error)) {
if err := c.RunOnce(ctx); err != nil && ctx.Err() == nil && report != nil {
report(err)
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := c.RunOnce(ctx); err != nil && ctx.Err() == nil && report != nil {
report(err)
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
package probe
import (
"context"
"errors"
"testing"
"time"
"yovision/sense/internal/device"
)
type fakeRepository struct {
devices []device.Device
states map[string]device.ActualState
}
func (f *fakeRepository) ListEnabledVideoDevices(context.Context, int) ([]device.Device, error) {
return f.devices, nil
}
func (f *fakeRepository) UpdateActualState(_ context.Context, id string, state device.ActualState, _ time.Time) error {
if f.states == nil {
f.states = make(map[string]device.ActualState)
}
f.states[id] = state
return nil
}
type fakeRuntime struct {
ready map[string]bool
errors map[string]error
}
func (f fakeRuntime) PathReady(_ context.Context, name string) (bool, error) {
return f.ready[name], f.errors[name]
}
func TestCheckerMapsReadyAndUnavailablePaths(t *testing.T) {
t.Parallel()
repository := &fakeRepository{devices: []device.Device{
{ID: "online", PathName: "online"}, {ID: "offline", PathName: "offline"},
}}
checker := New(repository, fakeRuntime{
ready: map[string]bool{"online": true}, errors: map[string]error{"offline": errors.New("unavailable")},
})
err := checker.RunOnce(context.Background())
if err == nil {
t.Fatal("probe transport error must remain observable")
}
if repository.states["online"] != device.ActualOnline || repository.states["offline"] != device.ActualOffline {
t.Fatalf("unexpected actual states: %+v", repository.states)
}
}