refactor: simplify codebase (-209 lines)

This commit is contained in:
Mathis Maquenne
2026-03-01 21:55:42 +01:00
parent cd45c3f40a
commit 08b608ec80
31 changed files with 389 additions and 598 deletions
+25 -42
View File
@@ -16,7 +16,6 @@ import (
"github.com/mathismqn/godeez/internal/deezer"
"github.com/mathismqn/godeez/internal/fileutil"
"github.com/mathismqn/godeez/internal/logger"
"github.com/mathismqn/godeez/internal/provider"
"github.com/mathismqn/godeez/internal/store"
"github.com/mathismqn/godeez/internal/tags"
)
@@ -38,8 +37,7 @@ func New(appConfig *config.Config, resourceType string) *Client {
return &Client{
appConfig: appConfig,
resourceType: resourceType,
deezerClient: nil,
Logger: logger.New(nil), // Initialize with a nil logger, can be set later
Logger: logger.New(nil),
}
}
@@ -89,16 +87,15 @@ func (c *Client) prepareResource(ctx context.Context, id string, opts Options) (
}
if c.resourceType == "artist" && len(songs) > opts.Limit {
songs = songs[:opts.Limit]
resource.SetSongs(songs)
resource.SetSongs(songs[:opts.Limit])
}
resourceOutputDir := resource.GetOutputDir(c.appConfig.OutputDir)
if err := fileutil.EnsureDir(resourceOutputDir); err != nil {
outputDir := resource.GetOutputDir(c.appConfig.OutputDir)
if err := fileutil.EnsureDir(outputDir); err != nil {
return nil, "", fmt.Errorf("failed to create output directory: %w", err)
}
return resource, resourceOutputDir, nil
return resource, outputDir, nil
}
func (c *Client) createResource() (deezer.Resource, error) {
@@ -135,10 +132,8 @@ func (c *Client) downloadAllSongs(ctx context.Context, resource deezer.Resource,
result := c.downloadSong(ctx, resource, song, opts, outputDir)
sp.Stop()
if result.err != nil {
if errors.Is(result.err, context.Canceled) {
return result.err
}
if result.err != nil && errors.Is(result.err, context.Canceled) {
return result.err
}
progress.handleResult(i, song, result)
@@ -150,46 +145,44 @@ func (c *Client) downloadAllSongs(ctx context.Context, resource deezer.Resource,
}
func (c *Client) downloadSong(ctx context.Context, resource deezer.Resource, song *deezer.Song, opts Options, outputDir string) downloadResult {
var warnings []string
media, err := c.deezerClient.FetchMedia(ctx, song, opts.Quality)
if err != nil {
return handleError(fmt.Errorf("failed to fetch media: %w", err))
return downloadResult{err: fmt.Errorf("failed to fetch media: %w", err)}
}
mediaFormat := media.GetFormat()
if opts.Strict && strings.ToLower(mediaFormat) != opts.Quality {
return handleError(fmt.Errorf("requested quality '%s' not available", opts.Quality))
return downloadResult{err: fmt.Errorf("requested quality '%s' not available", opts.Quality)}
}
if path, skip := c.shouldSkipDownload(ctx, song.ID, mediaFormat); skip {
return handleError(SkipError{Path: path})
if skipPath, skip := c.shouldSkipDownload(ctx, song.ID, mediaFormat); skip {
return downloadResult{skipped: true, path: skipPath}
}
metadataFetcher := newMetadataFetcher(c.deezerClient.Session.HttpClient)
metadataChan := make(chan metadataResult, 1)
go func() {
metadataResult := metadataFetcher.fetch(ctx, song, opts)
metadataChan <- metadataResult
metadataChan <- fetchMetadata(c.deezerClient.Session.HttpClient, ctx, song, opts)
}()
stream, err := c.deezerClient.GetMediaStream(ctx, media, song.ID)
stream, err := c.deezerClient.GetMediaStream(ctx, media)
if err != nil {
return handleError(fmt.Errorf("failed to get media stream: %w", err))
return downloadResult{err: fmt.Errorf("failed to get media stream: %w", err)}
}
dlCtx, cancel := context.WithTimeout(ctx, opts.Timeout)
defer cancel()
fileName := song.GetFileName(c.resourceType, mediaFormat, song)
fileName := song.GetFileName(c.resourceType, mediaFormat)
outputPath := path.Join(outputDir, fileName)
key := crypto.GetKey(c.appConfig.SecretKey, song.ID)
if err := c.streamToFile(dlCtx, stream, outputPath, key); err != nil {
fileutil.DeleteFile(outputPath)
return handleError(fmt.Errorf("failed to stream to file: %w", err))
return downloadResult{err: fmt.Errorf("failed to stream to file: %w", err)}
}
var warnings []string
if opts.Quality != strings.ToLower(mediaFormat) {
warnings = append(warnings, fmt.Sprintf("requested quality '%s' not available, using '%s' instead", opts.Quality, strings.ToLower(mediaFormat)))
}
@@ -199,16 +192,11 @@ func (c *Client) downloadSong(ctx context.Context, resource deezer.Resource, son
warnings = append(warnings, fmt.Sprintf("failed to fetch cover image: %v", err))
}
metadataResult := <-metadataChan
warnings = append(warnings, metadataResult.warnings...)
metadata := <-metadataChan
warnings = append(warnings, metadata.warnings...)
warnings = append(warnings, c.finalizeDownload(resource, song, outputPath, mediaFormat, metadata.genre, cover, metadata.bpmKey)...)
finalizeWarnings := c.finalizeDownload(resource, song, outputPath, mediaFormat, metadataResult.genre, cover, metadataResult.bpmKey)
warnings = append(warnings, finalizeWarnings...)
return downloadResult{
success: true,
warnings: warnings,
}
return downloadResult{warnings: warnings}
}
func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputPath string, key []byte) error {
@@ -226,22 +214,18 @@ func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputP
case <-ctx.Done():
return ctx.Err()
default:
// continue
}
totalRead := 0
for totalRead < chunkSize {
n, err := stream.Read(buffer[totalRead:])
totalRead += n
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
if n > 0 {
totalRead += n
}
}
if totalRead == 0 {
@@ -255,8 +239,7 @@ func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputP
}
}
_, err = file.Write(buffer[:totalRead])
if err != nil {
if _, err = file.Write(buffer[:totalRead]); err != nil {
return err
}
@@ -268,7 +251,7 @@ func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputP
return nil
}
func (c *Client) finalizeDownload(resource deezer.Resource, song *deezer.Song, outputPath, mediaFormat, genre string, cover []byte, bpmKey provider.BPMKey) []string {
func (c *Client) finalizeDownload(resource deezer.Resource, song *deezer.Song, outputPath, mediaFormat, genre string, cover []byte, bpmKey bpmKey) []string {
var warnings []string
if err := tags.AddTags(resource, song, cover, outputPath, bpmKey.BPM, bpmKey.Key, genre); err != nil {
-15
View File
@@ -1,15 +0,0 @@
package downloader
func handleError(err error) downloadResult {
if path, ok := IsSkipError(err); ok {
return downloadResult{
skipped: true,
path: path,
}
}
return downloadResult{
success: false,
err: err,
}
}
+37 -49
View File
@@ -10,81 +10,69 @@ import (
"github.com/mathismqn/godeez/internal/provider"
)
type bpmKey struct {
BPM string
Key string
}
type metadataResult struct {
bpmKey provider.BPMKey
bpmKey bpmKey
genre string
warnings []string
}
type metadataFetcher struct {
httpClient *http.Client
}
func newMetadataFetcher(httpClient *http.Client) *metadataFetcher {
return &metadataFetcher{
httpClient: httpClient,
}
}
func (mf *metadataFetcher) fetch(ctx context.Context, song *deezer.Song, opts Options) metadataResult {
result := metadataResult{
bpmKey: provider.BPMKey{},
genre: "",
warnings: []string{},
}
func fetchMetadata(httpClient *http.Client, ctx context.Context, song *deezer.Song, opts Options) metadataResult {
if !opts.BPM && !opts.Genre {
return result
return metadataResult{}
}
bmpChan := make(chan provider.BPMKey, 1)
bmpErrChan := make(chan error, 1)
genreChan := make(chan string, 1)
genreErrChan := make(chan error, 1)
type bpmResult struct {
value bpmKey
err error
}
type genreResult struct {
value string
err error
}
bpmChan := make(chan bpmResult, 1)
genreChan := make(chan genreResult, 1)
if opts.BPM {
go func() {
p := provider.BPMProvider{}
bmpKey, err := p.Fetch(ctx, mf.httpClient, song.Artist, song.Title, song.Duration)
if err != nil {
bmpErrChan <- err
} else {
bmpChan <- bmpKey
}
result, err := provider.FetchBPM(ctx, httpClient, song.Artist, song.Title, song.Duration)
bpmChan <- bpmResult{value: bpmKey{BPM: result.BPM, Key: result.Key}, err: err}
}()
}
if opts.Genre {
go func() {
p := provider.GenreProvider{}
genre, err := p.Fetch(ctx, mf.httpClient, song.Artist, song.GetTitle())
if err != nil {
genreErrChan <- err
} else {
genreChan <- genre
}
genre, err := provider.FetchGenre(ctx, httpClient, song.Artist, song.GetTitle())
genreChan <- genreResult{value: genre, err: err}
}()
}
var result metadataResult
if opts.BPM {
select {
case bmpKey := <-bmpChan:
result.bpmKey = bmpKey
case err := <-bmpErrChan:
if !errors.Is(err, context.Canceled) {
result.warnings = append(result.warnings, fmt.Sprintf("failed to fetch BPM and key: %v", err))
r := <-bpmChan
if r.err != nil {
if !errors.Is(r.err, context.Canceled) {
result.warnings = append(result.warnings, fmt.Sprintf("failed to fetch BPM and key: %v", r.err))
}
} else {
result.bpmKey = r.value
}
}
if opts.Genre {
select {
case genre := <-genreChan:
result.genre = genre
case err := <-genreErrChan:
if !errors.Is(err, context.Canceled) {
result.warnings = append(result.warnings, fmt.Sprintf("failed to fetch genre: %v", err))
r := <-genreChan
if r.err != nil {
if !errors.Is(r.err, context.Canceled) {
result.warnings = append(result.warnings, fmt.Sprintf("failed to fetch genre: %v", r.err))
}
} else {
result.genre = r.value
}
}
+11 -14
View File
@@ -11,23 +11,22 @@ import (
"github.com/mathismqn/godeez/internal/logger"
)
type downloadStats struct {
downloaded int
skipped int
failed int
}
type downloadResult struct {
success bool
skipped bool
path string
warnings []string
err error
}
type downloadStats struct {
downloaded int
skipped int
failed int
}
type progressTracker struct {
logger *logger.Logger
stats *downloadStats
stats downloadStats
totalSongs int
resourceType string
}
@@ -35,20 +34,18 @@ type progressTracker struct {
func newProgressTracker(logger *logger.Logger, totalSongs int, resourceType string) *progressTracker {
return &progressTracker{
logger: logger,
stats: &downloadStats{},
totalSongs: totalSongs,
resourceType: resourceType,
}
}
func (pt *progressTracker) startDownload(index int, song *deezer.Song) *spinner.Spinner {
songTitle := song.GetTitle()
trackProgress := fmt.Sprintf("[%d/%d]", index+1, pt.totalSongs)
sp := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
sp.Writer = os.Stdout
sp.Prefix = trackProgress + " "
sp.Suffix = fmt.Sprintf(" Downloading: %s - %s", song.Artist, songTitle)
sp.Suffix = fmt.Sprintf(" Downloading: %s - %s", song.Artist, song.GetTitle())
sp.Start()
return sp
@@ -73,13 +70,13 @@ func (pt *progressTracker) handleResult(index int, song *deezer.Song, result dow
return
}
pt.stats.downloaded++
pt.logger.Infof("Downloaded %s - %s\n", song.Artist, songTitle)
symbol := "✔"
if len(result.warnings) > 0 {
symbol = "⚠"
}
pt.stats.downloaded++
pt.logger.Infof("Downloaded %s - %s\n", song.Artist, songTitle)
fmt.Printf("%s %s Downloaded: %s - %s\n", trackProgress, symbol, song.Artist, songTitle)
for _, w := range result.warnings {
+24 -30
View File
@@ -7,37 +7,31 @@ import (
"github.com/mathismqn/godeez/internal/store"
)
type SkipError struct {
Path string
}
func (e SkipError) Error() string {
return e.Path
}
func IsSkipError(err error) (string, bool) {
if skipErr, ok := err.(SkipError); ok {
return skipErr.Path, true
}
return "", false
}
func (c *Client) shouldSkipDownload(ctx context.Context, songID, mediaFormat string) (string, bool) {
if existing, err := store.GetDownloadInfo(songID); err == nil && existing.Quality == mediaFormat {
if fileutil.FileExists(existing.Path) {
return existing.Path, true
}
if existing.Hash != "" {
if err := c.initHashIndex(ctx); err == nil {
if foundPath, ok := c.hashIndex.Find(existing.Hash); ok {
existing.Path = foundPath
_ = existing.Save()
return foundPath, true
}
}
}
existing, err := store.GetDownloadInfo(songID)
if err != nil || existing.Quality != mediaFormat {
return "", false
}
return "", false
if fileutil.FileExists(existing.Path) {
return existing.Path, true
}
if existing.Hash == "" {
return "", false
}
if err := c.initHashIndex(ctx); err != nil {
return "", false
}
foundPath, ok := c.hashIndex.Find(existing.Hash)
if !ok {
return "", false
}
existing.Path = foundPath
_ = existing.Save()
return foundPath, true
}