diff --git a/.github/renovate.json b/.github/renovate.json
index fb0b963..aee303c 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -1,25 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": ["config:base", "default:automergeDigest"],
+ "extends": [
+ "config:recommended",
+ "default:automergeDigest",
+ "helpers:pinGitHubActionDigestsToSemver"
+ ],
+ "minimumReleaseAge": "7 days",
+ "internalChecksFilter": "strict",
"packageRules": [
{
- "description": "Group Go stdlib / toolchain updates and automerge when tests pass",
+ "description": "Automerge non-major Go module updates when checks pass",
"matchManagers": ["gomod"],
- "matchDepNames": ["go"],
- "matchDepTypes": ["golang", "toolchain"],
- "groupName": "go",
- "rangeStrategy": "bump",
+ "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
},
{
- "description": "Group GitHub Actions updates and automerge when tests pass",
+ "description": "Automerge GitHub Actions updates, including majors, when checks pass",
"matchManagers": ["github-actions"],
- "groupName": "github-actions",
- "automerge": true
- },
- {
- "matchDatasources": ["go", "github-releases", "github-tags"],
- "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
}
]
diff --git a/.github/workflows/codetests.yml b/.github/workflows/codetests.yml
index 50da11a..f8e454c 100644
--- a/.github/workflows/codetests.yml
+++ b/.github/workflows/codetests.yml
@@ -3,12 +3,17 @@ on:
pull_request:
branches:
- main
- push:
- branches:
- - main
permissions:
contents: read
jobs:
+ codespell:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: codespell-project/actions-codespell@e3258cca84ce02b0bb36a3d41f2c18f719a0cc1a # v2.4.3
+ with:
+ skip: .git,go.sum
+ ignore_words_list: Medias
# Runs `go test` against 3 operating systems.
gotest:
strategy:
@@ -16,12 +21,12 @@ jobs:
os: [macos, windows, ubuntu]
runs-on: ${{ matrix.os }}-latest
steps:
- - uses: actions/checkout@v7
- - uses: actions/setup-go@v7
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
- name: go-test
- run: go test -race '-test.v' -covermode=atomic ./...
+ run: go test -race -covermode=atomic '-test.v' ./...
# Runs golangci-lint on macos against freebsd and macos.
golangci-darwin:
strategy:
@@ -32,14 +37,14 @@ jobs:
env:
GOOS: ${{ matrix.os }}
steps:
- - uses: actions/setup-go@v7
+ - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
- - uses: actions/checkout@v7
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: golangci-lint
- uses: golangci/golangci-lint-action@v9
+ uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
- version: v2.12
+ version: v2.13
# Runs golangci-lint on linux against linux and windows.
golangci-linux:
strategy:
@@ -50,11 +55,11 @@ jobs:
env:
GOOS: ${{ matrix.os }}
steps:
- - uses: actions/setup-go@v7
+ - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
- - uses: actions/checkout@v7
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: golangci-lint
- uses: golangci/golangci-lint-action@v9
+ uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
- version: v2.12
+ version: v2.13
diff --git a/.golangci.yml b/.golangci.yml
index 8753653..cc7c33e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,30 +1,28 @@
-version: '2'
+version: "2"
linters:
default: all
disable:
- - exhaustruct
- - depguard
- - wsl
- - funcorder
- - noinlineerr
- - exhaustive
+ - wsl # deprecated.
+ - exhaustruct # deprecated.
+ - exhaustruct_v5 # camera and XML structs are filled a field at a time.
+ - gomodguard # deprecated.
+ - depguard # not useful.
- nlreturn
- - gomodguard
- - goconst
settings:
+ errcheck:
+ exclude-functions:
+ - (*os.File).Close
+ - (io.Closer).Close
+ exhaustive:
+ default-signifies-exhaustive: true
gocritic:
enable-all: true
+ disabled-checks:
+ - whyNoLint
+ - commentedOutCode
settings:
unnamedResult:
checkExported: true
- errcheck:
- check-type-assertions: true
- check-blank: false
- disable-default-exclusions: false
- exclude-functions:
- - (*os.File).Close
- - (io.Closer).Close
-
issues:
max-issues-per-linter: 0
max-same-issues: 0
diff --git a/cameras.go b/cameras.go
index 89a2e42..be7aef6 100644
--- a/cameras.go
+++ b/cameras.go
@@ -88,7 +88,8 @@ func (c *Camera) StreamVideo(ops *VidOps, length time.Duration, maxsize int64) (
// UseHTTP is not supported (returns ErrHTTPVideoUnsupported).
// Set VidOps.VCodec to "h265" for HEVC cameras (see PreferredVCodec).
func (c *Camera) SaveVideo(ops *VidOps, length time.Duration, maxsize int64, outputFile string) error {
- if _, err := os.Stat(outputFile); !os.IsNotExist(err) {
+ _, statErr := os.Stat(outputFile)
+ if !os.IsNotExist(statErr) {
return ErrPathExists
}
@@ -351,7 +352,8 @@ func (c *Camera) ToggleContinuous(arm CameraArmMode) error {
params := make(url.Values)
params.Set("arm", string(arm))
- if err := c.server.SimpleReq("++ssControlContinuous", params, c.Number); err != nil {
+ err := c.server.SimpleReq("++ssControlContinuous", params, c.Number)
+ if err != nil {
if errors.Is(err, server.ErrNotFound) {
return ErrUnsupported
}
@@ -367,7 +369,8 @@ func (c *Camera) ToggleMotion(arm CameraArmMode) error {
params := make(url.Values)
params.Set("arm", string(arm))
- if err := c.server.SimpleReq("++ssControlMotionCapture", params, c.Number); err != nil {
+ err := c.server.SimpleReq("++ssControlMotionCapture", params, c.Number)
+ if err != nil {
return fmt.Errorf("request failed: %w", err)
}
@@ -379,7 +382,8 @@ func (c *Camera) ToggleActions(arm CameraArmMode) error {
params := make(url.Values)
params.Set("arm", string(arm))
- if err := c.server.SimpleReq("++ssControlActions", params, c.Number); err != nil {
+ err := c.server.SimpleReq("++ssControlActions", params, c.Number)
+ if err != nil {
return fmt.Errorf("request failed: %w", err)
}
@@ -389,7 +393,8 @@ func (c *Camera) ToggleActions(arm CameraArmMode) error {
// TriggerMotion sets a camera as currently seeing motion.
// Other actions likely occur because of this!
func (c *Camera) TriggerMotion() error {
- if err := c.server.SimpleReq("++triggermd", make(url.Values), c.Number); err != nil {
+ err := c.server.SimpleReq("++triggermd", make(url.Values), c.Number)
+ if err != nil {
return fmt.Errorf("request failed: %w", err)
}
@@ -497,7 +502,8 @@ func (c *Camera) SetSchedule(mode CameraMode, scheduleID int) error {
params.Set("mode", string(mode))
params.Set("id", strconv.Itoa(scheduleID))
- if err := c.server.SimpleReq("++ssSetSchedule", params, c.Number); err != nil {
+ err := c.server.SimpleReq("++ssSetSchedule", params, c.Number)
+ if err != nil {
return fmt.Errorf("request failed: %w", err)
}
@@ -512,7 +518,8 @@ func (c *Camera) SetScheduleOverride(mode CameraMode, overrideID int) error {
params.Set("mode", string(mode))
params.Set("id", strconv.Itoa(overrideID))
- if err := c.server.SimpleReq("++ssSetOverride", params, c.Number); err != nil {
+ err := c.server.SimpleReq("++ssSetOverride", params, c.Number)
+ if err != nil {
return fmt.Errorf("request failed: %w", err)
}
diff --git a/cameras_test.go b/cameras_test.go
index ef7f9b5..18f90d3 100644
--- a/cameras_test.go
+++ b/cameras_test.go
@@ -11,13 +11,13 @@ import (
func TestUnmarshalXMLCameraSchedule(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
var s securityspy.CameraSchedule
err := xml.Unmarshal([]byte("3"), &s)
require.NoError(t, err, "valid data must not produce an error")
- asert.Equal(3, s.ID, "the data was not unmarshalled properly")
+ check.Equal(3, s.ID, "the data was not unmarshalled properly")
}
// SS 5.5 keeps v5 width/height tags but also emits video-format (a v6-era field).
@@ -54,40 +54,40 @@ func TestUnmarshalXMLCameraSS55WithVideoFormat(t *testing.T) {
func TestAll(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
secspyServer, _, _ := testServerWithCamera(t)
cams := secspyServer.Cameras.All()
- asert.Len(cams, 2, "the data contains two cameras, two cameras must be returned")
+ check.Len(cams, 2, "the data contains two cameras, two cameras must be returned")
}
func TestByNum(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
secspyServer, _, _ := testServerWithCamera(t)
cam := secspyServer.Cameras.ByNum(2)
- asert.Equal("Porch", cam.Name, "camera 2 is Porch in the v6 test data")
+ check.Equal("Porch", cam.Name, "camera 2 is Porch in the v6 test data")
require.Nil(t, secspyServer.Cameras.ByNum(99), "a non-existent camera must return nil")
}
func TestByName(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
secspyServer, _, _ := testServerWithCamera(t)
cam := secspyServer.Cameras.ByName("Porch")
- asert.Equal(2, cam.Number, "camera 2 is Porch in the v6 test data")
+ check.Equal(2, cam.Number, "camera 2 is Porch in the v6 test data")
require.Nil(t, secspyServer.Cameras.ByName("not here"), "a non-existent camera must return nil")
cam = secspyServer.Cameras.ByName("porch2")
require.Nil(t, cam, "there is no camera named porch2")
cam = secspyServer.Cameras.ByName("porch")
- asert.Equal(2, cam.Number, "camera 2 is Porch in the v6 test data")
+ check.Equal(2, cam.Number, "camera 2 is Porch in the v6 test data")
require.Nil(t, secspyServer.Cameras.ByName("not here"), "a non-existent camera must return nil")
}
diff --git a/cameras_types.go b/cameras_types.go
index fdadb05..198878b 100644
--- a/cameras_types.go
+++ b/cameras_types.go
@@ -105,7 +105,8 @@ type CameraSchedule struct {
// UnmarshalXML stores a schedule ID into a CameraSchedule type.
// This isn't a method you should ever call directly; it is only used during data initialization.
func (bit *CameraSchedule) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- if err := d.DecodeElement(&bit.ID, &start); err != nil {
+ err := d.DecodeElement(&bit.ID, &start)
+ if err != nil {
return fmt.Errorf("decoding xml: %w", err)
}
@@ -340,7 +341,9 @@ type cameraXML struct {
//nolint:funlen // dual schema mapping
func (c *Camera) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var raw cameraXML
- if err := d.DecodeElement(&raw, &start); err != nil {
+
+ err := d.DecodeElement(&raw, &start)
+ if err != nil {
return fmt.Errorf("decoding camera xml: %w", err)
}
diff --git a/events.go b/events.go
index 756d88c..3740381 100644
--- a/events.go
+++ b/events.go
@@ -325,7 +325,8 @@ func (e *Events) eventStreamSelector(ctx context.Context, refreshOnConfigChange
}
func (e *Events) serverRefresh(ctx context.Context) {
- if err := e.server.RefreshContext(ctx); err != nil {
+ err := e.server.RefreshContext(ctx)
+ if err != nil {
e.custom(EventWatcherRefreshFail, -9997, -1, err.Error())
return
@@ -380,13 +381,15 @@ func (e *Events) UnmarshalEvent(text string) *Event {
eventTime = fmt.Sprintf("%v%+03.0f", parts[0], gmtOffset)
//nolint:gosmopolitan // The event stream uses the system's local time.
- if newEvent.When, err = time.ParseInLocation(EventTimeFormat+"-07", eventTime, time.Local); err != nil {
+ newEvent.When, err = time.ParseInLocation(EventTimeFormat+"-07", eventTime, time.Local)
+ if err != nil {
newEvent.When = time.Now()
newEvent.Errors = append(newEvent.Errors, ErrDateParseFail)
}
// Parse the ID
- if newEvent.ID, err = strconv.Atoi(parts[1]); err != nil {
+ newEvent.ID, err = strconv.Atoi(parts[1])
+ if err != nil {
newEvent.ID = BadID
newEvent.Errors = append(newEvent.Errors, ErrIDParseFail)
}
@@ -395,7 +398,8 @@ func (e *Events) UnmarshalEvent(text string) *Event {
parts[2] = strings.TrimPrefix(parts[2], "CAM")
if cams := e.server.GetCameras(); parts[2] != "X" && cams != nil {
- if cameraNum, err := strconv.Atoi(parts[2]); err != nil {
+ cameraNum, err := strconv.Atoi(parts[2])
+ if err != nil {
newEvent.Errors = append(newEvent.Errors, ErrCAMParseFail)
} else if newEvent.Camera = cams.ByNum(cameraNum); newEvent.Camera == nil {
newEvent.Errors = append(newEvent.Errors, ErrCAMMissing)
diff --git a/files.go b/files.go
index e85d296..baec330 100644
--- a/files.go
+++ b/files.go
@@ -17,8 +17,8 @@ import (
const (
// DownloadDateFormat is the format the SecuritySpy ++download method accepts.
// This matches the ++download inputs AND the folder names files are saved into.
- // The file1/file2 inputs this gets passed into are actually undocuemnted and were
- // created specifically for programmtic SDK access (ie. this library).
+ // The file1/file2 inputs this gets passed into are actually undocumented and were
+ // created specifically for programmatic SDK access (ie. this library).
DownloadDateFormat = "2006-01-02"
// FileDateFormat is an arbitrary date format used for saved files; we hope doesn't change.
// This is used in the actual name of files that are saved. No where else.
@@ -50,7 +50,7 @@ type fileFeed struct {
Entries []*File `xml:"entry"` // List of File pointers
}
-// File represents a saved media file. This is all the data retreived from
+// File represents a saved media file. This is all the data retrieved from
// the ++download method for a particular file. Contains a camera interface
// for the camera that created the file. All of the Files type methods return this type.
type File struct {
@@ -110,15 +110,25 @@ func (f *Files) GetFile(name string) (*File, error) {
}
cams := f.server.GetCameras()
- if fileExtSplit := strings.Split(name, "."); len(fileExtSplit) != fileParts {
+ fileExtSplit := strings.Split(name, ".")
+ if len(fileExtSplit) != fileParts {
return file, ErrInvalidName
- } else if nameDateSplit := strings.Split(fileExtSplit[0], " "); len(fileExtSplit) < fileParts {
- return file, ErrInvalidName
- } else if file.Updated, err = time.Parse(FileDateFormat, nameDateSplit[0]); err != nil {
+ }
+
+ nameDateSplit := strings.Split(fileExtSplit[0], " ")
+
+ file.Updated, err = time.Parse(FileDateFormat, nameDateSplit[0])
+ if err != nil {
return file, ErrInvalidName
- } else if file.Camera = cams.ByName(nameDateSplit[len(nameDateSplit)-1]); file.Camera == nil {
+ }
+
+ file.Camera = cams.ByName(nameDateSplit[len(nameDateSplit)-1])
+ if file.Camera == nil {
return file, ErrCAMMissing
- } else if file.Link.Type = "video/quicktime"; fileExtSplit[1] == "jpg" {
+ }
+
+ file.Link.Type = "video/quicktime"
+ if fileExtSplit[1] == "jpg" {
file.Link.Type = "image/jpeg"
}
@@ -134,7 +144,8 @@ func (f *Files) GetFile(name string) (*File, error) {
// Save downloads a saved media File from SecuritySpy and saves it to a local file.
// Returns an error if path exists.
func (f *File) Save(path string) (int64, error) {
- if _, err := os.Stat(path); !os.IsNotExist(err) {
+ _, statErr := os.Stat(path)
+ if !os.IsNotExist(statErr) {
return 0, ErrPathExists
}
@@ -187,7 +198,8 @@ func (f *Files) getFiles(cameraNums []int, start, end time.Time, fileTypes, cont
params = makeFilesParams(cameraNums, start, end, fileTypes, continuation)
)
- if err := f.server.GetXML("++download", params, &feed); err != nil {
+ err := f.server.GetXML("++download", params, &feed)
+ if err != nil {
return nil, fmt.Errorf("getting download: %w", err)
}
@@ -214,7 +226,7 @@ func (f *Files) getFiles(cameraNums []int, start, end time.Time, fileTypes, cont
return entries, nil
}
-// makeFilesParams makes the url Values for a file retreival.
+// makeFilesParams makes the url Values for a file retrieval.
func makeFilesParams(cameraNums []int, from, to time.Time, fileTypes, continuation string) url.Values {
params := make(url.Values)
params.Set("results", "1000")
diff --git a/internal/rtspclip/clip.go b/internal/rtspclip/clip.go
index d8a8b81..3c8d419 100644
--- a/internal/rtspclip/clip.go
+++ b/internal/rtspclip/clip.go
@@ -89,7 +89,8 @@ func SaveMP4(ctx context.Context, rtspURL, path string, opts Options) (*Result,
return nil, ErrBadPath
}
- if _, err := os.Stat(path); err == nil {
+ _, statErr := os.Stat(path)
+ if statErr == nil {
return nil, os.ErrExist
}
@@ -147,7 +148,8 @@ type cancelReadCloser struct {
func (c *cancelReadCloser) Close() error {
c.cancel()
- if err := c.ReadCloser.Close(); err != nil {
+ err := c.ReadCloser.Close()
+ if err != nil {
return fmt.Errorf("close stream: %w", err)
}
@@ -162,7 +164,8 @@ func capture(ctx context.Context, rtspURL string, opts Options, writer io.Writer
client := newRTSPClient(parsed, opts.InsecureSkipVerify)
- if err := client.Start(); err != nil {
+ err = client.Start()
+ if err != nil {
return nil, fmt.Errorf("rtsp start: %w", err)
}
@@ -186,22 +189,26 @@ func capture(ctx context.Context, rtspURL string, opts Options, writer io.Writer
state := &captureState{}
stop := makeStopper(state, cancel, client)
- if err := client.SetupAll(session.BaseURL, session.Medias); err != nil {
+ err = client.SetupAll(session.BaseURL, session.Medias)
+ if err != nil {
return nil, fmt.Errorf("rtsp setup: %w", err)
}
attachVideo(client, tracks, mux, state, opts, stop)
attachAudio(client, tracks, mux, state, opts, stop)
- if _, err := client.Play(nil); err != nil {
+ _, err = client.Play(nil)
+ if err != nil {
return nil, fmt.Errorf("rtsp play: %w", err)
}
- if err := waitCapture(captureCtx, client, state, stop); err != nil {
+ err = waitCapture(captureCtx, client, state, stop)
+ if err != nil {
return nil, err
}
- if err := mux.close(); err != nil {
+ err = mux.close()
+ if err != nil {
return nil, err
}
@@ -620,7 +627,8 @@ func (m *fmp4Muxer) writeVideo(accessUnit [][]byte, pts int64) (int, error) {
return 0, nil
}
- if err := m.ensureVideoStarted(idr, pts); err != nil {
+ err := m.ensureVideoStarted(idr, pts)
+ if err != nil {
return 0, err
}
@@ -674,7 +682,8 @@ func (m *fmp4Muxer) afterVideoSample() error {
return nil
}
- if err := m.maybeInitProgressive(); err != nil {
+ err := m.maybeInitProgressive()
+ if err != nil {
return err
}
@@ -787,8 +796,11 @@ func (m *fmp4Muxer) writeInit(includeAudio bool) error {
if err != nil {
return fmt.Errorf("hevc descriptor: %w", err)
}
- } else if err := init.Moov.Trak.SetAVCDescriptor("avc1", [][]byte{m.sps}, [][]byte{m.pps}, true); err != nil {
- return fmt.Errorf("avc descriptor: %w", err)
+ } else {
+ err := init.Moov.Trak.SetAVCDescriptor("avc1", [][]byte{m.sps}, [][]byte{m.pps}, true)
+ if err != nil {
+ return fmt.Errorf("avc descriptor: %w", err)
+ }
}
if includeAudio && m.aacFormat != nil && m.aacFormat.Config != nil && m.audioRate > 0 {
@@ -797,12 +809,14 @@ func (m *fmp4Muxer) writeInit(includeAudio bool) error {
traks := init.Moov.Traks
audioTrak := traks[len(traks)-1]
- if err := setMPEG4AudioDescriptor(audioTrak, m.aacFormat.Config); err != nil {
+ err := setMPEG4AudioDescriptor(audioTrak, m.aacFormat.Config)
+ if err != nil {
return err
}
}
- if err := init.Encode(m.w); err != nil {
+ err := init.Encode(m.w)
+ if err != nil {
return fmt.Errorf("write init: %w", err)
}
@@ -844,7 +858,9 @@ func (m *fmp4Muxer) maybeInitProgressive() error {
}
m.includeAudio = len(m.audioSamples) > 0 && m.aacFormat != nil && m.aacFormat.Config != nil
- if err := m.writeInit(m.includeAudio); err != nil {
+
+ err := m.writeInit(m.includeAudio)
+ if err != nil {
return err
}
@@ -881,20 +897,23 @@ func (m *fmp4Muxer) flushFragment() error {
seg.AddFragment(frag)
for _, sample := range m.videoSamples {
- if err := frag.AddFullSampleToTrack(sample, videoTrackID); err != nil {
+ err := frag.AddFullSampleToTrack(sample, videoTrackID)
+ if err != nil {
return fmt.Errorf("add video sample: %w", err)
}
}
if m.includeAudio {
for _, sample := range m.audioSamples {
- if err := frag.AddFullSampleToTrack(sample, audioTrackID); err != nil {
+ err := frag.AddFullSampleToTrack(sample, audioTrackID)
+ if err != nil {
return fmt.Errorf("add audio sample: %w", err)
}
}
}
- if err := seg.Encode(m.w); err != nil {
+ err = seg.Encode(m.w)
+ if err != nil {
return fmt.Errorf("encode segment: %w", err)
}
@@ -914,7 +933,8 @@ func (m *fmp4Muxer) close() error {
}
if m.progressive {
- if err := m.maybeInitProgressive(); err != nil {
+ err := m.maybeInitProgressive()
+ if err != nil {
return err
}
@@ -930,7 +950,8 @@ func (m *fmp4Muxer) close() error {
m.hadAudio = true
}
- if err := m.writeInit(includeAudio); err != nil {
+ err := m.writeInit(includeAudio)
+ if err != nil {
return err
}
diff --git a/internal/rtspclip/clip_test.go b/internal/rtspclip/clip_test.go
index 350eea8..d14a47d 100644
--- a/internal/rtspclip/clip_test.go
+++ b/internal/rtspclip/clip_test.go
@@ -46,7 +46,6 @@ func TestMonoAAC64kASC(t *testing.T) {
cfg := mpeg4audio.AudioSpecificConfig{
Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 64000,
- ChannelCount: 1,
ChannelConfig: 1,
}
asc, err := cfg.Marshal()
@@ -61,7 +60,6 @@ func TestHighSampleRateASCStillMarshals(t *testing.T) {
cfg := mpeg4audio.AudioSpecificConfig{
Type: mpeg4audio.ObjectTypeAACLC,
SampleRate: 96000,
- ChannelCount: 2,
ChannelConfig: 2,
}
asc, err := cfg.Marshal()
diff --git a/ptz.go b/ptz.go
index b419922..d6326c3 100644
--- a/ptz.go
+++ b/ptz.go
@@ -198,24 +198,11 @@ func (z *PTZ) Stop() error {
return z.ptzReq(ptzCommandStop)
}
-/* INTERFACE HELPER METHODS FOLLOW */
-
-// ptzReq wraps all the ptz-specific calls.
-func (z *PTZ) ptzReq(command ptzCommand) error {
- params := make(url.Values)
- params.Set("command", strconv.Itoa(int(command)))
-
- if err := z.camera.server.SimpleReq("++ptz/command", params, z.camera.Number); err != nil {
- return fmt.Errorf("ptz failed: %w", err)
- }
-
- return nil
-}
-
-// UnmarshalXML method converts ptzCapbilities bitmask from an XML payload into true/false abilities.
+// UnmarshalXML method converts ptzCapabilities bitmask from an XML payload into true/false abilities.
// This isn't a method you should ever call directly; it is only used during data initialization.
func (z *PTZ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- if err := d.DecodeElement(&z.rawCaps, &start); err != nil {
+ err := d.DecodeElement(&z.rawCaps, &start)
+ if err != nil {
return fmt.Errorf("ptz caps: %w", err)
}
@@ -228,3 +215,16 @@ func (z *PTZ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
+
+// ptzReq wraps all the ptz-specific calls.
+func (z *PTZ) ptzReq(command ptzCommand) error {
+ params := make(url.Values)
+ params.Set("command", strconv.Itoa(int(command)))
+
+ err := z.camera.server.SimpleReq("++ptz/command", params, z.camera.Number)
+ if err != nil {
+ return fmt.Errorf("ptz failed: %w", err)
+ }
+
+ return nil
+}
diff --git a/schedules.go b/schedules.go
index a76c618..20bbd2d 100644
--- a/schedules.go
+++ b/schedules.go
@@ -46,7 +46,8 @@ func (m *ScheduleContainer) UnmarshalXML(decoder *xml.Decoder, start xml.StartEl
switch xmlToken := token.(type) {
case xml.StartElement:
- if err = decoder.DecodeElement(&schedule, &xmlToken); err != nil {
+ err = decoder.DecodeElement(&schedule, &xmlToken)
+ if err != nil {
return fmt.Errorf("XML decode: %w", err)
}
@@ -65,7 +66,8 @@ func (s *Server) SetSchedulePreset(presetID int) error {
params := make(url.Values)
params.Set("id", strconv.Itoa(presetID))
- if err := s.SimpleReq("++ssSetPreset", params, -1); err != nil {
+ err := s.SimpleReq("++ssSetPreset", params, -1)
+ if err != nil {
return fmt.Errorf("http request: %w", err)
}
diff --git a/schedules_test.go b/schedules_test.go
index f37b011..69ebb0e 100644
--- a/schedules_test.go
+++ b/schedules_test.go
@@ -24,14 +24,14 @@ func TestSetSchedulePreset(t *testing.T) {
func TestUnmarshalXMLscheduleContainer(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
var schedule securityspy.ScheduleContainer
err := xml.Unmarshal([]byte(testScheduleList), &schedule)
require.NoError(t, err, "valid data must not produce an error")
- asert.Equal("Armed 24/7", schedule[1], "the scheduleContainer data did not unmarshal properly")
- asert.Len(schedule, 6)
+ check.Equal("Armed 24/7", schedule[1], "the scheduleContainer data did not unmarshal properly")
+ check.Len(schedule, 6)
err = xml.Unmarshal([]byte(""), &schedule)
require.Error(t, err, "invalid data must produce an error")
diff --git a/securityspy.go b/securityspy.go
index 69626bd..9794603 100644
--- a/securityspy.go
+++ b/securityspy.go
@@ -21,9 +21,9 @@ func New(c *server.Config) (*Server, error) {
return s, s.RefreshContext(context.Background()) //nolint:gocritic
}
-// NewMust returns an iterface to interact with SecuritySpy.
+// NewMust returns an interface to interact with SecuritySpy.
// This does not attempt to connect to SecuritySpy first.
-// You must call s.Refresh() before attempting to access other datas.
+// You must call s.Refresh() before attempting to access other data.
func NewMust(config *server.Config) *Server {
if !strings.HasSuffix(config.URL, "/") {
config.URL += "/"
@@ -169,7 +169,8 @@ func (s *Server) GetScripts() ([]string, error) {
Names []string `xml:"name"`
}
- if err := s.GetXML("++scripts", nil, &val); err != nil {
+ err := s.GetXML("++scripts", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting scripts: %w", err)
}
@@ -183,7 +184,8 @@ func (s *Server) GetSounds() ([]string, error) {
Names []string `xml:"name"`
}
- if err := s.GetXML("++sounds", nil, &val); err != nil {
+ err := s.GetXML("++sounds", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting sounds: %w", err)
}
diff --git a/securityspy_test.go b/securityspy_test.go
index a9fda0a..f5f7f39 100644
--- a/securityspy_test.go
+++ b/securityspy_test.go
@@ -23,14 +23,14 @@ const (
func TestGetServer(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
URL := "http://127.0.0.1:5678"
user := "user123"
pass := "pass456"
secspyServer, err := securityspy.New(&server.Config{Username: user, Password: pass, URL: URL, VerifySSL: true})
require.Error(t, err, "there is no server at the address provided so an error must exist")
- asert.NotNil(secspyServer, "server must not be nil. even wiuth an error it must be returned")
+ check.NotNil(secspyServer, "server must not be nil. even with an error it must be returned")
if !strings.Contains(err.Error(), "target machine actively refused it") &&
!strings.Contains(err.Error(), "connection refused") {
@@ -62,26 +62,26 @@ func TestRefresh(t *testing.T) {
}))
defer fakeServer.Close()
- asert := assert.New(t)
+ check := assert.New(t)
secspyServer := securityspy.NewMust(
&server.Config{Username: userStr, Password: passStr, URL: fakeServer.URL + "/", VerifySSL: false})
require.NoError(t, secspyServer.Refresh(),
"an error must not be returned while testing with valid XML")
// Make sure Refresh() did all the things it is supposed to do.
- asert.WithinDuration(time.Now(), secspyServer.Info.Refreshed, time.Second,
+ check.WithinDuration(time.Now(), secspyServer.Info.Refreshed, time.Second,
"Refreshed field must be updated by Refresh() method")
// Test that the data was unmarshalled properly.
// These tests assume the test data does not change.
- asert.Equal("SecuritySpy", secspyServer.Info.Name, "the server's name was not properly unmarshalled")
- asert.Equal("2019-02-10T15:53:23", secspyServer.Info.CurrentTime.Format("2006-01-02T15:04:05"),
+ check.Equal("SecuritySpy", secspyServer.Info.Name, "the server's name was not properly unmarshalled")
+ check.Equal("2019-02-10T15:53:23", secspyServer.Info.CurrentTime.Format("2006-01-02T15:04:05"),
"the server's current time was not properly unmarshalled")
- asert.Equal(2304, secspyServer.Cameras.ByNum(1).Width, "camera info was not properly unmarshalled")
- asert.Equal("Road", secspyServer.Cameras.ByNum(2).Name, "camera info was not properly unmarshalled")
- asert.Equal("Unarmed 24/7", secspyServer.Info.ServerSchedules[0], "schedule info was not properly unmarshalled")
- asert.Equal("None", secspyServer.Info.ScheduleOverrides[0], "schedule override info was not properly unmarshalled")
- asert.Equal("MyFirstPreset", secspyServer.Info.SchedulePresets[1930238093],
+ check.Equal(2304, secspyServer.Cameras.ByNum(1).Width, "camera info was not properly unmarshalled")
+ check.Equal("Road", secspyServer.Cameras.ByNum(2).Name, "camera info was not properly unmarshalled")
+ check.Equal("Unarmed 24/7", secspyServer.Info.ServerSchedules[0], "schedule info was not properly unmarshalled")
+ check.Equal("None", secspyServer.Info.ScheduleOverrides[0], "schedule override info was not properly unmarshalled")
+ check.Equal("MyFirstPreset", secspyServer.Info.SchedulePresets[1930238093],
"schedule preset info was not properly unmarshalled")
// second call returns an HTTP error response.
@@ -111,14 +111,14 @@ func TestGetSounds(t *testing.T) { //nolint:dupl // it just looks like a duplica
}))
defer fakeServer.Close()
- asert := assert.New(t)
+ check := assert.New(t)
secspyServer := securityspy.NewMust(
&server.Config{Username: userStr, Password: passStr, URL: fakeServer.URL + "/", VerifySSL: false})
sounds, err := secspyServer.GetSounds()
require.NoError(t, err, "the method must not return an error when given valid XML to unmarshal")
- asert.Len(sounds, 20, "all 20 sounds must exist in the slice")
- asert.Equal("Beeps.aif", sounds[0], "the sound files were not properly unmarhsalled")
+ check.Len(sounds, 20, "all 20 sounds must exist in the slice")
+ check.Equal("Beeps.aif", sounds[0], "the sound files were not properly unmarshalled")
_, err = secspyServer.GetSounds()
require.Error(t, err)
@@ -147,14 +147,14 @@ func TestGetScripts(t *testing.T) { //nolint:dupl // it just looks like a duplic
}))
defer fakeServer.Close()
- asert := assert.New(t)
+ check := assert.New(t)
secspyServer := securityspy.NewMust(
&server.Config{Username: userStr, Password: passStr, URL: fakeServer.URL + "/", VerifySSL: false})
scripts, err := secspyServer.GetScripts()
require.NoError(t, err, "the method must not return an error when given valid XML to unmarshal")
- asert.Len(scripts, 16, "all 16 scripts must exist in the slice")
- asert.Equal("Web-i Activate Relay 1.scpt", scripts[0], "the script files were not properly unmarhsalled")
+ check.Len(scripts, 16, "all 16 scripts must exist in the slice")
+ check.Equal("Web-i Activate Relay 1.scpt", scripts[0], "the script files were not properly unmarshalled")
_, err = secspyServer.GetScripts()
require.Error(t, err)
@@ -163,7 +163,7 @@ func TestGetScripts(t *testing.T) { //nolint:dupl // it just looks like a duplic
func TestUnmarshalXMLYesNoBool(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
good := []string{"true", "yes", "1", "armed", "active", "enabled"}
fail := []string{"anything", "else", "returns", "false", "including", "no", "0", "disarmed", "inactive", "disabled"}
@@ -171,38 +171,38 @@ func TestUnmarshalXMLYesNoBool(t *testing.T) {
for _, val := range good {
require.NoError(t, xml.Unmarshal([]byte(""+val+""), &bit), "unmarshalling must not produce an error")
- asert.True(bit.Val, "the value must unmarshal to true")
- asert.Equal(val, bit.Txt, "the value was not unmarshalled correctly")
+ check.True(bit.Val, "the value must unmarshal to true")
+ check.Equal(val, bit.Txt, "the value was not unmarshalled correctly")
}
for _, val := range fail {
require.NoError(t, xml.Unmarshal([]byte(""+val+""), &bit), "unmarshalling must not produce an error")
- asert.False(bit.Val, "the value must unmarshal to false")
- asert.Equal(val, bit.Txt, "the value was not unmarshalled correctly")
+ check.False(bit.Val, "the value must unmarshal to false")
+ check.Equal(val, bit.Txt, "the value was not unmarshalled correctly")
}
}
func TestUnmarshalXMLDuration(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
good := []string{"1", "20", "300", "4000", "50000", "666666"}
var bit securityspy.Duration
for _, val := range good {
require.NoError(t, xml.Unmarshal([]byte(""+val+""), &bit), "unmarshalling must not produce an error")
- asert.Equal(val, bit.Val, "the value was not unmarshalled correctly")
+ check.Equal(val, bit.Val, "the value was not unmarshalled correctly")
num, err := strconv.ParseFloat(val, 64)
require.NoError(t, err, "must not be an error parsing test numbers")
- asert.InDelta(num, bit.Seconds(), 0.000001, "the value was not unmarshalled correctly")
- asert.Equal(val, bit.Val, "the value was not unmarshalled correctly")
+ check.InDelta(num, bit.Seconds(), 0.000001, "the value was not unmarshalled correctly")
+ check.Equal(val, bit.Val, "the value was not unmarshalled correctly")
}
// Test empty value.
require.NoError(t, xml.Unmarshal([]byte(""), &bit), "unmarshalling must not produce an error")
- asert.Empty(bit.Val, "the value was not unmarshalled correctly")
- asert.Equal(int64(-1), bit.Nanoseconds(), "an empty value must produce -1 nano second.")
+ check.Empty(bit.Val, "the value was not unmarshalled correctly")
+ check.Equal(int64(-1), bit.Nanoseconds(), "an empty value must produce -1 nano second.")
}
func TestRefreshHandlesNilPTZAndMissingSchedules(t *testing.T) {
diff --git a/server/server.go b/server/server.go
index cbc261b..06d4119 100644
--- a/server/server.go
+++ b/server/server.go
@@ -244,14 +244,15 @@ func (s *Config) GetXMLContext(ctx context.Context, apiPath string, params url.V
s.Username, s.URL+apiPath, resp.StatusCode, resp.Status, err, string(body))
}
- if err = xml.NewDecoder(resp.Body).Decode(val); err != nil {
+ err = xml.NewDecoder(resp.Body).Decode(val)
+ if err != nil {
return fmt.Errorf("reading body: %w", err)
}
return nil
}
-// SimpleReq performes HTTP req, checks for OK at end of output.
+// SimpleReq performs HTTP req, checks for OK at end of output.
func (s *Config) SimpleReq(apiURI string, params url.Values, cameraNum int) error {
ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutDur())
defer cancel()
@@ -259,7 +260,7 @@ func (s *Config) SimpleReq(apiURI string, params url.Values, cameraNum int) erro
return s.SimpleReqContext(ctx, apiURI, params, cameraNum)
}
-// SimpleReqContext performes HTTP req, checks for OK at end of output.
+// SimpleReqContext performs HTTP req, checks for OK at end of output.
func (s *Config) SimpleReqContext(ctx context.Context, apiURI string, params url.Values, cameraNum int) error {
if cameraNum >= 0 {
params.Set("cameraNum", strconv.Itoa(cameraNum))
diff --git a/server/server_test.go b/server/server_test.go
index 07c3b2d..e2dbe78 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -25,7 +25,7 @@ const (
func TestGet(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
config := &server.Config{
Username: userStr,
Password: passStr,
@@ -34,13 +34,13 @@ func TestGet(t *testing.T) {
Timeout: server.Duration{time.Second},
}
handler := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- asert.Equal("xml", req.URL.Query().Get("format"), "format parameter was not added")
- asert.Equal(config.Password, req.URL.Query().Get("auth"), "auth parameter was not added")
- asert.Equal("application/xml", req.Header.Get("Accept"), "accept header is not correct")
+ check.Equal("xml", req.URL.Query().Get("format"), "format parameter was not added")
+ check.Equal(config.Password, req.URL.Query().Get("auth"), "auth parameter was not added")
+ check.Equal("application/xml", req.Header.Get("Accept"), "accept header is not correct")
_, err := resp.Write([]byte("request OK"))
- asert.NoError(err, "the fake server must return an error writing to the client")
- asert.Equal(config.URL, "http://"+req.Host+"/", "the host was not set correctly in the request")
+ check.NoError(err, "the fake server must return an error writing to the client")
+ check.Equal(config.URL, "http://"+req.Host+"/", "the host was not set correctly in the request")
})
httpClient, fakeServer := testingHTTPClient(handler)
@@ -53,17 +53,17 @@ func TestGet(t *testing.T) {
if err == nil {
defer resp.Body.Close()
- asert.Equal(http.StatusOK, resp.StatusCode, "the server must return a 200 response code")
+ check.Equal(http.StatusOK, resp.StatusCode, "the server must return a 200 response code")
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "must not be an error reading the response body")
- asert.Equal("request OK", string(body), "wrong data was returned from the server")
+ check.Equal("request OK", string(body), "wrong data was returned from the server")
}
}
func TestPostAudioContentType(t *testing.T) {
t.Parallel()
- asert := assert.New(t)
+ check := assert.New(t)
config := &server.Config{
Username: userStr,
Password: passStr,
@@ -86,7 +86,7 @@ func TestPostAudioContentType(t *testing.T) {
}
_, err := resp.Write([]byte("OK"))
- asert.NoError(err)
+ check.NoError(err)
})
httpClient, fakeServer := testingHTTPClient(handler)
@@ -96,14 +96,14 @@ func TestPostAudioContentType(t *testing.T) {
body, err := config.Post("++audio", nil, io.NopCloser(strings.NewReader("audio-data")))
require.NoError(t, err)
- asert.Equal("OK", string(body))
+ check.Equal("OK", string(body))
body, err = config.Post("++other", nil, io.NopCloser(strings.NewReader("other-data")))
require.NoError(t, err)
- asert.Equal("OK", string(body))
+ check.Equal("OK", string(body))
- asert.Equal("audio/g711-ulaw", audioHeader)
- asert.Empty(otherHeader)
+ check.Equal("audio/g711-ulaw", audioHeader)
+ check.Empty(otherHeader)
}
func TestGetXMLStatusAndDecodeErrors(t *testing.T) {
diff --git a/settings.go b/settings.go
index 5d25b58..3bd1b46 100644
--- a/settings.go
+++ b/settings.go
@@ -13,7 +13,9 @@ var ErrCameraNumRequired = errors.New("cameraNum required")
// GetGeneralSettings fetches ++settings-general.
func (s *Server) GetGeneralSettings() (*GeneralSettings, error) {
var val GeneralSettings
- if err := s.GetXML("++settings-general", nil, &val); err != nil {
+
+ err := s.GetXML("++settings-general", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting general settings: %w", err)
}
@@ -22,7 +24,8 @@ func (s *Server) GetGeneralSettings() (*GeneralSettings, error) {
// SetGeneralSettings posts form fields to ++settings-general (partial update).
func (s *Server) SetGeneralSettings(form url.Values) error {
- if err := s.PostForm("++settings-general", form); err != nil {
+ err := s.PostForm("++settings-general", form)
+ if err != nil {
return fmt.Errorf("setting general settings: %w", err)
}
@@ -32,7 +35,9 @@ func (s *Server) SetGeneralSettings(form url.Values) error {
// GetDisplaySettings fetches ++settings-display.
func (s *Server) GetDisplaySettings() (*DisplaySettings, error) {
var val DisplaySettings
- if err := s.GetXML("++settings-display", nil, &val); err != nil {
+
+ err := s.GetXML("++settings-display", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting display settings: %w", err)
}
@@ -41,7 +46,8 @@ func (s *Server) GetDisplaySettings() (*DisplaySettings, error) {
// SetDisplaySettings posts form fields to ++settings-display (partial update).
func (s *Server) SetDisplaySettings(form url.Values) error {
- if err := s.PostForm("++settings-display", form); err != nil {
+ err := s.PostForm("++settings-display", form)
+ if err != nil {
return fmt.Errorf("setting display settings: %w", err)
}
@@ -51,7 +57,9 @@ func (s *Server) SetDisplaySettings(form url.Values) error {
// GetStorageSettings fetches ++settings-storage.
func (s *Server) GetStorageSettings() (*StorageSettings, error) {
var val StorageSettings
- if err := s.GetXML("++settings-storage", nil, &val); err != nil {
+
+ err := s.GetXML("++settings-storage", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting storage settings: %w", err)
}
@@ -60,7 +68,8 @@ func (s *Server) GetStorageSettings() (*StorageSettings, error) {
// SetStorageSettings posts form fields to ++settings-storage (partial update).
func (s *Server) SetStorageSettings(form url.Values) error {
- if err := s.PostForm("++settings-storage", form); err != nil {
+ err := s.PostForm("++settings-storage", form)
+ if err != nil {
return fmt.Errorf("setting storage settings: %w", err)
}
@@ -70,7 +79,9 @@ func (s *Server) SetStorageSettings(form url.Values) error {
// GetCompressionSettings fetches ++settings-compression.
func (s *Server) GetCompressionSettings() (*CompressionSettings, error) {
var val CompressionSettings
- if err := s.GetXML("++settings-compression", nil, &val); err != nil {
+
+ err := s.GetXML("++settings-compression", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting compression settings: %w", err)
}
@@ -79,7 +90,8 @@ func (s *Server) GetCompressionSettings() (*CompressionSettings, error) {
// SetCompressionSettings posts form fields to ++settings-compression (partial update).
func (s *Server) SetCompressionSettings(form url.Values) error {
- if err := s.PostForm("++settings-compression", form); err != nil {
+ err := s.PostForm("++settings-compression", form)
+ if err != nil {
return fmt.Errorf("setting compression settings: %w", err)
}
@@ -89,7 +101,9 @@ func (s *Server) SetCompressionSettings(form url.Values) error {
// GetEmailSettings fetches ++settings-email.
func (s *Server) GetEmailSettings() (*EmailSettings, error) {
var val EmailSettings
- if err := s.GetXML("++settings-email", nil, &val); err != nil {
+
+ err := s.GetXML("++settings-email", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting email settings: %w", err)
}
@@ -98,7 +112,8 @@ func (s *Server) GetEmailSettings() (*EmailSettings, error) {
// SetEmailSettings posts form fields to ++settings-email (partial update).
func (s *Server) SetEmailSettings(form url.Values) error {
- if err := s.PostForm("++settings-email", form); err != nil {
+ err := s.PostForm("++settings-email", form)
+ if err != nil {
return fmt.Errorf("setting email settings: %w", err)
}
@@ -108,7 +123,9 @@ func (s *Server) SetEmailSettings(form url.Values) error {
// GetWebSettings fetches ++settings-web.
func (s *Server) GetWebSettings() (*WebSettings, error) {
var val WebSettings
- if err := s.GetXML("++settings-web", nil, &val); err != nil {
+
+ err := s.GetXML("++settings-web", nil, &val)
+ if err != nil {
return nil, fmt.Errorf("getting web settings: %w", err)
}
@@ -117,7 +134,8 @@ func (s *Server) GetWebSettings() (*WebSettings, error) {
// SetWebSettings posts form fields to ++settings-web (partial update).
func (s *Server) SetWebSettings(form url.Values) error {
- if err := s.PostForm("++settings-web", form); err != nil {
+ err := s.PostForm("++settings-web", form)
+ if err != nil {
return fmt.Errorf("setting web settings: %w", err)
}
@@ -130,7 +148,9 @@ func (s *Server) GetCameraSettings(cameraNum int) (*CameraSettings, error) {
params.Set("cameraNum", strconv.Itoa(cameraNum))
var val CameraSettings
- if err := s.GetXML("++settings-cameras", params, &val); err != nil {
+
+ err := s.GetXML("++settings-cameras", params, &val)
+ if err != nil {
return nil, fmt.Errorf("getting camera settings: %w", err)
}
@@ -144,7 +164,8 @@ func (s *Server) SetCameraSettings(form url.Values) error {
return ErrCameraNumRequired
}
- if err := s.PostForm("++settings-cameras", form); err != nil {
+ err := s.PostForm("++settings-cameras", form)
+ if err != nil {
return fmt.Errorf("setting camera settings: %w", err)
}