diff --git a/agent/internal/httpapi/models_status.go b/agent/internal/httpapi/models_status.go new file mode 100644 index 0000000..8afef7c --- /dev/null +++ b/agent/internal/httpapi/models_status.go @@ -0,0 +1,20 @@ +package httpapi + +import "net/http" + +func (s *Server) handleModelsStatus(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + errorJSON(w, http.StatusMethodNotAllowed, "method not allowed") + return + } + if !s.authorize(r, false) { + errorJSON(w, http.StatusUnauthorized, "unauthorized") + return + } + items, err := s.store.ListInstalledModels() + if err != nil { + errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]any{"models": items}) +} diff --git a/agent/internal/httpapi/models_status_test.go b/agent/internal/httpapi/models_status_test.go new file mode 100644 index 0000000..e42d5b1 --- /dev/null +++ b/agent/internal/httpapi/models_status_test.go @@ -0,0 +1,41 @@ +package httpapi + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + "rk3588sys/agent/internal/modelstore" +) + +func TestHandleModelsStatusReturnsInstalledModels(t *testing.T) { + dir := t.TempDir() + store := modelstore.New(dir, 8) + itemDir := store.FilesDir() + if err := os.MkdirAll(itemDir, 0o755); err != nil { + t.Fatalf("MkdirAll: %v", err) + } + modelPath := filepath.Join(itemDir, "face_det_scrfd_500m_640_rk3588__abc.rknn") + if err := os.WriteFile(modelPath, []byte("abc"), 0o644); err != nil { + t.Fatalf("WriteFile: %v", err) + } + if err := os.WriteFile(store.ManifestPath(), []byte(`{"items":[{"name":"face_det_scrfd_500m_640_rk3588","sha256":"abc","path":"`+filepath.ToSlash(modelPath)+`","size":3,"mtime_ms":1}]}`), 0o644); err != nil { + t.Fatalf("WriteFile manifest: %v", err) + } + + srv := &Server{store: store} + req := httptest.NewRequest(http.MethodGet, "/v1/models/status", nil) + rr := httptest.NewRecorder() + + srv.handleModelsStatus(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) + } + if !strings.Contains(rr.Body.String(), "face_det_scrfd_500m_640_rk3588") { + t.Fatalf("expected response to include model name, got %s", rr.Body.String()) + } +} diff --git a/agent/internal/httpapi/server.go b/agent/internal/httpapi/server.go index 1a98844..078abe5 100644 --- a/agent/internal/httpapi/server.go +++ b/agent/internal/httpapi/server.go @@ -127,6 +127,7 @@ func New(agentCfg config.AgentConfig, baseDir string, ms *mediaserver.Client, st mux.HandleFunc("/v1/face-gallery", s.handleFaceGallery) mux.HandleFunc("/v1/face-gallery/reload", s.handleFaceGalleryReload) mux.HandleFunc("/v1/models", s.handleModelsList) + mux.HandleFunc("/v1/models/status", s.handleModelsStatus) mux.HandleFunc("/v1/models/", s.handleModelUpload) mux.HandleFunc("/v1/media-server/reload", s.handleMediaReload) mux.HandleFunc("/v1/media-server/rollback", s.handleMediaRollback) diff --git a/agent/internal/modelstore/modelstore.go b/agent/internal/modelstore/modelstore.go index 96305da..99958cf 100644 --- a/agent/internal/modelstore/modelstore.go +++ b/agent/internal/modelstore/modelstore.go @@ -26,6 +26,15 @@ type Manifest struct { Items []Item `json:"items"` } +type InstalledModel struct { + Name string `json:"name"` + FileName string `json:"file_name"` + Sha256 string `json:"sha256"` + Path string `json:"path"` + Size int64 `json:"size"` + MtimeMS int64 `json:"mtime_ms"` +} + type Store struct { ModelsDir string MaxUploadBytes int64 @@ -136,6 +145,26 @@ func (s *Store) List() (Manifest, error) { return m, nil } +func (s *Store) ListInstalledModels() ([]InstalledModel, error) { + manifest, err := s.List() + if err != nil { + return nil, err + } + items := make([]InstalledModel, 0, len(manifest.Items)) + for _, item := range manifest.Items { + fileName := filepath.Base(filepath.FromSlash(item.Path)) + items = append(items, InstalledModel{ + Name: item.Name, + FileName: fileName, + Sha256: item.Sha256, + Path: item.Path, + Size: item.Size, + MtimeMS: item.MtimeMS, + }) + } + return items, nil +} + func (s *Store) upsertManifest(item Item) error { m, err := s.List() if err != nil { diff --git a/agent/internal/modelstore/modelstore_test.go b/agent/internal/modelstore/modelstore_test.go new file mode 100644 index 0000000..2c40704 --- /dev/null +++ b/agent/internal/modelstore/modelstore_test.go @@ -0,0 +1,30 @@ +package modelstore + +import ( + "os" + "path/filepath" + "testing" +) + +func TestListInstalledModelsUsesManifestEntries(t *testing.T) { + dir := t.TempDir() + store := New(dir, 8) + if err := os.MkdirAll(store.FilesDir(), 0o755); err != nil { + t.Fatalf("MkdirAll: %v", err) + } + modelPath := filepath.Join(store.FilesDir(), "face_det_scrfd_500m_640_rk3588__abc.rknn") + if err := os.WriteFile(modelPath, []byte("abc"), 0o644); err != nil { + t.Fatalf("WriteFile: %v", err) + } + if err := os.WriteFile(store.ManifestPath(), []byte(`{"items":[{"name":"face_det_scrfd_500m_640_rk3588","sha256":"abc","path":"`+filepath.ToSlash(modelPath)+`","size":3,"mtime_ms":1}]}`), 0o644); err != nil { + t.Fatalf("WriteFile manifest: %v", err) + } + + items, err := store.ListInstalledModels() + if err != nil { + t.Fatalf("ListInstalledModels: %v", err) + } + if len(items) != 1 || items[0].FileName == "" { + t.Fatalf("unexpected installed models: %#v", items) + } +} diff --git a/agent/rk3588-agent_linux_arm64 b/agent/rk3588-agent_linux_arm64 index e33dd69..9cc4ff2 100755 Binary files a/agent/rk3588-agent_linux_arm64 and b/agent/rk3588-agent_linux_arm64 differ