mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
initial Go port of streamrip
This commit is contained in:
1016
internal/app/app.go
Normal file
1016
internal/app/app.go
Normal file
File diff suppressed because it is too large
Load Diff
318
internal/app/app_test.go
Normal file
318
internal/app/app_test.go
Normal file
@@ -0,0 +1,318 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"streamrip-go/internal/audio/tag"
|
||||
"streamrip-go/internal/config"
|
||||
"streamrip-go/internal/download"
|
||||
"streamrip-go/internal/provider"
|
||||
"streamrip-go/internal/store"
|
||||
)
|
||||
|
||||
type noopTagger struct{}
|
||||
|
||||
func (n noopTagger) TagFLAC(string, tag.Metadata, string) error { return nil }
|
||||
|
||||
type fakeProvider struct {
|
||||
url string
|
||||
}
|
||||
|
||||
func (f *fakeProvider) Source() string { return "qobuz" }
|
||||
func (f *fakeProvider) Login(context.Context) error { return nil }
|
||||
func (f *fakeProvider) LoggedIn() bool { return true }
|
||||
func (f *fakeProvider) Close() error { return nil }
|
||||
func (f *fakeProvider) Search(context.Context, string, string, int) ([]map[string]any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeProvider) GetMetadata(context.Context, string, string) (map[string]any, error) {
|
||||
return map[string]any{
|
||||
"title": "Dreams/Live",
|
||||
"track_number": float64(3),
|
||||
"performer": map[string]any{
|
||||
"name": "Fleetwood Mac",
|
||||
},
|
||||
"album": map[string]any{
|
||||
"artist": map[string]any{"name": "Fleetwood Mac"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type fakeAlbumProvider struct {
|
||||
url string
|
||||
}
|
||||
|
||||
type fakePlaylistProvider struct {
|
||||
url string
|
||||
}
|
||||
|
||||
func (f *fakeAlbumProvider) Source() string { return "qobuz" }
|
||||
func (f *fakePlaylistProvider) Source() string { return "qobuz" }
|
||||
func (f *fakeAlbumProvider) Login(context.Context) error { return nil }
|
||||
func (f *fakePlaylistProvider) Login(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (f *fakeAlbumProvider) LoggedIn() bool { return true }
|
||||
func (f *fakePlaylistProvider) LoggedIn() bool { return true }
|
||||
func (f *fakeAlbumProvider) Close() error { return nil }
|
||||
func (f *fakePlaylistProvider) Close() error { return nil }
|
||||
func (f *fakeAlbumProvider) Search(context.Context, string, string, int) ([]map[string]any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakePlaylistProvider) Search(context.Context, string, string, int) ([]map[string]any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeAlbumProvider) GetMetadata(_ context.Context, id string, mediaType string) (map[string]any, error) {
|
||||
if mediaType == "album" {
|
||||
return map[string]any{
|
||||
"title": "Rumours",
|
||||
"release_date_original": "1977-02-04",
|
||||
"media_count": float64(2),
|
||||
"maximum_bit_depth": float64(24),
|
||||
"maximum_sampling_rate": float64(96),
|
||||
"artist": map[string]any{"name": "Fleetwood Mac"},
|
||||
"tracks": map[string]any{"items": []any{
|
||||
map[string]any{"id": "t1"},
|
||||
map[string]any{"id": "t2"},
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
tn := float64(1)
|
||||
disc := float64(1)
|
||||
title := "Dreams"
|
||||
if id == "t2" {
|
||||
tn = 2
|
||||
disc = 2
|
||||
title = "Go Your Own Way"
|
||||
}
|
||||
return map[string]any{
|
||||
"title": title,
|
||||
"track_number": tn,
|
||||
"media_number": disc,
|
||||
"performer": map[string]any{
|
||||
"name": "Fleetwood Mac",
|
||||
},
|
||||
"album": map[string]any{
|
||||
"title": "Rumours",
|
||||
"artist": map[string]any{"name": "Fleetwood Mac"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakePlaylistProvider) GetMetadata(_ context.Context, id string, mediaType string) (map[string]any, error) {
|
||||
if mediaType == "playlist" {
|
||||
return map[string]any{
|
||||
"name": "Road Trip",
|
||||
"tracks": map[string]any{
|
||||
"items": []any{map[string]any{"id": "p1"}, map[string]any{"id": "p2"}},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
trackNum := float64(7)
|
||||
title := "Track One"
|
||||
if id == "p2" {
|
||||
trackNum = 9
|
||||
title = "Track Two"
|
||||
}
|
||||
return map[string]any{
|
||||
"title": title,
|
||||
"track_number": trackNum,
|
||||
"performer": map[string]any{
|
||||
"name": "Artist",
|
||||
},
|
||||
"album": map[string]any{
|
||||
"title": "Original Album",
|
||||
"artist": map[string]any{"name": "Artist"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
func (f *fakeProvider) GetDownloadable(context.Context, string, int) (*provider.Downloadable, error) {
|
||||
return &provider.Downloadable{URL: f.url, Extension: "flac", Source: "qobuz"}, nil
|
||||
}
|
||||
func (f *fakeAlbumProvider) GetDownloadable(context.Context, string, int) (*provider.Downloadable, error) {
|
||||
return &provider.Downloadable{URL: f.url, Extension: "flac", Source: "qobuz"}, nil
|
||||
}
|
||||
func (f *fakePlaylistProvider) GetDownloadable(context.Context, string, int) (*provider.Downloadable, error) {
|
||||
return &provider.Downloadable{URL: f.url, Extension: "flac", Source: "qobuz"}, nil
|
||||
}
|
||||
|
||||
func TestTrackRipPipeline(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("audio-bytes"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
d := config.DefaultConfigData()
|
||||
d.Downloads.Folder = tmp
|
||||
d.Downloads.SourceSubdirectories = false
|
||||
cfg := &config.Config{File: d, Session: d}
|
||||
|
||||
sqlite, err := store.NewSQLite(filepath.Join(tmp, "db.sqlite"))
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLite() error = %v", err)
|
||||
}
|
||||
defer func() { _ = sqlite.Close() }()
|
||||
|
||||
m := &Main{
|
||||
Config: cfg,
|
||||
Providers: map[string]provider.Client{
|
||||
"qobuz": &fakeProvider{url: ts.URL},
|
||||
},
|
||||
Store: sqlite,
|
||||
DL: download.New(),
|
||||
Tagger: noopTagger{},
|
||||
Pending: nil,
|
||||
Media: nil,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err = m.AddByID(ctx, "qobuz", "track", "19512574"); err != nil {
|
||||
t.Fatalf("AddByID() error = %v", err)
|
||||
}
|
||||
if err = m.Resolve(ctx); err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
if err = m.Rip(ctx); err != nil {
|
||||
t.Fatalf("Rip() error = %v", err)
|
||||
}
|
||||
|
||||
if _, err = os.Stat(filepath.Join(tmp, "03. Fleetwood Mac - Dreams_Live.flac")); err != nil {
|
||||
t.Fatalf("expected downloaded file: %v", err)
|
||||
}
|
||||
|
||||
ok, err := sqlite.IsDownloaded(ctx, "19512574")
|
||||
if err != nil {
|
||||
t.Fatalf("IsDownloaded() error = %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected track marked downloaded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlbumRipPipeline(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("audio-bytes"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
d := config.DefaultConfigData()
|
||||
d.Downloads.Folder = tmp
|
||||
d.Downloads.SourceSubdirectories = false
|
||||
d.Downloads.Concurrency = false
|
||||
cfg := &config.Config{File: d, Session: d}
|
||||
|
||||
sqlite, err := store.NewSQLite(filepath.Join(tmp, "db.sqlite"))
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLite() error = %v", err)
|
||||
}
|
||||
defer func() { _ = sqlite.Close() }()
|
||||
|
||||
m := &Main{
|
||||
Config: cfg,
|
||||
Providers: map[string]provider.Client{
|
||||
"qobuz": &fakeAlbumProvider{url: ts.URL},
|
||||
},
|
||||
Store: sqlite,
|
||||
DL: download.New(),
|
||||
Tagger: noopTagger{},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err = m.AddByID(ctx, "qobuz", "album", "a1"); err != nil {
|
||||
t.Fatalf("AddByID() error = %v", err)
|
||||
}
|
||||
if err = m.Resolve(ctx); err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
if err = m.Rip(ctx); err != nil {
|
||||
t.Fatalf("Rip() error = %v", err)
|
||||
}
|
||||
|
||||
folder := filepath.Join(tmp, "Fleetwood Mac - Rumours (1977) [FLAC] [24B-96kHz]")
|
||||
if _, err = os.Stat(filepath.Join(folder, "Disc 1", "01. Fleetwood Mac - Dreams.flac")); err != nil {
|
||||
t.Fatalf("missing first album track: %v", err)
|
||||
}
|
||||
if _, err = os.Stat(filepath.Join(folder, "Disc 2", "02. Fleetwood Mac - Go Your Own Way.flac")); err != nil {
|
||||
t.Fatalf("missing second album track: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaylistRipPipeline(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("audio-bytes"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
d := config.DefaultConfigData()
|
||||
d.Downloads.Folder = tmp
|
||||
d.Downloads.Concurrency = false
|
||||
d.Filepaths.RestrictCharacters = false
|
||||
cfg := &config.Config{File: d, Session: d}
|
||||
|
||||
sqlite, err := store.NewSQLite(filepath.Join(tmp, "db.sqlite"))
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLite() error = %v", err)
|
||||
}
|
||||
defer func() { _ = sqlite.Close() }()
|
||||
|
||||
m := &Main{
|
||||
Config: cfg,
|
||||
Providers: map[string]provider.Client{
|
||||
"qobuz": &fakePlaylistProvider{url: ts.URL},
|
||||
},
|
||||
Store: sqlite,
|
||||
DL: download.NewWithOptions(true, false),
|
||||
Tagger: noopTagger{},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err = m.AddByID(ctx, "qobuz", "playlist", "pl1"); err != nil {
|
||||
t.Fatalf("AddByID() error = %v", err)
|
||||
}
|
||||
if err = m.Resolve(ctx); err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
if err = m.Rip(ctx); err != nil {
|
||||
t.Fatalf("Rip() error = %v", err)
|
||||
}
|
||||
|
||||
folder := filepath.Join(tmp, "Road Trip")
|
||||
if _, err = os.Stat(filepath.Join(folder, "01. Artist - Track One.flac")); err != nil {
|
||||
t.Fatalf("missing first playlist track: %v", err)
|
||||
}
|
||||
if _, err = os.Stat(filepath.Join(folder, "02. Artist - Track Two.flac")); err != nil {
|
||||
t.Fatalf("missing second playlist track: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyQobuzArtistFiltersRepeats(t *testing.T) {
|
||||
albums := []collectionAlbum{
|
||||
{ID: "a1", Title: "Album X", BitDepth: 16, Sampling: 44.1, Explicit: false},
|
||||
{ID: "a2", Title: "Album X (Deluxe)", BitDepth: 24, Sampling: 96, Explicit: false},
|
||||
{ID: "b1", Title: "Album B", BitDepth: 16, Sampling: 44.1, Explicit: false},
|
||||
}
|
||||
filtered := applyQobuzArtistFilters("artist", albums, config.QobuzDiscographyFilterConfig{Repeats: true})
|
||||
if len(filtered) != 2 {
|
||||
t.Fatalf("len(filtered)=%d want 2", len(filtered))
|
||||
}
|
||||
ids := map[string]bool{}
|
||||
for _, a := range filtered {
|
||||
ids[a.ID] = true
|
||||
}
|
||||
if !ids["a2"] || !ids["b1"] {
|
||||
t.Fatalf("unexpected winners: %+v", ids)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user