From c85debde3aaeccb3752a5edc8f6c38b7f4c515d0 Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Wed, 18 Mar 2026 07:06:32 +0100 Subject: [PATCH] Add recordings API endpoints --- cmd/sdrd/main.go | 40 ++++++++++++++++++++++++++++++++++ internal/recorder/list.go | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 internal/recorder/list.go diff --git a/cmd/sdrd/main.go b/cmd/sdrd/main.go index 5c24cbf..35f52dc 100644 --- a/cmd/sdrd/main.go +++ b/cmd/sdrd/main.go @@ -485,6 +485,46 @@ func main() { _ = json.NewEncoder(w).Encode(evs) }) + http.HandleFunc("/api/recordings", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + w.Header().Set("Content-Type", "application/json") + snap := cfgManager.Snapshot() + list, err := recorder.ListRecordings(snap.Recorder.OutputDir) + if err != nil { + http.Error(w, "failed to list recordings", http.StatusInternalServerError) + return + } + _ = json.NewEncoder(w).Encode(list) + }) + + http.HandleFunc("/api/recordings/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + id := strings.TrimPrefix(r.URL.Path, "/api/recordings/") + if id == "" { + http.Error(w, "missing id", http.StatusBadRequest) + return + } + snap := cfgManager.Snapshot() + base := filepath.Clean(filepath.Join(snap.Recorder.OutputDir, id)) + if !strings.HasPrefix(base, filepath.Clean(snap.Recorder.OutputDir)) { + http.Error(w, "invalid path", http.StatusBadRequest) + return + } + if r.URL.Path == "/api/recordings/"+id+"/audio" { + http.ServeFile(w, r, filepath.Join(base, "audio.wav")) + return + } + if r.URL.Path == "/api/recordings/"+id+"/iq" { + http.ServeFile(w, r, filepath.Join(base, "signal.cf32")) + return + } + // default: meta.json + http.ServeFile(w, r, filepath.Join(base, "meta.json")) + }) + http.Handle("/", http.FileServer(http.Dir(cfg.WebRoot))) server := &http.Server{Addr: cfg.WebAddr} diff --git a/internal/recorder/list.go b/internal/recorder/list.go new file mode 100644 index 0000000..ccd45b0 --- /dev/null +++ b/internal/recorder/list.go @@ -0,0 +1,45 @@ +package recorder + +import ( + "encoding/json" + "os" + "path/filepath" + "sort" + "time" +) + +type Recording struct { + ID string `json:"id"` + Start time.Time `json:"start"` + CenterHz float64 `json:"center_hz"` + Path string `json:"path"` +} + +func ListRecordings(root string) ([]Recording, error) { + entries, err := os.ReadDir(root) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + var out []Recording + for _, e := range entries { + if !e.IsDir() { + continue + } + id := e.Name() + metaPath := filepath.Join(root, id, "meta.json") + b, err := os.ReadFile(metaPath) + if err != nil { + continue + } + var m Meta + if err := json.Unmarshal(b, &m); err != nil { + continue + } + out = append(out, Recording{ID: id, Start: m.Start, CenterHz: m.CenterHz, Path: filepath.Join(root, id)}) + } + sort.Slice(out, func(i, j int) bool { return out[i].Start.After(out[j].Start) }) + return out, nil +}