103 lines
3.4 KiB
Go
103 lines
3.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"yovision/sense/internal/device"
|
|
)
|
|
|
|
const (
|
|
DriverSQLite = "sqlite"
|
|
DriverPostgres = "postgres"
|
|
)
|
|
|
|
var (
|
|
ErrQuotaProjectionUnavailable = errors.New("quota projection unavailable")
|
|
ErrQuotaProjectionInvalid = errors.New("quota projection invalid")
|
|
ErrAreaPolicyUnavailable = errors.New("area policy unavailable")
|
|
ErrAreaPolicyInvalid = errors.New("area policy invalid")
|
|
ErrAreaPolicyDenied = errors.New("area policy denies imaging device")
|
|
ErrReconcileLeaseLost = errors.New("reconcile lease lost")
|
|
ErrOperationalLeaseLost = errors.New("operational lease lost")
|
|
)
|
|
|
|
// Repository is the storage boundary used by the Sense process. SQLite stays
|
|
// available for M1 development; PostgreSQL implements the M2 production path.
|
|
type Repository interface {
|
|
Close() error
|
|
CreateDevice(context.Context, device.Device) error
|
|
SetDesiredState(context.Context, string, device.DesiredState) error
|
|
GetDevice(context.Context, string) (device.Device, error)
|
|
ListDueReconcile(context.Context, time.Time, int) ([]ReconcileCandidate, error)
|
|
ClaimDueReconcile(context.Context, ReconcileClaim) ([]ReconcileCandidate, error)
|
|
RenewReconcileLease(context.Context, string, string, string, time.Time, time.Duration) (bool, error)
|
|
CompleteReconcile(context.Context, string, int64, string, string, time.Time) error
|
|
FailReconcile(context.Context, string, int, time.Time, string, string, string, time.Time) error
|
|
ListEnabledVideoDevices(context.Context, int) ([]device.Device, error)
|
|
MarkReconciled(context.Context, string, int64, time.Time) error
|
|
MarkReconcileFailure(context.Context, string, int, time.Time, string, time.Time) error
|
|
UpdateActualState(context.Context, string, device.ActualState, time.Time) error
|
|
RequestReconcile(context.Context, string, time.Time) error
|
|
ConvergenceSnapshot(context.Context) (ConvergenceSnapshot, error)
|
|
}
|
|
|
|
// ReconcileClaim identifies one short-lived batch claim. Token is unique per
|
|
// run and fences a worker whose lease expired and was acquired by another
|
|
// process. SQLite accepts the shape but remains explicitly single-process.
|
|
type ReconcileClaim struct {
|
|
Owner string
|
|
Token string
|
|
Now time.Time
|
|
LeaseDuration time.Duration
|
|
Limit int
|
|
}
|
|
|
|
func OpenRepository(ctx context.Context, driver, dsn string) (Repository, error) {
|
|
switch strings.ToLower(strings.TrimSpace(driver)) {
|
|
case "", DriverSQLite:
|
|
return OpenSQLite(ctx, dsn)
|
|
case DriverPostgres:
|
|
return OpenPostgres(ctx, dsn)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported database driver %q", driver)
|
|
}
|
|
}
|
|
|
|
type quotaProjectionError struct {
|
|
kind error
|
|
}
|
|
|
|
func (e *quotaProjectionError) Error() string { return e.kind.Error() }
|
|
func (e *quotaProjectionError) Unwrap() error { return e.kind }
|
|
|
|
func projectionUnavailable() error {
|
|
return "aProjectionError{kind: ErrQuotaProjectionUnavailable}
|
|
}
|
|
|
|
func projectionInvalid() error {
|
|
return "aProjectionError{kind: ErrQuotaProjectionInvalid}
|
|
}
|
|
|
|
type areaPolicyError struct {
|
|
kind error
|
|
}
|
|
|
|
func (e *areaPolicyError) Error() string { return e.kind.Error() }
|
|
func (e *areaPolicyError) Unwrap() error { return e.kind }
|
|
|
|
func areaPolicyUnavailable() error {
|
|
return &areaPolicyError{kind: ErrAreaPolicyUnavailable}
|
|
}
|
|
|
|
func areaPolicyInvalid() error {
|
|
return &areaPolicyError{kind: ErrAreaPolicyInvalid}
|
|
}
|
|
|
|
func areaPolicyDenied() error {
|
|
return &areaPolicyError{kind: ErrAreaPolicyDenied}
|
|
}
|