feat(sense): add reconciliation safety controls [T-012]
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
mediamtxapi "yovision/sense/internal/mtx/generated"
|
||||
@@ -31,9 +32,63 @@ type pathAPI interface {
|
||||
ConfigPathsGetWithResponse(context.Context, string, ...mediamtxapi.RequestEditorFn) (*mediamtxapi.ConfigPathsGetResponse, error)
|
||||
ConfigPathsPatchWithResponse(context.Context, string, mediamtxapi.ConfigPathsPatchJSONRequestBody, ...mediamtxapi.RequestEditorFn) (*mediamtxapi.ConfigPathsPatchResponse, error)
|
||||
ConfigPathsDeleteWithResponse(context.Context, string, ...mediamtxapi.RequestEditorFn) (*mediamtxapi.ConfigPathsDeleteResponse, error)
|
||||
ConfigPathsListWithResponse(context.Context, *mediamtxapi.ConfigPathsListParams, ...mediamtxapi.RequestEditorFn) (*mediamtxapi.ConfigPathsListResponse, error)
|
||||
PathsGetWithResponse(context.Context, string, ...mediamtxapi.RequestEditorFn) (*mediamtxapi.PathsGetResponse, error)
|
||||
}
|
||||
|
||||
// ListPathNames enumerates only configuration names. Sources are deliberately
|
||||
// discarded so inventory and orphan reports cannot expose stream URIs.
|
||||
func (c *Client) ListPathNames(ctx context.Context) ([]string, error) {
|
||||
const (
|
||||
itemsPerPage = 100
|
||||
maxPages = 1000
|
||||
)
|
||||
result := make(map[string]struct{})
|
||||
seenPages := make(map[string]struct{})
|
||||
for page := 0; page < maxPages; page++ {
|
||||
pageValue, limitValue := page, itemsPerPage
|
||||
response, err := c.api.ConfigPathsListWithResponse(ctx, &mediamtxapi.ConfigPathsListParams{
|
||||
Page: &pageValue, ItemsPerPage: &limitValue,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MediaMTX list paths transport: %w", err)
|
||||
}
|
||||
if response.StatusCode() != http.StatusOK || response.JSON200 == nil ||
|
||||
response.JSON200.PageCount == nil || response.JSON200.Items == nil {
|
||||
return nil, &APIError{Operation: "list paths", StatusCode: response.StatusCode()}
|
||||
}
|
||||
pageCount := *response.JSON200.PageCount
|
||||
if pageCount < 0 || pageCount > maxPages {
|
||||
return nil, &APIError{Operation: "list paths pagination", StatusCode: response.StatusCode()}
|
||||
}
|
||||
pageNames := make([]string, 0, len(*response.JSON200.Items))
|
||||
for _, item := range *response.JSON200.Items {
|
||||
if item.Name == nil || strings.TrimSpace(*item.Name) == "" {
|
||||
return nil, &APIError{Operation: "list paths response", StatusCode: response.StatusCode()}
|
||||
}
|
||||
pageNames = append(pageNames, *item.Name)
|
||||
result[*item.Name] = struct{}{}
|
||||
}
|
||||
sort.Strings(pageNames)
|
||||
signature := strings.Join(pageNames, "\x00")
|
||||
if page > 0 && signature != "" {
|
||||
if _, duplicate := seenPages[signature]; duplicate {
|
||||
return nil, &APIError{Operation: "list paths repeated page", StatusCode: response.StatusCode()}
|
||||
}
|
||||
}
|
||||
seenPages[signature] = struct{}{}
|
||||
if int64(page+1) >= pageCount {
|
||||
values := make([]string, 0, len(result))
|
||||
for name := range result {
|
||||
values = append(values, name)
|
||||
}
|
||||
sort.Strings(values)
|
||||
return values, nil
|
||||
}
|
||||
}
|
||||
return nil, &APIError{Operation: "list paths page limit", StatusCode: http.StatusOK}
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
api pathAPI
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user