package recovery import ( "context" "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" "sync" ) type QobuzDL struct { Path string OutputDir string Username string Password string Quality int } var cachedBinaries sync.Map func (d QobuzDL) DownloadAlbum(ctx context.Context, albumID string) error { albumID = strings.TrimSpace(albumID) if albumID == "" { return fmt.Errorf("album id must not be empty") } url := fmt.Sprintf("https://play.qobuz.com/album/%s", albumID) args := []string{} if strings.TrimSpace(d.OutputDir) != "" { args = append(args, "--output", d.OutputDir) } if d.Quality > 0 { args = append(args, "--quality", strconv.Itoa(d.Quality)) } if strings.TrimSpace(d.Username) != "" { args = append(args, "--user", strings.TrimSpace(d.Username)) } if strings.TrimSpace(d.Password) != "" { args = append(args, "--pass", d.Password) } args = append(args, "url", url) name, cmdArgs, workdir, err := resolveCommand(ctx, strings.TrimSpace(d.Path), args) if err != nil { return err } cmd := exec.CommandContext(ctx, name, cmdArgs...) if workdir != "" { cmd.Dir = workdir } out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("qobuz-dl failed: %w: %s", err, strings.TrimSpace(string(out))) } return nil } func resolveCommand(ctx context.Context, path string, args []string) (name string, cmdArgs []string, workdir string, err error) { if path == "" { return "qobuz-dl", args, "", nil } info, err := os.Stat(path) if err == nil && info.IsDir() { bin, err := ensureBuiltBinary(ctx, path) if err != nil { return "", nil, "", err } return bin, args, "", nil } if filepath.Base(path) == "." { dir := filepath.Dir(path) bin, err := ensureBuiltBinary(ctx, dir) if err != nil { return "", nil, "", err } return bin, args, "", nil } return path, args, "", nil } func ensureBuiltBinary(ctx context.Context, dir string) (string, error) { dir = filepath.Clean(dir) if v, ok := cachedBinaries.Load(dir); ok { if bin, ok := v.(string); ok && strings.TrimSpace(bin) != "" { if _, err := os.Stat(bin); err == nil { return bin, nil } } } tmpDir := filepath.Join(os.TempDir(), "navimigrate-qobuzdl") if err := os.MkdirAll(tmpDir, 0o755); err != nil { return "", fmt.Errorf("create temp build directory: %w", err) } out := filepath.Join(tmpDir, "qobuz-dl") build := exec.CommandContext(ctx, "go", "build", "-o", out, ".") build.Dir = dir b, err := build.CombinedOutput() if err != nil { return "", fmt.Errorf("build qobuz-dl in %s: %w: %s", dir, err, strings.TrimSpace(string(b))) } cachedBinaries.Store(dir, out) return out, nil }