Skip to content

Commit a4a4b5a

Browse files
committed
feat: Improved favorite navigation and usage
1 parent 8f722de commit a4a4b5a

13 files changed

Lines changed: 631 additions & 51 deletions

File tree

cmd/sigsentinel/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ func run(opts cliFlags) error {
184184
ExecuteControl: func(request gui.ControlRequest) gui.ControlResult {
185185
return executeGUIControl(runtime, request)
186186
},
187+
LoadScannerList: func(listType string, index int) ([]gui.ListItem, error) {
188+
return runtime.ReadScannerList(listType, index)
189+
},
187190
LoadScanScope: func(favoritesTag, systemTag int) (gui.ScanScopeSnapshot, error) {
188191
return runtime.ReadScanScope(favoritesTag, systemTag)
189192
},

cmd/sigsentinel/runtime.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ func (r *Runtime) ExecuteControl(intent ControlIntent, params ControlParams) err
123123
return r.session.ExecuteControl(intent, params)
124124
}
125125

126+
func (r *Runtime) ReadScannerList(listType string, index int) ([]gui.ListItem, error) {
127+
return r.session.ReadScannerList(listType, index)
128+
}
129+
126130
func (r *Runtime) ReadScanScope(favoritesTag, systemTag int) (gui.ScanScopeSnapshot, error) {
127131
return r.session.ReadScanScope(favoritesTag, systemTag)
128132
}
@@ -225,10 +229,10 @@ func (r *Runtime) ApplyScanProfile(name string, scope ProfileScopeSelector) erro
225229
if r == nil || r.session == nil {
226230
return errors.New("runtime session unavailable")
227231
}
228-
if err := validateQuickKeyTag("favorites quick key", scope.FavoritesTag); err != nil {
232+
if err := validateFavoritesIndex("favorites index", scope.FavoritesTag); err != nil {
229233
return err
230234
}
231-
if err := validateQuickKeyTag("system quick key", scope.SystemTag); err != nil {
235+
if err := validateQuickKeySlot("system quick key", scope.SystemTag); err != nil {
232236
return err
233237
}
234238
profile, err := r.findScanProfile(name)
@@ -567,7 +571,7 @@ func normalizeScanProfile(profile store.ScanProfile) (store.ScanProfile, error)
567571
if err != nil {
568572
return store.ScanProfile{}, fmt.Errorf("invalid system quick key profile key %q", cleanKey)
569573
}
570-
if err := validateQuickKeyTag("system quick key profile key", parsed); err != nil {
574+
if err := validateFavoritesIndex("system quick key profile key", parsed); err != nil {
571575
return store.ScanProfile{}, err
572576
}
573577
normalized, normErr := validateBinaryValues(values, 100, "system quick keys")
@@ -596,10 +600,10 @@ func normalizeScanProfile(profile store.ScanProfile) (store.ScanProfile, error)
596600
if err != nil {
597601
return store.ScanProfile{}, fmt.Errorf("invalid department quick key profile key %q", cleanKey)
598602
}
599-
if err := validateQuickKeyTag("department quick key profile favorites tag", favTag); err != nil {
603+
if err := validateFavoritesIndex("department quick key profile favorites tag", favTag); err != nil {
600604
return store.ScanProfile{}, err
601605
}
602-
if err := validateQuickKeyTag("department quick key profile system tag", sysTag); err != nil {
606+
if err := validateQuickKeySlot("department quick key profile system tag", sysTag); err != nil {
603607
return store.ScanProfile{}, err
604608
}
605609
normalized, normErr := validateBinaryValues(values, 100, "department quick keys")

cmd/sigsentinel/session.go

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type SDS200Client interface {
5252
SetDateTime(daylightSaving int, t time.Time) error
5353
GetLocationRange() (sds200.LocationRange, error)
5454
SetLocationRange(lat, lon, rng string) error
55+
GetList(listType string, index ...int) (sds200.XMLNode, error)
5556
GetChargeStatus() (sds200.ChargeStatus, error)
5657
KeepAlive() error
5758
PowerOff() error
@@ -501,7 +502,7 @@ func (s *ScannerSession) executeIntent(intent ControlIntent, params ControlParam
501502
}
502503
return s.resyncAfterScopeChange(client)
503504
case IntentSetSystemQuickKeys:
504-
if err := validateQuickKeyTag("favorites quick key", params.ScopeFavoritesTag); err != nil {
505+
if err := validateFavoritesIndex("favorites index", params.ScopeFavoritesTag); err != nil {
505506
return err
506507
}
507508
values, err := validateQuickKeyValues(params.QuickKeyValues, 100, "system quick keys")
@@ -514,10 +515,10 @@ func (s *ScannerSession) executeIntent(intent ControlIntent, params ControlParam
514515
}
515516
return s.resyncAfterScopeChange(client)
516517
case IntentSetDepartmentQuickKeys:
517-
if err := validateQuickKeyTag("favorites quick key", params.ScopeFavoritesTag); err != nil {
518+
if err := validateFavoritesIndex("favorites index", params.ScopeFavoritesTag); err != nil {
518519
return err
519520
}
520-
if err := validateQuickKeyTag("system quick key", params.ScopeSystemTag); err != nil {
521+
if err := validateQuickKeySlot("system quick key", params.ScopeSystemTag); err != nil {
521522
return err
522523
}
523524
values, err := validateQuickKeyValues(params.QuickKeyValues, 100, "department quick keys")
@@ -731,14 +732,58 @@ func (s *ScannerSession) executeIntent(intent ControlIntent, params ControlParam
731732
}
732733
}
733734

735+
func (s *ScannerSession) ReadScannerList(listType string, index int) ([]gui.ListItem, error) {
736+
if s == nil {
737+
return nil, errors.New("scanner session unavailable")
738+
}
739+
listType = strings.TrimSpace(listType)
740+
if listType == "" {
741+
return nil, errors.New("list type is required")
742+
}
743+
744+
s.mu.RLock()
745+
client := s.client
746+
s.mu.RUnlock()
747+
if client == nil {
748+
return nil, errors.New("scanner client unavailable")
749+
}
750+
751+
var node sds200.XMLNode
752+
var err error
753+
if index >= 0 {
754+
node, err = client.GetList(listType, index)
755+
} else {
756+
node, err = client.GetList(listType)
757+
}
758+
if err != nil {
759+
return nil, err
760+
}
761+
762+
items := make([]gui.ListItem, 0, len(node.Children))
763+
for _, child := range node.Children {
764+
if strings.EqualFold(child.XMLName.Local, "Footer") {
765+
continue
766+
}
767+
attrs := make(map[string]string, len(child.Attrs))
768+
for k, v := range child.Attrs {
769+
attrs[k] = v
770+
}
771+
items = append(items, gui.ListItem{
772+
Tag: child.XMLName.Local,
773+
Attrs: attrs,
774+
})
775+
}
776+
return items, nil
777+
}
778+
734779
func (s *ScannerSession) ReadScanScope(favoritesTag, systemTag int) (gui.ScanScopeSnapshot, error) {
735780
if s == nil {
736781
return gui.ScanScopeSnapshot{}, errors.New("scanner session unavailable")
737782
}
738-
if err := validateQuickKeyTag("favorites quick key", favoritesTag); err != nil {
783+
if err := validateFavoritesIndex("favorites index", favoritesTag); err != nil {
739784
return gui.ScanScopeSnapshot{}, err
740785
}
741-
if err := validateQuickKeyTag("system quick key", systemTag); err != nil {
786+
if err := validateQuickKeySlot("system quick key", systemTag); err != nil {
742787
return gui.ScanScopeSnapshot{}, err
743788
}
744789

@@ -753,13 +798,15 @@ func (s *ScannerSession) ReadScanScope(favoritesTag, systemTag int) (gui.ScanSco
753798
if err != nil {
754799
return gui.ScanScopeSnapshot{}, err
755800
}
756-
systemState, err := client.GetSystemQuickKeys(favoritesTag)
757-
if err != nil {
758-
return gui.ScanScopeSnapshot{}, err
801+
// SQK/DQK may fail for special favorites lists (e.g. Full Database) that
802+
// use sentinel index values. Treat failures as empty quick key states.
803+
var systemState sds200.QuickKeyState
804+
if sqk, sqkErr := client.GetSystemQuickKeys(favoritesTag); sqkErr == nil {
805+
systemState = sqk
759806
}
760-
departmentState, err := client.GetDepartmentQuickKeys(favoritesTag, systemTag)
761-
if err != nil {
762-
return gui.ScanScopeSnapshot{}, err
807+
var departmentState sds200.QuickKeyState
808+
if dqk, dqkErr := client.GetDepartmentQuickKeys(favoritesTag, systemTag); dqkErr == nil {
809+
departmentState = dqk
763810
}
764811
serviceTypes, err := client.GetServiceTypes()
765812
if err != nil {
@@ -835,7 +882,14 @@ func applyLocationToExpert(expert *ExpertRuntimeState, loc sds200.LocationRange)
835882
)
836883
}
837884

838-
func validateQuickKeyTag(name string, value int) error {
885+
func validateFavoritesIndex(name string, value int) error {
886+
if value < 0 {
887+
return fmt.Errorf("%s must be >= 0 (got %d)", name, value)
888+
}
889+
return nil
890+
}
891+
892+
func validateQuickKeySlot(name string, value int) error {
839893
if value < 0 || value > 99 {
840894
return fmt.Errorf("%s must be in range 0-99 (got %d)", name, value)
841895
}

cmd/sigsentinel/session_test.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"encoding/xml"
56
"errors"
67
"sync"
78
"sync/atomic"
@@ -57,6 +58,9 @@ type fakeSDS200Client struct {
5758
powerOffErr error
5859
closeErr error
5960

61+
getListErr error
62+
getListNode sds200.XMLNode
63+
6064
telemetrySnapshot sds200.RuntimeStatus
6165
scannerInfo sds200.ScannerInfo
6266
onTelemetry func(sds200.RuntimeStatus)
@@ -524,6 +528,12 @@ func (f *fakeSDS200Client) SetLocationRange(lat, lon, rng string) error {
524528
return err
525529
}
526530

531+
func (f *fakeSDS200Client) GetList(listType string, index ...int) (sds200.XMLNode, error) {
532+
f.mu.Lock()
533+
defer f.mu.Unlock()
534+
return f.getListNode, f.getListErr
535+
}
536+
527537
func (f *fakeSDS200Client) GetChargeStatus() (sds200.ChargeStatus, error) {
528538
f.mu.Lock()
529539
defer f.mu.Unlock()
@@ -1309,7 +1319,7 @@ func TestScannerSessionReadScanScope(t *testing.T) {
13091319

13101320
_, err := session.ReadScanScope(-1, 0)
13111321
require.Error(t, err)
1312-
assert.Equal(t, "favorites quick key must be in range 0-99 (got -1)", err.Error())
1322+
assert.Equal(t, "favorites index must be >= 0 (got -1)", err.Error())
13131323
})
13141324

13151325
t.Run("rejects_nil_session", func(t *testing.T) {
@@ -1353,6 +1363,86 @@ func TestScannerSessionReadScanScope(t *testing.T) {
13531363
})
13541364
}
13551365

1366+
func TestScannerSessionReadScannerList(t *testing.T) {
1367+
t.Parallel()
1368+
1369+
t.Run("returns_list_items", func(t *testing.T) {
1370+
client := &fakeSDS200Client{
1371+
getListNode: sds200.XMLNode{
1372+
Children: []sds200.XMLNode{
1373+
{
1374+
XMLName: xml.Name{Local: "FL"},
1375+
Attrs: map[string]string{"Index": "0", "Name": "Favorites 1", "Monitor": "On"},
1376+
},
1377+
{
1378+
XMLName: xml.Name{Local: "FL"},
1379+
Attrs: map[string]string{"Index": "1", "Name": "Favorites 2", "Monitor": "Off"},
1380+
},
1381+
{
1382+
XMLName: xml.Name{Local: "Footer"},
1383+
Attrs: map[string]string{"No": "1", "EOT": "1"},
1384+
},
1385+
},
1386+
},
1387+
}
1388+
session := &ScannerSession{client: client}
1389+
1390+
items, err := session.ReadScannerList("FL", -1)
1391+
require.NoError(t, err)
1392+
require.Len(t, items, 2)
1393+
assert.Equal(t, "FL", items[0].Tag)
1394+
assert.Equal(t, "0", items[0].Attrs["Index"])
1395+
assert.Equal(t, "Favorites 1", items[0].Attrs["Name"])
1396+
assert.Equal(t, "On", items[0].Attrs["Monitor"])
1397+
assert.Equal(t, "1", items[1].Attrs["Index"])
1398+
assert.Equal(t, "Favorites 2", items[1].Attrs["Name"])
1399+
})
1400+
1401+
t.Run("filters_footer_nodes", func(t *testing.T) {
1402+
client := &fakeSDS200Client{
1403+
getListNode: sds200.XMLNode{
1404+
Children: []sds200.XMLNode{
1405+
{
1406+
XMLName: xml.Name{Local: "SYS"},
1407+
Attrs: map[string]string{"Index": "0", "Name": "County"},
1408+
},
1409+
{
1410+
XMLName: xml.Name{Local: "footer"},
1411+
Attrs: map[string]string{"No": "1", "EOT": "1"},
1412+
},
1413+
},
1414+
},
1415+
}
1416+
session := &ScannerSession{client: client}
1417+
1418+
items, err := session.ReadScannerList("SYS", 0)
1419+
require.NoError(t, err)
1420+
require.Len(t, items, 1)
1421+
assert.Equal(t, "SYS", items[0].Tag)
1422+
})
1423+
1424+
t.Run("empty_list_type_rejected", func(t *testing.T) {
1425+
session := &ScannerSession{client: &fakeSDS200Client{}}
1426+
_, err := session.ReadScannerList("", -1)
1427+
require.Error(t, err)
1428+
assert.Equal(t, "list type is required", err.Error())
1429+
})
1430+
1431+
t.Run("nil_session_rejected", func(t *testing.T) {
1432+
var session *ScannerSession
1433+
_, err := session.ReadScannerList("FL", -1)
1434+
require.Error(t, err)
1435+
assert.Equal(t, "scanner session unavailable", err.Error())
1436+
})
1437+
1438+
t.Run("nil_client_rejected", func(t *testing.T) {
1439+
session := &ScannerSession{}
1440+
_, err := session.ReadScannerList("FL", -1)
1441+
require.Error(t, err)
1442+
assert.Equal(t, "scanner client unavailable", err.Error())
1443+
})
1444+
}
1445+
13561446
func TestScannerSessionEnqueueControl(t *testing.T) {
13571447
t.Parallel()
13581448

0 commit comments

Comments
 (0)