Fix all linter issues: errcheck, staticcheck, and unused code

- Fix critical scanner.go bug: ineffective break in select (SA4011)
  Use labeled break to properly exit loop on context cancellation

- Add error checking for all file.Close() and resp.Body.Close()
  Prevent resource leaks in loader, onvif_simple, and tester

- Add error checking for fmt.Sscanf() calls in tester.go
  Prevent silent parse failures for FPS and bitrate extraction

- Add error checking for all SSE streamWriter calls
  Explicit ignore with _ = for SendJSON and SendError

- Remove unused sync.RWMutex field from SearchEngine

- Refactor if/else to switch for CodecType (staticcheck QF1003)
  More idiomatic Go code in stream tester

All 20 linter issues resolved. Code compiles and runs correctly.
This commit is contained in:
eduard256
2025-11-12 11:17:12 +03:00
parent 94f94a9f8c
commit 86a8fb36d5
7 changed files with 32 additions and 31 deletions
+2 -2
View File
@@ -103,7 +103,7 @@ func (h *DiscoverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
result, err := h.scanner.Scan(r.Context(), req, streamWriter)
if err != nil {
h.logger.Error("discovery failed", err)
streamWriter.SendError(err)
_ = streamWriter.SendError(err)
return
}
@@ -126,5 +126,5 @@ func (h *DiscoverHandler) sendErrorResponse(w http.ResponseWriter, message strin
"code": statusCode,
}
json.NewEncoder(w).Encode(response)
_ = json.NewEncoder(w).Encode(response)
}
+1 -1
View File
@@ -95,5 +95,5 @@ func (h *SearchHandler) sendErrorResponse(w http.ResponseWriter, message string,
"code": statusCode,
}
json.NewEncoder(w).Encode(response)
_ = json.NewEncoder(w).Encode(response)
}
+4 -4
View File
@@ -52,7 +52,7 @@ func (l *Loader) LoadBrand(brandID string) (*models.Camera, error) {
}
return nil, fmt.Errorf("failed to open brand file: %w", err)
}
defer file.Close()
defer func() { _ = file.Close() }()
var camera models.Camera
decoder := json.NewDecoder(file)
@@ -104,7 +104,7 @@ func (l *Loader) LoadPopularPatterns() ([]models.StreamPattern, error) {
if err != nil {
return nil, fmt.Errorf("failed to open patterns file: %w", err)
}
defer file.Close()
defer func() { _ = file.Close() }()
var patterns []models.StreamPattern
decoder := json.NewDecoder(file)
@@ -133,7 +133,7 @@ func (l *Loader) LoadQueryParameters() ([]string, error) {
if err != nil {
return nil, fmt.Errorf("failed to open parameters file: %w", err)
}
defer file.Close()
defer func() { _ = file.Close() }()
var params []string
decoder := json.NewDecoder(file)
@@ -187,7 +187,7 @@ func (l *Loader) loadCameraFromFile(filePath string) (*models.Camera, error) {
if err != nil {
return nil, err
}
defer file.Close()
defer func() { _ = file.Close() }()
var camera models.Camera
decoder := json.NewDecoder(file)
-1
View File
@@ -15,7 +15,6 @@ import (
type SearchEngine struct {
loader *Loader
logger interface{ Debug(string, ...any); Error(string, error, ...any) }
mu sync.RWMutex
}
// NewSearchEngine creates a new search engine
+2 -2
View File
@@ -198,7 +198,7 @@ func (o *ONVIFDiscovery) getProfileStreams(ctx context.Context, dev *onvif.Devic
"elapsed", elapsed.String())
return streams
}
defer profilesResp.Body.Close()
defer func() { _ = profilesResp.Body.Close() }()
o.logger.Debug("✅ GetProfiles call successful",
"elapsed", elapsed.String(),
@@ -311,7 +311,7 @@ func (o *ONVIFDiscovery) getStreamURI(dev *onvif.Device, profileToken string) st
"elapsed", elapsed.String())
return ""
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
o.logger.Debug("✅ GetStreamUri call successful",
"profile", profileToken,
+13 -12
View File
@@ -92,7 +92,7 @@ func (s *Scanner) Scan(ctx context.Context, req models.StreamDiscoveryRequest, s
)
// Send initial message
streamWriter.SendJSON("scan_started", map[string]interface{}{
_ = streamWriter.SendJSON("scan_started", map[string]interface{}{
"target": req.Target,
"model": req.Model,
"max_streams": req.MaxStreams,
@@ -108,7 +108,7 @@ func (s *Scanner) Scan(ctx context.Context, req models.StreamDiscoveryRequest, s
ip := s.extractIP(req.Target)
if ip == "" {
err := fmt.Errorf("invalid target IP: %s", req.Target)
streamWriter.SendError(err)
_ = streamWriter.SendError(err)
result.Error = err
return result, err
}
@@ -116,7 +116,7 @@ func (s *Scanner) Scan(ctx context.Context, req models.StreamDiscoveryRequest, s
// Collect all streams to test (includes metadata like type)
streams, err := s.collectStreams(scanCtx, req, ip)
if err != nil {
streamWriter.SendError(err)
_ = streamWriter.SendError(err)
result.Error = err
return result, err
}
@@ -124,7 +124,7 @@ func (s *Scanner) Scan(ctx context.Context, req models.StreamDiscoveryRequest, s
s.logger.Info("collected streams for testing", "count", len(streams))
// Send progress update
streamWriter.SendJSON("progress", models.ProgressMessage{
_ = streamWriter.SendJSON("progress", models.ProgressMessage{
Tested: 0,
Found: 0,
Remaining: len(streams),
@@ -137,14 +137,14 @@ func (s *Scanner) Scan(ctx context.Context, req models.StreamDiscoveryRequest, s
result.Duration = time.Since(startTime)
// Send completion message
streamWriter.SendJSON("complete", models.CompleteMessage{
_ = streamWriter.SendJSON("complete", models.CompleteMessage{
TotalTested: result.TotalTested,
TotalFound: result.TotalFound,
Duration: result.Duration.Seconds(),
})
// Send final done event to signal proper stream closure
streamWriter.SendJSON("done", map[string]interface{}{
_ = streamWriter.SendJSON("done", map[string]interface{}{
"message": "Stream discovery finished",
})
@@ -196,11 +196,11 @@ func (s *Scanner) scanDirectStream(ctx context.Context, req models.StreamDiscove
result.Streams = append(result.Streams, discoveredStream)
// Send to SSE
streamWriter.SendJSON("stream_found", map[string]interface{}{
_ = streamWriter.SendJSON("stream_found", map[string]interface{}{
"stream": discoveredStream,
})
} else {
streamWriter.SendJSON("stream_failed", map[string]interface{}{
_ = streamWriter.SendJSON("stream_failed", map[string]interface{}{
"url": req.Target,
"error": testResult.Error,
})
@@ -422,7 +422,7 @@ func (s *Scanner) testStreamsConcurrently(ctx context.Context, streams []models.
currentTested := atomic.LoadInt32(&tested)
// Only send if there's been progress
if currentTested != lastTested {
streamWriter.SendJSON("progress", models.ProgressMessage{
_ = streamWriter.SendJSON("progress", models.ProgressMessage{
Tested: int(currentTested),
Found: int(atomic.LoadInt32(&found)),
Remaining: len(streams) - int(currentTested),
@@ -439,12 +439,12 @@ func (s *Scanner) testStreamsConcurrently(ctx context.Context, streams []models.
result.Streams = append(result.Streams, stream)
// Send to SSE
streamWriter.SendJSON("stream_found", map[string]interface{}{
_ = streamWriter.SendJSON("stream_found", map[string]interface{}{
"stream": stream,
})
// Send progress (immediate update when stream is found)
streamWriter.SendJSON("progress", models.ProgressMessage{
_ = streamWriter.SendJSON("progress", models.ProgressMessage{
Tested: int(atomic.LoadInt32(&tested)),
Found: int(atomic.LoadInt32(&found)),
Remaining: len(streams) - int(atomic.LoadInt32(&tested)),
@@ -458,12 +458,13 @@ func (s *Scanner) testStreamsConcurrently(ctx context.Context, streams []models.
}()
// Test each stream
TestLoop:
for _, streamToTest := range streams {
// Check if context is done or max streams reached
select {
case <-ctx.Done():
s.logger.Debug("scan cancelled or timeout")
break
break TestLoop
default:
}
+10 -9
View File
@@ -278,7 +278,8 @@ func (t *Tester) testRTSP(ctx context.Context, streamURL string, result *TestRes
result.Type = "FFMPEG"
for _, stream := range probeResult.Streams {
if stream.CodecType == "video" {
switch stream.CodecType {
case "video":
result.Codec = stream.CodecName
result.Resolution = fmt.Sprintf("%dx%d", stream.Width, stream.Height)
@@ -288,26 +289,26 @@ func (t *Tester) testRTSP(ctx context.Context, streamURL string, result *TestRes
if len(parts) == 2 {
// Calculate FPS from fraction
var num, den int
fmt.Sscanf(parts[0], "%d", &num)
fmt.Sscanf(parts[1], "%d", &den)
if den > 0 {
result.FPS = num / den
if n, _ := fmt.Sscanf(parts[0], "%d", &num); n == 1 {
if n, _ := fmt.Sscanf(parts[1], "%d", &den); n == 1 && den > 0 {
result.FPS = num / den
}
}
}
}
// Parse bitrate
if stream.BitRate != "" {
fmt.Sscanf(stream.BitRate, "%d", &result.Bitrate)
_, _ = fmt.Sscanf(stream.BitRate, "%d", &result.Bitrate)
}
} else if stream.CodecType == "audio" {
case "audio":
result.HasAudio = true
}
}
// Use format bitrate if stream bitrate not available
if result.Bitrate == 0 && probeResult.Format.BitRate != "" {
fmt.Sscanf(probeResult.Format.BitRate, "%d", &result.Bitrate)
_, _ = fmt.Sscanf(probeResult.Format.BitRate, "%d", &result.Bitrate)
}
if !result.Working {
@@ -348,7 +349,7 @@ func (t *Tester) testHTTP(ctx context.Context, streamURL string, result *TestRes
result.Error = fmt.Sprintf("HTTP request failed: %v", err)
return
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
// Check status code
if resp.StatusCode != http.StatusOK {