mirror of
https://github.com/kerberos-io/onvif.git
synced 2026-08-23 15:08:33 +00:00
Audit against the standard 'default to no comments; only add one when
the WHY is non-obvious'. Net: 238 lines removed across 8 files, no
behaviour change, tests still pass -race.
What went
---------
* Section banners (// ---------- Motion ----------): noise once
per-rule citations exist.
* Per-rule 'Data: IsMotion (xsd:boolean)' wire-format lines in
topics.go: that's WHAT; the spec citation carries WHY.
* Per-field doc on Event struct restating each field name (// Kind
is the normalized event category) and the type-doc preamble.
* Stringer doc comments ('// String implements fmt.Stringer.') and
similar conventional-method noise.
* 'Used by ErrPullFailed / ErrRenewFailed / ErrRecreateFailed' in
the Op doc — the rule-named anti-pattern.
* doc.go Invariants and Reconnect sections duplicating per-function
docs.
* Internal helper doc-comments restating what the function does
(surfaceError, run, simpleItemsToMap first sentence, etc.).
What stayed
-----------
* Every spec / vendor-doc citation in topics.go.
* Race-condition WHY in stream.go run() close ordering.
* Workaround WHY in renew.go (absolute datetime vs duration).
* WS-BaseNotification UTC rationale + vendor format list in
decode.go.
* Fleet-sizing and thundering-herd rationale in reconnect.go.
* Stream consumer invariants (NewStream synchronous I/O, Errors
non-blocking, Close idempotent + bounded).
The change matches the codebase's stated style (CLAUDE.md): WHY only,
no WHAT, no cross-file references, no current-task narration.
59 lines
1.6 KiB
Go
59 lines
1.6 KiB
Go
package stream
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/kerberos-io/onvif/event"
|
|
"github.com/kerberos-io/onvif/xsd"
|
|
)
|
|
|
|
// renewLoop surfaces renew failures and continues. A permanently
|
|
// failing renew lets the subscription die at the camera; the pull
|
|
// loop's reconnect path then recreates it — recreate is the only
|
|
// reliable recovery once a subscription is GC'd.
|
|
func (s *Stream) renewLoop(ctx context.Context) {
|
|
interval := s.opts.InitialTermination - s.opts.RenewMargin
|
|
if interval <= 0 {
|
|
// Pathological config (margin >= termination): renew at
|
|
// half termination so we still refresh.
|
|
interval = s.opts.InitialTermination / 2
|
|
if interval <= 0 {
|
|
interval = time.Second
|
|
}
|
|
}
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
if err := renewPullPoint(s.caller, s.getPullPoint(), s.opts); err != nil {
|
|
s.surfaceError(ErrRenewFailed{Err: err})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// renewPullPoint sends Renew with an absolute UTC TerminationTime.
|
|
// WS-BaseNotification §6.1.1 also allows xsd:duration but older
|
|
// Hikvision, some Dahua and some Bosch firmwares reject the
|
|
// relative form.
|
|
func renewPullPoint(c caller, endpoint string, opts Options) error {
|
|
absoluteEnd := time.Now().UTC().Add(opts.InitialTermination).Format("2006-01-02T15:04:05Z")
|
|
req := event.Renew{TerminationTime: xsd.String(absoluteEnd)}
|
|
body, err := xml.Marshal(req)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal Renew: %w", err)
|
|
}
|
|
resp, err := c.SendSoap(endpoint, string(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = readClose(resp)
|
|
return err
|
|
}
|