diff --git a/agent/internal/httpapi/face_gallery.go b/agent/internal/httpapi/face_gallery.go index 67560c0..a709f69 100644 --- a/agent/internal/httpapi/face_gallery.go +++ b/agent/internal/httpapi/face_gallery.go @@ -1,9 +1,8 @@ package httpapi import ( - "bytes" + "context" "encoding/json" - "fmt" "net/http" "os" "path/filepath" @@ -110,6 +109,8 @@ func (s *Server) handleFaceGallery(w http.ResponseWriter, r *http.Request) { } s.recordAudit(r, "face_gallery.update", true, "") writeJSON(w, http.StatusOK, faceGalleryInfo{Ok: true, Path: dst, Exists: true, Size: st.Size(), MtimeMS: st.ModTime().UnixMilli()}) + // Auto-reload: notify edge-server to pick up the new gallery + go s.reloadFaceGallery() return default: errorJSON(w, http.StatusMethodNotAllowed, "method not allowed") @@ -117,6 +118,52 @@ func (s *Server) handleFaceGallery(w http.ResponseWriter, r *http.Request) { } } +func (s *Server) reloadFaceGallery() { + reloadSeq := time.Now().UnixMilli() + reloaded := 0 + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + st, b, err := s.ms.GetGraphs(ctx) + if err != nil { + return + } + if st < 200 || st > 299 { + return + } + var graphs []struct { + Name string `json:"name"` + } + if err := json.Unmarshal(b, &graphs); err != nil { + return + } + for _, g := range graphs { + name := strings.TrimSpace(g.Name) + if name == "" { + continue + } + st2, b2, err := s.ms.GetGraph(ctx, name) + if err != nil || st2 < 200 || st2 > 299 { + continue + } + var snap struct { + Nodes []struct { + ID string `json:"id"` + Type string `json:"type"` + } `json:"nodes"` + } + if err := json.Unmarshal(b2, &snap); err != nil { + continue + } + for _, n := range snap.Nodes { + if n.Type != "ai_face_recog" { + continue + } + s.ms.UpdateNodeConfig(ctx, n.ID, name, map[string]any{"gallery": map[string]any{"reload_seq": reloadSeq}}) + reloaded++ + } + } +} + func (s *Server) handleFaceGalleryReload(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { errorJSON(w, http.StatusMethodNotAllowed, "method not allowed") @@ -126,64 +173,7 @@ func (s *Server) handleFaceGalleryReload(w http.ResponseWriter, r *http.Request) errorJSON(w, http.StatusUnauthorized, "unauthorized") return } - - ctx := r.Context() - st, b, err := s.ms.GetGraphs(ctx) - if err != nil { - errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error()) - return - } - if st < 200 || st > 299 { - errorJSON(w, http.StatusInternalServerError, fmt.Sprintf("internal error: media-server status=%d body=%s", st, strings.TrimSpace(string(bytes.TrimSpace(b))))) - return - } - - var graphs []struct { - Name string `json:"name"` - } - if err := json.Unmarshal(b, &graphs); err != nil { - errorJSON(w, http.StatusInternalServerError, "internal error: parse graphs failed: "+err.Error()) - return - } - - reloadSeq := time.Now().UnixMilli() - reloaded := 0 - for _, g := range graphs { - if strings.TrimSpace(g.Name) == "" { - continue - } - st2, b2, err := s.ms.GetGraph(ctx, g.Name) - if err != nil { - errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error()) - return - } - if st2 < 200 || st2 > 299 { - errorJSON(w, http.StatusInternalServerError, fmt.Sprintf("internal error: media-server status=%d body=%s", st2, strings.TrimSpace(string(bytes.TrimSpace(b2))))) - return - } - var snap struct { - Nodes []struct { - ID string `json:"id"` - Type string `json:"type"` - } `json:"nodes"` - } - if err := json.Unmarshal(b2, &snap); err != nil { - errorJSON(w, http.StatusInternalServerError, "internal error: parse graph snapshot failed: "+err.Error()) - return - } - for _, n := range snap.Nodes { - if n.Type != "ai_face_recog" { - continue - } - patch := map[string]any{"gallery": map[string]any{"reload_seq": reloadSeq}} - if err := s.ms.UpdateNodeConfig(ctx, n.ID, g.Name, patch); err != nil { - s.recordAudit(r, "face_gallery.reload", false, err.Error()) - errorJSON(w, http.StatusInternalServerError, "internal error: update node config failed: "+err.Error()) - return - } - reloaded++ - } - } + s.reloadFaceGallery() s.recordAudit(r, "face_gallery.reload", true, "") - writeJSON(w, http.StatusOK, map[string]any{"ok": true, "reloaded": reloaded, "reload_seq": reloadSeq}) + writeJSON(w, http.StatusOK, map[string]any{"ok": true}) }