From fe3e92ce33a09b1ae46a4d5114c00c3112ad7538 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Mon, 22 Jun 2026 01:05:44 -0700 Subject: [PATCH] feat: recursive WAV browsing in subdirectories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handle ListWavs now uses filepath.WalkDir to find .wav files in all subdirectories. Returns relative paths (e.g. Kicks/kick.wav). Serves them correctly via /wavs/ prefix. Fixed: WalkDir swallows root errors differently than ReadDir — now detects and reports non-existent root directory errors. Added test for recursive subdirectory traversal. --- serve.go | 28 ++++++++++++++++++++-------- serve_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/serve.go b/serve.go index ec2df45..aad5e39 100644 --- a/serve.go +++ b/serve.go @@ -273,18 +273,30 @@ func handleListWavs(w http.ResponseWriter, r *http.Request) { dir = abs } - entries, err := os.ReadDir(dir) + files := []string{} + var walkErr error + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + if path == dir { + walkErr = err + return filepath.SkipAll + } + return nil + } + if !d.IsDir() && strings.HasSuffix(strings.ToLower(d.Name()), ".wav") { + rel, _ := filepath.Rel(dir, path) + files = append(files, rel) + } + return nil + }) + if walkErr != nil { + writeJSON(w, map[string]any{"error": walkErr.Error()}) + return + } if err != nil { writeJSON(w, map[string]any{"error": err.Error()}) return } - - files := []string{} - for _, e := range entries { - if !e.IsDir() && strings.HasSuffix(strings.ToLower(e.Name()), ".wav") { - files = append(files, e.Name()) - } - } writeJSON(w, files) } diff --git a/serve_test.go b/serve_test.go index 20e3ffc..1ea68d1 100644 --- a/serve_test.go +++ b/serve_test.go @@ -473,6 +473,41 @@ func TestHandleListWavs_ReturnsWavFiles(t *testing.T) { } } +func TestHandleListWavs_RecursiveSubdirs(t *testing.T) { + tmp := t.TempDir() + wavDir := filepath.Join(tmp, "wavs") + subDir := filepath.Join(wavDir, "Kicks") + if err := os.MkdirAll(subDir, 0755); err != nil { + t.Fatal(err) + } + fd, _ := os.Create(filepath.Join(wavDir, "root.wav")) + _ = fd.Close() + fd2, _ := os.Create(filepath.Join(subDir, "kick1.wav")) + _ = fd2.Close() + + req := httptest.NewRequest("GET", "/api/list-wavs?dir="+wavDir, nil) + rr := httptest.NewRecorder() + handleListWavs(rr, req) + + var files []string + if err := json.Unmarshal(rr.Body.Bytes(), &files); err != nil { + t.Fatalf("invalid JSON: %v", err) + } + if len(files) != 2 { + t.Errorf("expected 2 wav files, got %d: %v", len(files), files) + } + found := make(map[string]bool) + for _, f := range files { + found[f] = true + } + if !found["root.wav"] { + t.Errorf("missing root.wav, got %v", files) + } + if !found[filepath.Join("Kicks", "kick1.wav")] { + t.Errorf("missing Kicks/kick1.wav, got %v", files) + } +} + func TestHandleListWavs_EmptyDir(t *testing.T) { tmp := t.TempDir() req := httptest.NewRequest("GET", "/api/list-wavs?dir="+tmp, nil) @@ -486,6 +521,9 @@ func TestHandleListWavs_EmptyDir(t *testing.T) { if err := json.Unmarshal(rr.Body.Bytes(), &files); err != nil { t.Fatalf("invalid JSON: %v", err) } + if files == nil { + t.Fatal("expected empty array [], got null") + } if len(files) != 0 { t.Errorf("expected 0 files, got %d", len(files)) }