diff --git a/internal/app/library.go b/internal/app/library.go new file mode 100644 index 0000000..f506a50 --- /dev/null +++ b/internal/app/library.go @@ -0,0 +1,45 @@ +// Package app wires use-cases: read the generator's library, ship mp3s to +// the streaming VPS, and feed the endless stream with now-playing MQTT. +package app + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "radio-go/internal/domain" +) + +// LoadLibrary reads the ai-radio app's index.json and returns only tracks +// whose audio file actually exists on disk. +func LoadLibrary(libraryDir string) ([]domain.Track, error) { + raw, err := os.ReadFile(filepath.Join(libraryDir, "index.json")) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, fmt.Errorf("read index.json: %w", err) + } + var items []domain.Track + if err := json.Unmarshal(raw, &items); err != nil { + return nil, fmt.Errorf("parse index.json: %w", err) + } + out := make([]domain.Track, 0, len(items)) + for _, t := range items { + if t.ID == "" || t.Filename == "" { + continue + } + // filename must stay inside the library (no traversal) + clean := filepath.Clean(t.Filename) + if strings.HasPrefix(clean, "..") || filepath.IsAbs(clean) { + continue + } + if _, err := os.Stat(filepath.Join(libraryDir, clean)); err != nil { + continue + } + out = append(out, t) + } + return out, nil +}