fix: resolve bugs found in pre-release review
This commit is contained in:
@@ -93,7 +93,7 @@ func findTrackURL(ctx context.Context, httpClient *http.Client, artist, title, d
|
||||
|
||||
const toleranceSec = 2
|
||||
foundDuration := minutes*60 + seconds
|
||||
if foundDuration <= wantDuration-toleranceSec || foundDuration >= wantDuration+toleranceSec {
|
||||
if foundDuration < wantDuration-toleranceSec || foundDuration > wantDuration+toleranceSec {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ func (d *Downloader) prepareResource(ctx context.Context, id string, opts Option
|
||||
if err := fsutil.EnsureDir(outputDir); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
sweepPartFiles(outputDir)
|
||||
|
||||
return resource, outputDir, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
@@ -31,7 +34,9 @@ func toLower(ss []string) []string {
|
||||
}
|
||||
|
||||
func fetchGenre(ctx context.Context, httpClient *http.Client, artist, title string) (string, error) {
|
||||
reqURL := fmt.Sprintf("https://www.last.fm/music/%s/%s/+tags", artist, title)
|
||||
// Escape the path segments: names containing '/', '?', or '#' would
|
||||
// otherwise change the URL structure and fetch the wrong page.
|
||||
reqURL := fmt.Sprintf("https://www.last.fm/music/%s/%s/+tags", url.PathEscape(artist), url.PathEscape(title))
|
||||
|
||||
doc, err := fetchGenrePage(ctx, httpClient, reqURL)
|
||||
if err != nil {
|
||||
@@ -116,7 +121,8 @@ func formatTags(tags []string) string {
|
||||
}
|
||||
words := strings.Fields(tag)
|
||||
for i, w := range words {
|
||||
words[i] = strings.ToUpper(w[:1]) + strings.ToLower(w[1:])
|
||||
r, size := utf8.DecodeRuneInString(w)
|
||||
words[i] = string(unicode.ToUpper(r)) + strings.ToLower(w[size:])
|
||||
}
|
||||
formatted = append(formatted, strings.Join(words, " "))
|
||||
}
|
||||
|
||||
@@ -5,20 +5,41 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mathismqn/godeez/internal/deezer"
|
||||
)
|
||||
|
||||
const chunkSize = 2048
|
||||
const (
|
||||
chunkSize = 2048
|
||||
partPattern = ".godeez-*.part"
|
||||
)
|
||||
|
||||
func sweepPartFiles(dir string) {
|
||||
matches, err := filepath.Glob(filepath.Join(dir, partPattern))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, match := range matches {
|
||||
os.Remove(match)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Downloader) streamToFile(ctx context.Context, stream io.ReadCloser, outputPath string, key []byte) error {
|
||||
defer stream.Close()
|
||||
|
||||
file, err := os.Create(outputPath)
|
||||
file, err := os.CreateTemp(filepath.Dir(outputPath), partPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
tmpPath := file.Name()
|
||||
done := false
|
||||
defer func() {
|
||||
if !done {
|
||||
file.Close()
|
||||
os.Remove(tmpPath)
|
||||
}
|
||||
}()
|
||||
|
||||
buffer := make([]byte, chunkSize)
|
||||
for chunk := 0; ; chunk++ {
|
||||
@@ -60,5 +81,16 @@ func (d *Downloader) streamToFile(ctx context.Context, stream io.ReadCloser, out
|
||||
}
|
||||
}
|
||||
|
||||
if err := file.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmpPath, outputPath); err != nil {
|
||||
return err
|
||||
}
|
||||
done = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -34,20 +34,19 @@ func (d *Downloader) downloadTrack(ctx context.Context, resource deezer.Resource
|
||||
metadataChan <- fetchMetadata(d.deezerClient.Session.HTTPClient, ctx, track, opts)
|
||||
}()
|
||||
|
||||
stream, err := d.deezerClient.MediaStream(ctx, media)
|
||||
dlCtx, cancel := context.WithTimeout(ctx, opts.Timeout)
|
||||
defer cancel()
|
||||
|
||||
stream, err := d.deezerClient.MediaStream(dlCtx, media)
|
||||
if err != nil {
|
||||
return downloadResult{err: fmt.Errorf("failed to get media stream: %w", err)}
|
||||
}
|
||||
|
||||
dlCtx, cancel := context.WithTimeout(ctx, opts.Timeout)
|
||||
defer cancel()
|
||||
|
||||
fileName := track.Filename(d.kind, mediaFormat)
|
||||
outputPath := filepath.Join(outputDir, fileName)
|
||||
outputPath := d.uniqueOutputPath(track.ID, filepath.Join(outputDir, fileName))
|
||||
|
||||
key := deezer.BlowfishKey(track.ID)
|
||||
if err := d.streamToFile(dlCtx, stream, outputPath, key); err != nil {
|
||||
fsutil.Remove(outputPath)
|
||||
return downloadResult{err: fmt.Errorf("failed to stream to file: %w", err)}
|
||||
}
|
||||
|
||||
@@ -63,12 +62,34 @@ func (d *Downloader) downloadTrack(ctx context.Context, resource deezer.Resource
|
||||
}
|
||||
|
||||
metadata := <-metadataChan
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
fsutil.Remove(outputPath)
|
||||
return downloadResult{err: err}
|
||||
}
|
||||
|
||||
warnings = append(warnings, metadata.warnings...)
|
||||
warnings = append(warnings, d.finalizeDownload(resource, track, outputPath, mediaFormat, metadata.genre, cover, metadata.bpmKey)...)
|
||||
|
||||
return downloadResult{warnings: warnings}
|
||||
}
|
||||
|
||||
func (d *Downloader) uniqueOutputPath(trackID, path string) string {
|
||||
owned := ""
|
||||
if info, err := d.store.DownloadInfo(trackID); err == nil {
|
||||
owned = info.Path
|
||||
}
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
stem := strings.TrimSuffix(path, ext)
|
||||
candidate := path
|
||||
for i := 2; candidate != owned && fsutil.Exists(candidate); i++ {
|
||||
candidate = fmt.Sprintf("%s (%d)%s", stem, i, ext)
|
||||
}
|
||||
|
||||
return candidate
|
||||
}
|
||||
|
||||
func (d *Downloader) finalizeDownload(resource deezer.Resource, track *deezer.Track, outputPath, mediaFormat, genre string, cover []byte, bpmKey bpmKey) []string {
|
||||
var warnings []string
|
||||
|
||||
|
||||
Reference in New Issue
Block a user