2024-11-20 19:59:24 -05:00
|
|
|
package tgstore
|
2024-10-31 16:14:38 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-11-03 09:45:51 -05:00
|
|
|
"errors"
|
2024-11-20 07:26:59 -05:00
|
|
|
"strings"
|
2024-11-02 11:39:02 -04:00
|
|
|
"sync"
|
|
|
|
"time"
|
2024-10-31 16:14:38 -04:00
|
|
|
|
2024-11-20 07:26:59 -05:00
|
|
|
"dynatron.me/x/stillbox/internal/common"
|
2024-11-20 19:15:26 -05:00
|
|
|
"dynatron.me/x/stillbox/pkg/auth"
|
2024-11-21 13:23:21 -05:00
|
|
|
"dynatron.me/x/stillbox/pkg/calls"
|
2024-11-03 19:11:45 -05:00
|
|
|
"dynatron.me/x/stillbox/pkg/config"
|
2024-11-03 07:19:03 -05:00
|
|
|
"dynatron.me/x/stillbox/pkg/database"
|
2024-11-20 19:59:24 -05:00
|
|
|
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
2024-11-02 09:41:48 -04:00
|
|
|
|
2024-11-02 11:39:02 -04:00
|
|
|
"github.com/jackc/pgx/v5"
|
2024-11-02 09:41:48 -04:00
|
|
|
"github.com/rs/zerolog/log"
|
2024-10-31 16:14:38 -04:00
|
|
|
)
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
type tgMap map[tgsp.ID]*tgsp.Talkgroup
|
2024-10-31 16:14:38 -04:00
|
|
|
|
2024-11-10 10:13:38 -05:00
|
|
|
var (
|
2024-11-24 00:06:43 -05:00
|
|
|
ErrNotFound = errors.New("talkgroup not found")
|
|
|
|
ErrNoSuchSystem = errors.New("no such system")
|
|
|
|
ErrInvalidOrderBy = errors.New("invalid pagination orderBy value")
|
2024-12-10 19:10:25 -05:00
|
|
|
ErrReference = errors.New("item is still referenced, cannot delete")
|
2024-11-10 10:13:38 -05:00
|
|
|
)
|
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
type Store interface {
|
2024-11-10 10:13:38 -05:00
|
|
|
// UpdateTG updates a talkgroup record.
|
2024-11-20 19:59:24 -05:00
|
|
|
UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error)
|
2024-11-10 10:13:38 -05:00
|
|
|
|
2024-11-20 07:26:59 -05:00
|
|
|
// UpsertTGs upserts a slice of talkgroups.
|
2024-11-20 19:59:24 -05:00
|
|
|
UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error)
|
2024-11-20 07:26:59 -05:00
|
|
|
|
2024-12-10 19:10:25 -05:00
|
|
|
// CreateSystem creates a new system with the specified name and ID.
|
|
|
|
CreateSystem(ctx context.Context, id int, name string) error
|
|
|
|
|
2024-11-03 09:45:51 -05:00
|
|
|
// TG retrieves a Talkgroup from the Store.
|
2024-11-20 19:59:24 -05:00
|
|
|
TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error)
|
2024-11-04 11:15:24 -05:00
|
|
|
|
|
|
|
// TGs retrieves many talkgroups from the Store.
|
2024-11-24 00:06:43 -05:00
|
|
|
TGs(ctx context.Context, tgs tgsp.IDs, opts ...option) ([]*tgsp.Talkgroup, error)
|
2024-11-02 09:41:48 -04:00
|
|
|
|
2024-11-20 20:13:18 -05:00
|
|
|
// LearnTG learns the talkgroup from a Call.
|
2024-11-20 22:13:23 -05:00
|
|
|
LearnTG(ctx context.Context, call *calls.Call) (*tgsp.Talkgroup, error)
|
2024-11-20 20:13:18 -05:00
|
|
|
|
2024-11-04 23:41:52 -05:00
|
|
|
// SystemTGs retrieves all Talkgroups associated with a System.
|
2024-12-10 19:10:25 -05:00
|
|
|
SystemTGs(ctx context.Context, systemID int, opts ...option) ([]*tgsp.Talkgroup, error)
|
|
|
|
|
|
|
|
// DeleteTG deletes a talkgroup record.
|
|
|
|
DeleteTG(ctx context.Context, id tgsp.ID) error
|
|
|
|
|
|
|
|
// DeleteSystem deletes a system. The system must have no talkgroups or incidents.
|
|
|
|
DeleteSystem(ctx context.Context, id int) error
|
2024-11-04 23:41:52 -05:00
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
// SystemName retrieves a system name from the store. It returns the record and whether one was found.
|
|
|
|
SystemName(ctx context.Context, id int) (string, bool)
|
2024-10-31 16:14:38 -04:00
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
// Hint hints the Store that the provided talkgroups will be asked for.
|
2024-11-20 19:59:24 -05:00
|
|
|
Hint(ctx context.Context, tgs []tgsp.ID) error
|
2024-11-02 11:39:02 -04:00
|
|
|
|
2024-11-15 10:37:58 -05:00
|
|
|
// Load loads the provided talkgroup ID tuples into the Store.
|
|
|
|
Load(ctx context.Context, tgs database.TGTuples) error
|
2024-11-03 08:09:49 -05:00
|
|
|
|
|
|
|
// Invalidate invalidates any caching in the Store.
|
2024-11-02 11:39:02 -04:00
|
|
|
Invalidate()
|
2024-11-03 19:11:45 -05:00
|
|
|
|
2024-11-04 11:48:31 -05:00
|
|
|
// Weight returns the final weight of this talkgroup, including its static and rules-derived weight.
|
2024-11-20 19:59:24 -05:00
|
|
|
Weight(ctx context.Context, id tgsp.ID, t time.Time) float64
|
2024-11-03 19:11:45 -05:00
|
|
|
|
|
|
|
// Hupper
|
|
|
|
HUP(*config.Config)
|
2024-11-02 11:39:02 -04:00
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
type options struct {
|
|
|
|
pagination *Pagination
|
2024-11-25 19:06:56 -05:00
|
|
|
totalDest *int
|
2024-11-24 00:06:43 -05:00
|
|
|
perPageDefault int
|
|
|
|
}
|
|
|
|
|
|
|
|
func sOpt(opts []option) (o options) {
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(&o)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type option func(*options)
|
|
|
|
|
2024-11-25 19:06:56 -05:00
|
|
|
func WithPagination(p *Pagination, defPerPage int, totalDest *int) option {
|
2024-11-24 00:06:43 -05:00
|
|
|
return func(o *options) {
|
|
|
|
o.pagination = p
|
|
|
|
o.perPageDefault = defPerPage
|
2024-11-25 19:06:56 -05:00
|
|
|
o.totalDest = totalDest
|
2024-11-24 00:06:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type TGOrder string
|
|
|
|
|
|
|
|
const (
|
|
|
|
TGOrderTGID TGOrder = "tgid"
|
|
|
|
TGOrderGroup TGOrder = "group"
|
|
|
|
TGOrderName TGOrder = "name"
|
|
|
|
TGOrderID TGOrder = "id"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (t *TGOrder) IsValid() bool {
|
|
|
|
if t == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
switch *t {
|
|
|
|
case TGOrderTGID, TGOrderGroup, TGOrderName, TGOrderID:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
type Pagination struct {
|
|
|
|
common.Pagination
|
|
|
|
|
|
|
|
OrderBy *TGOrder `json:"orderBy"`
|
|
|
|
}
|
|
|
|
|
2024-11-10 10:28:04 -05:00
|
|
|
type storeCtxKey string
|
2024-11-03 09:45:51 -05:00
|
|
|
|
2024-11-10 10:28:04 -05:00
|
|
|
const StoreCtxKey storeCtxKey = "store"
|
2024-11-03 09:45:51 -05:00
|
|
|
|
|
|
|
func CtxWithStore(ctx context.Context, s Store) context.Context {
|
2024-11-10 10:28:04 -05:00
|
|
|
return context.WithValue(ctx, StoreCtxKey, s)
|
2024-11-03 09:45:51 -05:00
|
|
|
}
|
|
|
|
|
2024-11-21 07:44:08 -05:00
|
|
|
func FromCtx(ctx context.Context) Store {
|
2024-11-10 10:28:04 -05:00
|
|
|
s, ok := ctx.Value(StoreCtxKey).(Store)
|
2024-11-03 09:45:51 -05:00
|
|
|
if !ok {
|
|
|
|
return NewCache()
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-11-03 19:11:45 -05:00
|
|
|
func (t *cache) HUP(_ *config.Config) {
|
|
|
|
t.Invalidate()
|
|
|
|
}
|
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
func (t *cache) Invalidate() {
|
2024-11-02 11:39:02 -04:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
2024-12-10 19:10:25 -05:00
|
|
|
t.invalidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *cache) invalidate() {
|
2024-11-02 11:39:02 -04:00
|
|
|
clear(t.tgs)
|
|
|
|
clear(t.systems)
|
|
|
|
}
|
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
type cache struct {
|
2024-11-02 11:39:02 -04:00
|
|
|
sync.RWMutex
|
2024-10-31 16:14:38 -04:00
|
|
|
tgs tgMap
|
2024-12-10 19:10:25 -05:00
|
|
|
systems map[int]string
|
2024-10-31 16:14:38 -04:00
|
|
|
}
|
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
// NewCache returns a new cache Store.
|
2024-12-01 03:01:09 -05:00
|
|
|
func NewCache() *cache {
|
2024-11-03 08:09:49 -05:00
|
|
|
tgc := &cache{
|
2024-11-12 09:57:38 -05:00
|
|
|
tgs: make(tgMap),
|
2024-12-10 19:10:25 -05:00
|
|
|
systems: make(map[int]string),
|
2024-10-31 16:14:38 -04:00
|
|
|
}
|
|
|
|
|
2024-11-02 11:39:02 -04:00
|
|
|
return tgc
|
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
func (t *cache) Hint(ctx context.Context, tgs []tgsp.ID) error {
|
2024-12-10 19:10:25 -05:00
|
|
|
if len(tgs) < 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-02 11:39:02 -04:00
|
|
|
t.RLock()
|
2024-11-15 10:37:58 -05:00
|
|
|
var toLoad database.TGTuples
|
2024-11-02 11:39:02 -04:00
|
|
|
if len(t.tgs) > len(tgs)/2 { // TODO: instrument this
|
|
|
|
for _, tg := range tgs {
|
|
|
|
_, ok := t.tgs[tg]
|
|
|
|
if !ok {
|
2024-11-15 10:37:58 -05:00
|
|
|
toLoad.Append(tg.System, tg.Talkgroup)
|
2024-11-02 11:39:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2024-11-15 10:37:58 -05:00
|
|
|
toLoad[0] = make([]uint32, 0, len(tgs))
|
|
|
|
toLoad[1] = make([]uint32, 0, len(tgs))
|
2024-11-02 11:39:02 -04:00
|
|
|
for _, g := range tgs {
|
2024-11-15 10:37:58 -05:00
|
|
|
toLoad.Append(g.System, g.Talkgroup)
|
2024-11-02 11:39:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(toLoad) > 0 {
|
|
|
|
t.RUnlock()
|
|
|
|
return t.Load(ctx, toLoad)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.RUnlock()
|
|
|
|
return nil
|
2024-10-31 16:14:38 -04:00
|
|
|
}
|
|
|
|
|
2024-11-21 22:30:47 -05:00
|
|
|
func (t *cache) get(id tgsp.ID) (*tgsp.Talkgroup, bool) {
|
|
|
|
t.RLock()
|
|
|
|
defer t.RUnlock()
|
|
|
|
|
|
|
|
tg, has := t.tgs[id]
|
|
|
|
|
|
|
|
return tg, has
|
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
func (t *cache) add(rec *tgsp.Talkgroup) {
|
2024-11-03 14:11:38 -05:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
t.addNoLock(rec)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *cache) addNoLock(rec *tgsp.Talkgroup) {
|
2024-11-20 19:59:24 -05:00
|
|
|
tg := tgsp.TG(rec.System.ID, rec.Talkgroup.TGID)
|
2024-11-02 11:39:02 -04:00
|
|
|
t.tgs[tg] = rec
|
2024-12-10 19:10:25 -05:00
|
|
|
t.systems[rec.System.ID] = rec.System.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *cache) addSysNoLock(id int, name string) {
|
|
|
|
t.systems[id] = name
|
2024-11-02 11:39:02 -04:00
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
type rowType interface {
|
2024-11-15 08:46:29 -05:00
|
|
|
database.GetTalkgroupsRow | database.GetTalkgroupsWithLearnedRow |
|
2024-11-24 00:06:43 -05:00
|
|
|
database.GetTalkgroupsWithLearnedBySystemRow | database.GetTalkgroupWithLearnedRow |
|
|
|
|
database.GetTalkgroupsWithLearnedBySystemPRow | database.GetTalkgroupsWithLearnedPRow
|
|
|
|
row
|
|
|
|
}
|
|
|
|
|
|
|
|
type row interface {
|
2024-11-04 23:41:52 -05:00
|
|
|
GetTalkgroup() database.Talkgroup
|
|
|
|
GetSystem() database.System
|
|
|
|
GetLearned() bool
|
2024-11-03 09:45:51 -05:00
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
func rowToTalkgroup[T rowType](r T) *tgsp.Talkgroup {
|
2024-11-20 19:59:24 -05:00
|
|
|
return &tgsp.Talkgroup{
|
2024-11-04 23:41:52 -05:00
|
|
|
Talkgroup: r.GetTalkgroup(),
|
|
|
|
System: r.GetSystem(),
|
|
|
|
Learned: r.GetLearned(),
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
2024-11-04 23:41:52 -05:00
|
|
|
}
|
2024-11-04 11:15:24 -05:00
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
func addToRowListS[T rowType](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.Talkgroup {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-11-04 11:15:24 -05:00
|
|
|
for _, rec := range tgRecords {
|
|
|
|
tg := rowToTalkgroup(rec)
|
2024-11-24 00:06:43 -05:00
|
|
|
t.addNoLock(tg)
|
2024-11-04 11:15:24 -05:00
|
|
|
|
|
|
|
r = append(r, tg)
|
|
|
|
}
|
|
|
|
|
2024-11-20 11:07:58 -05:00
|
|
|
return r
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
func addToRowList[T rowType](t *cache, tgRecords []T) []*tgsp.Talkgroup {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
r := make([]*tgsp.Talkgroup, 0, len(tgRecords))
|
|
|
|
|
|
|
|
for _, rec := range tgRecords {
|
|
|
|
tg := rowToTalkgroup(rec)
|
|
|
|
t.addNoLock(tg)
|
|
|
|
|
|
|
|
r = append(r, tg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs, opts ...option) ([]*tgsp.Talkgroup, error) {
|
|
|
|
db := database.FromCtx(ctx)
|
2024-11-20 19:59:24 -05:00
|
|
|
r := make([]*tgsp.Talkgroup, 0, len(tgs))
|
2024-11-24 00:06:43 -05:00
|
|
|
opt := sOpt(opts)
|
2024-11-04 23:41:52 -05:00
|
|
|
var err error
|
|
|
|
if tgs != nil {
|
2024-11-20 19:59:24 -05:00
|
|
|
toGet := make(tgsp.IDs, 0, len(tgs))
|
2024-11-04 23:41:52 -05:00
|
|
|
for _, id := range tgs {
|
2024-11-21 22:30:47 -05:00
|
|
|
rec, has := t.get(id)
|
2024-11-04 23:41:52 -05:00
|
|
|
if has {
|
|
|
|
r = append(r, rec)
|
|
|
|
} else {
|
|
|
|
toGet = append(toGet, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
tgRecords, err := db.GetTalkgroupsWithLearnedBySysTGID(ctx, toGet.Tuples())
|
2024-11-04 23:41:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-25 12:43:02 -05:00
|
|
|
return addToRowListS(t, r, tgRecords), nil
|
2024-11-04 23:41:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// get all talkgroups
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
if opt.pagination != nil {
|
|
|
|
offset, perPage := opt.pagination.OffsetPerPage(opt.perPageDefault)
|
2024-11-25 19:06:56 -05:00
|
|
|
var tgRecords []database.GetTalkgroupsWithLearnedPRow
|
|
|
|
var err error
|
|
|
|
err = db.InTx(ctx, func(db database.Store) error {
|
|
|
|
tgRecords, err = db.GetTalkgroupsWithLearnedP(ctx, offset, perPage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if opt.totalDest != nil {
|
|
|
|
count, err := db.GetTalkgroupsWithLearnedPCount(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*opt.totalDest = int(count)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}, pgx.TxOptions{})
|
2024-11-24 00:06:43 -05:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-25 19:06:56 -05:00
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
return addToRowListS(t, r, tgRecords), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tgRecords, err := db.GetTalkgroupsWithLearned(ctx)
|
2024-11-04 23:41:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-24 00:06:43 -05:00
|
|
|
return addToRowListS(t, r, tgRecords), nil
|
2024-11-04 23:41:52 -05:00
|
|
|
}
|
|
|
|
|
2024-11-15 10:37:58 -05:00
|
|
|
func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error {
|
|
|
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySysTGID(ctx, tgs)
|
2024-10-31 16:14:38 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rec := range tgRecords {
|
2024-11-20 11:07:58 -05:00
|
|
|
t.add(rowToTalkgroup(rec))
|
2024-10-31 16:14:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 {
|
2024-11-03 19:11:45 -05:00
|
|
|
tg, err := t.TG(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return 1.0
|
|
|
|
}
|
|
|
|
|
|
|
|
m := float64(tg.Weight)
|
|
|
|
|
2024-11-12 09:57:38 -05:00
|
|
|
m *= tg.AlertConfig.Apply(tm)
|
2024-11-03 19:11:45 -05:00
|
|
|
|
2024-11-03 19:22:38 -05:00
|
|
|
return float64(m)
|
2024-11-03 19:11:45 -05:00
|
|
|
}
|
|
|
|
|
2024-12-10 19:10:25 -05:00
|
|
|
func (t *cache) SystemTGs(ctx context.Context, systemID int, opts ...option) ([]*tgsp.Talkgroup, error) {
|
2024-11-24 00:06:43 -05:00
|
|
|
db := database.FromCtx(ctx)
|
|
|
|
opt := sOpt(opts)
|
|
|
|
var err error
|
|
|
|
if opt.pagination != nil {
|
|
|
|
offset, perPage := opt.pagination.OffsetPerPage(opt.perPageDefault)
|
2024-12-10 19:10:25 -05:00
|
|
|
recs, err := db.GetTalkgroupsWithLearnedBySystemP(ctx, int32(systemID), offset, perPage)
|
2024-11-24 00:06:43 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return addToRowList(t, recs), nil
|
|
|
|
}
|
|
|
|
|
2024-12-10 19:10:25 -05:00
|
|
|
recs, err := db.GetTalkgroupsWithLearnedBySystem(ctx, int32(systemID))
|
2024-11-04 23:41:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
return addToRowList(t, recs), nil
|
2024-11-04 23:41:52 -05:00
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
2024-11-21 22:30:47 -05:00
|
|
|
rec, has := t.get(tg)
|
2024-10-31 16:14:38 -04:00
|
|
|
|
2024-11-02 11:39:02 -04:00
|
|
|
if has {
|
2024-11-03 09:45:51 -05:00
|
|
|
return rec, nil
|
2024-11-02 11:39:02 -04:00
|
|
|
}
|
|
|
|
|
2024-11-15 10:37:58 -05:00
|
|
|
record, err := database.FromCtx(ctx).GetTalkgroupWithLearned(ctx, int32(tg.System), int32(tg.Talkgroup))
|
2024-11-02 11:39:02 -04:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
case pgx.ErrNoRows:
|
2024-11-04 11:15:24 -05:00
|
|
|
return nil, ErrNotFound
|
2024-11-02 11:39:02 -04:00
|
|
|
default:
|
2024-11-17 21:46:10 -05:00
|
|
|
log.Error().Err(err).Uint32("sys", tg.System).Uint32("tg", tg.Talkgroup).Msg("TG() cache add db get")
|
2024-11-04 11:15:24 -05:00
|
|
|
return nil, errors.Join(ErrNotFound, err)
|
2024-11-02 11:39:02 -04:00
|
|
|
}
|
|
|
|
|
2024-11-20 11:07:58 -05:00
|
|
|
t.add(rowToTalkgroup(record))
|
2024-11-02 11:39:02 -04:00
|
|
|
|
2024-11-15 10:37:58 -05:00
|
|
|
return rowToTalkgroup(record), nil
|
2024-10-31 16:14:38 -04:00
|
|
|
}
|
|
|
|
|
2024-11-03 08:09:49 -05:00
|
|
|
func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool) {
|
2024-11-03 14:11:38 -05:00
|
|
|
t.RLock()
|
2024-12-10 19:10:25 -05:00
|
|
|
n, has := t.systems[id]
|
2024-11-03 14:11:38 -05:00
|
|
|
t.RUnlock()
|
2024-11-02 11:39:02 -04:00
|
|
|
|
|
|
|
if !has {
|
|
|
|
sys, err := database.FromCtx(ctx).GetSystemName(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2024-11-03 14:11:38 -05:00
|
|
|
t.Lock()
|
2024-12-10 19:10:25 -05:00
|
|
|
t.systems[id] = sys
|
2024-11-03 14:11:38 -05:00
|
|
|
t.Unlock()
|
|
|
|
|
2024-11-02 11:39:02 -04:00
|
|
|
return sys, true
|
|
|
|
}
|
|
|
|
|
2024-10-31 16:14:38 -04:00
|
|
|
return n, has
|
|
|
|
}
|
2024-11-10 10:13:38 -05:00
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error) {
|
2024-11-14 08:37:19 -05:00
|
|
|
sysName, has := t.SystemName(ctx, int(*input.SystemID))
|
2024-11-10 10:13:38 -05:00
|
|
|
if !has {
|
|
|
|
return nil, ErrNoSuchSystem
|
|
|
|
}
|
2024-12-10 19:10:25 -05:00
|
|
|
db := database.FromCtx(ctx)
|
|
|
|
var tg database.Talkgroup
|
|
|
|
err := db.InTx(ctx, func(db database.Store) error {
|
|
|
|
var oerr error
|
|
|
|
tg, oerr = db.UpdateTalkgroup(ctx, input)
|
|
|
|
if oerr != nil {
|
|
|
|
return oerr
|
|
|
|
}
|
|
|
|
versionBatch := db.StoreTGVersion(ctx, []database.StoreTGVersionParams{{
|
|
|
|
Submitter: auth.UIDFrom(ctx),
|
|
|
|
TGID: *input.TGID,
|
|
|
|
}})
|
|
|
|
defer versionBatch.Close()
|
|
|
|
|
|
|
|
versionBatch.Exec(func(_ int, err error) {
|
|
|
|
if err != nil {
|
|
|
|
oerr = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return oerr
|
|
|
|
}, pgx.TxOptions{})
|
2024-11-10 10:13:38 -05:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
record := &tgsp.Talkgroup{
|
2024-11-10 10:13:38 -05:00
|
|
|
Talkgroup: tg,
|
|
|
|
System: database.System{ID: int(tg.SystemID), Name: sysName},
|
|
|
|
}
|
|
|
|
t.add(record)
|
|
|
|
|
|
|
|
return record, nil
|
|
|
|
}
|
2024-11-20 07:26:59 -05:00
|
|
|
|
2024-12-10 19:10:25 -05:00
|
|
|
func (t *cache) DeleteSystem(ctx context.Context, id int) error {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.invalidate()
|
|
|
|
|
|
|
|
err := database.FromCtx(ctx).DeleteSystem(ctx, id)
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
return nil
|
|
|
|
case database.IsSystemConstraintViolation(err):
|
|
|
|
return ErrReference
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *cache) DeleteTG(ctx context.Context, id tgsp.ID) error {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
err := database.FromCtx(ctx).InTx(ctx, func(db database.Store) error {
|
|
|
|
err := db.StoreDeletedTGVersion(ctx, common.PtrTo(int32(id.System)), common.PtrTo(int32(id.Talkgroup)), auth.UIDFrom(ctx))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return db.DeleteTalkgroup(ctx, int32(id.System), int32(id.Talkgroup))
|
|
|
|
}, pgx.TxOptions{})
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
case database.IsTGConstraintViolation(err):
|
|
|
|
return ErrReference
|
|
|
|
default:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(t.tgs, id)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-20 22:13:23 -05:00
|
|
|
func (t *cache) LearnTG(ctx context.Context, c *calls.Call) (*tgsp.Talkgroup, error) {
|
2024-11-20 20:13:18 -05:00
|
|
|
db := database.FromCtx(ctx)
|
2024-11-21 07:44:08 -05:00
|
|
|
|
2024-11-20 22:13:23 -05:00
|
|
|
sys, has := t.SystemName(ctx, c.System)
|
|
|
|
if !has {
|
|
|
|
return nil, ErrNoSuchSystem
|
2024-11-20 20:13:18 -05:00
|
|
|
}
|
|
|
|
|
2024-11-20 22:13:23 -05:00
|
|
|
tgm, err := db.AddLearnedTalkgroup(ctx, database.AddLearnedTalkgroupParams{
|
|
|
|
SystemID: int32(c.System),
|
|
|
|
TGID: int32(c.Talkgroup),
|
2024-11-20 20:13:18 -05:00
|
|
|
Name: c.TalkgroupLabel,
|
|
|
|
AlphaTag: c.TGAlphaTag,
|
|
|
|
TGGroup: c.TalkgroupGroup,
|
|
|
|
})
|
2024-11-20 22:13:23 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tg := &tgsp.Talkgroup{
|
|
|
|
Talkgroup: tgm,
|
|
|
|
System: database.System{
|
2024-11-21 13:23:21 -05:00
|
|
|
ID: c.System,
|
2024-11-20 22:13:23 -05:00
|
|
|
Name: sys,
|
|
|
|
},
|
|
|
|
Learned: tgm.Learned,
|
|
|
|
}
|
|
|
|
|
|
|
|
t.add(tg)
|
|
|
|
|
|
|
|
return tg, nil
|
2024-11-20 20:13:18 -05:00
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error) {
|
2024-11-20 07:26:59 -05:00
|
|
|
db := database.FromCtx(ctx)
|
|
|
|
sysName, hasSys := t.SystemName(ctx, system)
|
|
|
|
if !hasSys {
|
|
|
|
return nil, ErrNoSuchSystem
|
|
|
|
}
|
|
|
|
sys := database.System{
|
2024-11-20 09:37:57 -05:00
|
|
|
ID: system,
|
2024-11-20 07:26:59 -05:00
|
|
|
Name: sysName,
|
|
|
|
}
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
tgs := make([]*tgsp.Talkgroup, 0, len(input))
|
2024-11-20 07:26:59 -05:00
|
|
|
|
|
|
|
err := db.InTx(ctx, func(db database.Store) error {
|
2024-11-20 19:15:26 -05:00
|
|
|
versionParams := make([]database.StoreTGVersionParams, 0, len(input))
|
2024-11-20 09:37:57 -05:00
|
|
|
for i := range input {
|
2024-11-20 07:26:59 -05:00
|
|
|
// normalize tags
|
2024-11-20 09:37:57 -05:00
|
|
|
for j, tag := range input[i].Tags {
|
|
|
|
input[i].Tags[j] = strings.ToLower(tag)
|
2024-11-20 07:26:59 -05:00
|
|
|
}
|
|
|
|
|
2024-11-20 09:37:57 -05:00
|
|
|
input[i].SystemID = int32(system)
|
|
|
|
input[i].Learned = common.PtrTo(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
var oerr error
|
|
|
|
|
2024-11-20 19:15:26 -05:00
|
|
|
tgUpsertBatch := db.UpsertTalkgroup(ctx, input)
|
|
|
|
defer tgUpsertBatch.Close()
|
2024-11-20 09:37:57 -05:00
|
|
|
|
2024-11-20 19:15:26 -05:00
|
|
|
tgUpsertBatch.QueryRow(func(_ int, r database.Talkgroup, err error) {
|
2024-11-20 07:26:59 -05:00
|
|
|
if err != nil {
|
2024-11-20 09:37:57 -05:00
|
|
|
oerr = err
|
|
|
|
return
|
2024-11-20 07:26:59 -05:00
|
|
|
}
|
2024-11-20 19:15:26 -05:00
|
|
|
versionParams = append(versionParams, database.StoreTGVersionParams{
|
2024-11-21 13:23:21 -05:00
|
|
|
SystemID: int32(system),
|
|
|
|
TGID: r.TGID,
|
2024-11-20 19:15:26 -05:00
|
|
|
Submitter: auth.UIDFrom(ctx),
|
|
|
|
})
|
2024-11-20 19:59:24 -05:00
|
|
|
tgs = append(tgs, &tgsp.Talkgroup{
|
2024-11-20 09:37:57 -05:00
|
|
|
Talkgroup: r,
|
|
|
|
System: sys,
|
|
|
|
Learned: r.Learned,
|
2024-11-20 07:26:59 -05:00
|
|
|
})
|
2024-11-20 09:37:57 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
if oerr != nil {
|
|
|
|
return oerr
|
2024-11-20 07:26:59 -05:00
|
|
|
}
|
|
|
|
|
2024-11-20 19:15:26 -05:00
|
|
|
versionBatch := db.StoreTGVersion(ctx, versionParams)
|
|
|
|
defer versionBatch.Close()
|
|
|
|
|
|
|
|
versionBatch.Exec(func(_ int, err error) {
|
|
|
|
if err != nil {
|
|
|
|
oerr = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return oerr
|
2024-11-20 07:26:59 -05:00
|
|
|
}, pgx.TxOptions{})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the cache
|
|
|
|
for _, tg := range tgs {
|
2024-11-20 11:07:58 -05:00
|
|
|
t.add(tg)
|
2024-11-20 07:26:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return tgs, nil
|
|
|
|
}
|
2024-12-10 19:10:25 -05:00
|
|
|
|
|
|
|
func (t *cache) CreateSystem(ctx context.Context, id int, name string) error {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.addSysNoLock(id, name)
|
|
|
|
|
|
|
|
return database.FromCtx(ctx).CreateSystem(ctx, id, name)
|
|
|
|
}
|