build spotify-to-navidrome migrator with recovery flow
This commit is contained in:
112
internal/recovery/qobuzdl.go
Normal file
112
internal/recovery/qobuzdl.go
Normal file
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user