119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
package download
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mathismqn/godeez/internal/config"
|
|
"github.com/mathismqn/godeez/internal/deezer"
|
|
"github.com/mathismqn/godeez/internal/fsutil"
|
|
"github.com/mathismqn/godeez/internal/store"
|
|
)
|
|
|
|
type Downloader struct {
|
|
appConfig *config.Config
|
|
store *store.Store
|
|
kind deezer.Kind
|
|
deezerClient *deezer.Client
|
|
|
|
hashIndexOnce sync.Once
|
|
hashIndex *hashIndex
|
|
hashIndexErr error
|
|
}
|
|
|
|
func New(appConfig *config.Config, st *store.Store, kind deezer.Kind) *Downloader {
|
|
return &Downloader{
|
|
appConfig: appConfig,
|
|
store: st,
|
|
kind: kind,
|
|
}
|
|
}
|
|
|
|
func (d *Downloader) Run(ctx context.Context, opts Options, id string) error {
|
|
if err := d.initDeezerClient(ctx, opts); err != nil {
|
|
return err
|
|
}
|
|
|
|
resource, outputDir, err := d.prepareResource(ctx, id, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return d.downloadAllTracks(ctx, resource, opts, outputDir)
|
|
}
|
|
|
|
func (d *Downloader) initDeezerClient(ctx context.Context, opts Options) error {
|
|
var err error
|
|
d.deezerClient, err = deezer.NewClient(ctx, d.appConfig.ARLCookie)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !d.deezerClient.Session.Premium && (opts.Quality == "mp3_320" || opts.Quality == "flac") {
|
|
return fmt.Errorf("premium account required for '%s' quality", opts.Quality)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Downloader) prepareResource(ctx context.Context, id string, opts Options) (deezer.Resource, string, error) {
|
|
resource, err := d.deezerClient.FetchResource(ctx, d.kind, id)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to fetch resource: %w", err)
|
|
}
|
|
|
|
tracks := resource.Tracks()
|
|
if len(tracks) == 0 {
|
|
if d.kind == deezer.KindTrack {
|
|
return nil, "", fmt.Errorf("track with ID %s not found", id)
|
|
}
|
|
return nil, "", fmt.Errorf("%s has no tracks", d.kind)
|
|
}
|
|
|
|
if d.kind == deezer.KindArtist && len(tracks) > opts.Limit {
|
|
resource.SetTracks(tracks[:opts.Limit])
|
|
}
|
|
|
|
outputDir := resource.OutputDir(d.appConfig.OutputDir)
|
|
if err := fsutil.EnsureDir(outputDir); err != nil {
|
|
return nil, "", fmt.Errorf("failed to create output directory: %w", err)
|
|
}
|
|
sweepPartFiles(outputDir)
|
|
|
|
return resource, outputDir, nil
|
|
}
|
|
|
|
func (d *Downloader) downloadAllTracks(ctx context.Context, resource deezer.Resource, opts Options, outputDir string) error {
|
|
tracks := resource.Tracks()
|
|
startTime := time.Now()
|
|
|
|
if d.kind != deezer.KindTrack {
|
|
fmt.Printf("%s\n\nStarting download...\n\n", resourceInfo(resource))
|
|
}
|
|
|
|
progress := newProgressTracker(len(tracks), d.kind)
|
|
|
|
for i, track := range tracks {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
sp := progress.startDownload(i, track)
|
|
result := d.downloadTrack(ctx, resource, track, opts, outputDir)
|
|
sp.Stop()
|
|
|
|
if result.err != nil && errors.Is(result.err, context.Canceled) {
|
|
return result.err
|
|
}
|
|
|
|
progress.handleResult(i, track, result)
|
|
}
|
|
|
|
progress.printSummary(outputDir, time.Since(startTime))
|
|
|
|
return nil
|
|
}
|