storage

package
v0.1.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package storage persists GopherTrunk's runtime data to disk.

The default backend is SQLite via the pure-Go `modernc.org/sqlite` driver — CGO_ENABLED=0 stays true across the daemon and the daemon cross-compiles to linux/arm64 without toolchain gymnastics.

Layout:

sqlite.go     Open + schema migrations. One-shot at startup.
calllog.go    CallLog: subscribes to events.KindCallStart /
              KindCallEnd from the trunking engine, writes rows
              keyed by (device serial, started_at).
retention.go  Background sweeper that deletes DB rows + the WAV /
              raw files written by internal/voice older than a
              configurable cutoff.

The API's /api/v1/calls/history endpoint reads through the `History` query helpers exposed here. There is no gRPC call-log service today (history is REST only).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CallLog

type CallLog struct {
	// contains filtered or unexported fields
}

CallLog persists trunking calls to the SQLite call_log table by subscribing to events.KindCallStart and events.KindCallEnd on the shared events bus.

Rows are keyed by (device_serial, started_at). On CallStart we INSERT with a NULL ended_at; on CallEnd we UPDATE the matching row with the ended_at, duration, and end-reason. The unique index in the schema keeps duplicate-start events idempotent.

func NewCallLog

func NewCallLog(db *DB, bus *events.Bus, logger *slog.Logger) (*CallLog, error)

NewCallLog wires the call log to the bus. It subscribes immediately so callers can publish events before Run is called.

func (*CallLog) Close

func (c *CallLog) Close() error

Close releases the bus subscription and waits for Run to drain.

func (*CallLog) Run

func (c *CallLog) Run(ctx context.Context) error

Run drains call.start / call.end events until ctx cancels.

type CallRow

type CallRow struct {
	ID             int64     `json:"id"`
	System         string    `json:"system"`
	Protocol       string    `json:"protocol"`
	GroupID        uint32    `json:"group_id"`
	SourceID       uint32    `json:"source_id"`
	FrequencyHz    uint32    `json:"frequency_hz"`
	Encrypted      bool      `json:"encrypted"`
	AlgorithmID    uint8     `json:"algorithm_id"`
	KeyID          uint16    `json:"key_id"`
	Emergency      bool      `json:"emergency"`
	DataCall       bool      `json:"data_call"`
	DeviceSerial   string    `json:"device_serial"`
	StartedAt      time.Time `json:"started_at"`
	EndedAt        time.Time `json:"ended_at,omitempty"` // zero if call still active
	DurationMs     int64     `json:"duration_ms,omitempty"`
	EndReason      string    `json:"end_reason,omitempty"`
	TalkgroupAlpha string    `json:"talkgroup_alpha,omitempty"`
}

CallRow is one row from the call_log table.

type DB

type DB struct {
	// contains filtered or unexported fields
}

DB is a thin wrapper over *sql.DB that lets the call-log + retention helpers share a typed handle. The schema is migrated on Open.

func Open

func Open(path string) (*DB, error)

Open creates (or opens) a SQLite database at path and applies the embedded schema migrations. The path's parent directory is created if missing.

`:memory:` and the standard "file:..." DSN forms are passed through to the driver — useful for tests.

func (*DB) Close

func (d *DB) Close() error

Close releases the connection.

func (*DB) History

func (d *DB) History(ctx context.Context, f HistoryFilter) ([]CallRow, error)

History queries the call_log with the supplied filter, newest-first.

func (*DB) SQL

func (d *DB) SQL() *sql.DB

SQL returns the underlying *sql.DB. Exposed so tests and future integrations (an /api/v1/calls/history handler, etc.) can run their own queries without adding a method here for every shape.

type HistoryFilter

type HistoryFilter struct {
	System    string
	GroupID   uint32 // 0 = no filter
	Since     time.Time
	Until     time.Time
	Limit     int
	OnlyEnded bool
}

HistoryFilter narrows a History query.

type LocationLog added in v0.1.9

type LocationLog struct {
	// contains filtered or unexported fields
}

LocationLog persists geographic fixes to the SQLite location_log table by subscribing to events.KindLocation on the shared bus.

func NewLocationLog added in v0.1.9

func NewLocationLog(db *DB, bus *events.Bus, logger *slog.Logger) (*LocationLog, error)

NewLocationLog wires the location log to the bus. It subscribes immediately so callers can publish before Run starts.

func (*LocationLog) Close added in v0.1.9

func (l *LocationLog) Close() error

Close releases the bus subscription and waits for Run to drain.

func (*LocationLog) Recent added in v0.1.9

func (l *LocationLog) Recent(limit int) ([]LocationRow, error)

Recent returns the most recent fixes, newest first, capped at limit.

func (*LocationLog) Run added in v0.1.9

func (l *LocationLog) Run(ctx context.Context) error

Run drains KindLocation events until ctx cancels or the bus closes.

type LocationRow added in v0.1.9

type LocationRow struct {
	ID         int64     `json:"id"`
	System     string    `json:"system"`
	Protocol   string    `json:"protocol"`
	RadioID    uint32    `json:"radio_id"`
	Talkgroup  uint32    `json:"talkgroup"`
	Latitude   float64   `json:"latitude"`
	Longitude  float64   `json:"longitude"`
	SpeedKnots float64   `json:"speed_knots"`
	HeadingDeg float64   `json:"heading_deg"`
	ReportedAt time.Time `json:"reported_at"`
}

LocationRow is one persisted fix, returned by Recent.

type Retention

type Retention struct {
	// contains filtered or unexported fields
}

Retention deletes old data on a schedule:

  1. call_log rows with started_at older than CallRowMaxAge.
  2. WAV / raw files under FilesRoot whose modification time is older than FilesMaxAge.

File deletion is opt-in by setting FilesRoot; an empty value skips the filesystem sweep. The sweeper is idempotent and safe to run concurrently with the call-log writer (SQLite serialises).

func NewRetention

func NewRetention(opts RetentionOptions) (*Retention, error)

func (*Retention) Run

func (r *Retention) Run(ctx context.Context) error

Run sweeps once at startup and then every Interval until ctx cancels.

func (*Retention) SweepOnce

func (r *Retention) SweepOnce(ctx context.Context)

SweepOnce runs the configured deletions. Errors are logged and swallowed so a transient FS or DB problem doesn't kill the loop.

type RetentionOptions

type RetentionOptions struct {
	DB *DB
	// FilesRoot is the directory the voice recorder writes WAV / raw
	// files under. Empty disables the filesystem sweep.
	FilesRoot string
	// CallRowMaxAge: rows with started_at older than this are deleted.
	// Zero (the default) disables row deletion.
	CallRowMaxAge time.Duration
	// FilesMaxAge: files older than this (mtime) are deleted. Zero
	// disables file deletion.
	FilesMaxAge time.Duration
	// Interval between sweeps. Default 1 h.
	Interval time.Duration
	Log      *slog.Logger
}

RetentionOptions configure a Retention sweeper.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL