mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
refactor: unify extension call skeleton and download finalization paths
Go: - callExtensionScript generic replaces the ~40-line perf/lock/run/ timeout skeleton in ten wrapper methods; PostProcess V1/V2 share postProcessCommon (V1 keeps its postProcess-only probe) - attemptExtensionDownload extracts the duplicated provider download attempt from the source-extension and priority-loop paths; overlayStr/overlayInt collapse the enrichment overlay chains; buildDownloadFilename shared by both output-path builders Dart (download_queue_provider): - _replaceSafFileVia wraps the SAF copy/operate/publish/cleanup roundtrip used at eight sites - decrypt and external-LRC finalization unified between the inline and native-worker pipelines (divergences parametrized: AC-4 repair, per-stage error messages, base-name resolution, extension-state snapshot); HIGH/container conversion keep separate lenient/strict policies and share only the SAF mechanics
This commit is contained in:
+217
-367
@@ -176,56 +176,58 @@ func normalizeExtensionDownloadResult(result *ExtDownloadResult) (DownloadResult
|
||||
return downloadResult, alreadyExists
|
||||
}
|
||||
|
||||
// overlayStr sets *dst = src when dst is empty and src is not. If field is
|
||||
// non-empty it logs "<field> from enrichment: <src>" on overlay.
|
||||
func overlayStr(dst *string, src, field string) {
|
||||
if src == "" || *dst != "" {
|
||||
return
|
||||
}
|
||||
*dst = src
|
||||
if field != "" {
|
||||
GoLog("[DownloadWithExtensionFallback] %s from enrichment: %s\n", field, src)
|
||||
}
|
||||
}
|
||||
|
||||
// overlayStrTrim is overlayStr but treats a whitespace-only dst as empty too.
|
||||
func overlayStrTrim(dst *string, src string) {
|
||||
if src == "" || strings.TrimSpace(*dst) != "" {
|
||||
return
|
||||
}
|
||||
*dst = src
|
||||
}
|
||||
|
||||
// overlayInt sets *dst = src when dst is zero and src is positive. If field is
|
||||
// non-empty it logs "<field> from enrichment: <src>" on overlay.
|
||||
func overlayInt(dst *int, src int, field string) {
|
||||
if src <= 0 || *dst != 0 {
|
||||
return
|
||||
}
|
||||
*dst = src
|
||||
if field != "" {
|
||||
GoLog("[DownloadWithExtensionFallback] %s from enrichment: %d\n", field, src)
|
||||
}
|
||||
}
|
||||
|
||||
func overlayExtensionDownloadMetadata(resp *DownloadResponse, result *ExtDownloadResult) {
|
||||
if resp == nil || result == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(resp.Title) == "" && result.Title != "" {
|
||||
resp.Title = result.Title
|
||||
}
|
||||
if strings.TrimSpace(resp.Artist) == "" && result.Artist != "" {
|
||||
resp.Artist = result.Artist
|
||||
}
|
||||
if strings.TrimSpace(resp.Album) == "" && result.Album != "" {
|
||||
resp.Album = result.Album
|
||||
}
|
||||
if strings.TrimSpace(resp.AlbumArtist) == "" && result.AlbumArtist != "" {
|
||||
resp.AlbumArtist = result.AlbumArtist
|
||||
}
|
||||
if resp.TrackNumber == 0 && result.TrackNumber > 0 {
|
||||
resp.TrackNumber = result.TrackNumber
|
||||
}
|
||||
if resp.DiscNumber == 0 && result.DiscNumber > 0 {
|
||||
resp.DiscNumber = result.DiscNumber
|
||||
}
|
||||
if resp.TotalTracks == 0 && result.TotalTracks > 0 {
|
||||
resp.TotalTracks = result.TotalTracks
|
||||
}
|
||||
if resp.TotalDiscs == 0 && result.TotalDiscs > 0 {
|
||||
resp.TotalDiscs = result.TotalDiscs
|
||||
}
|
||||
if strings.TrimSpace(resp.ReleaseDate) == "" && result.ReleaseDate != "" {
|
||||
resp.ReleaseDate = result.ReleaseDate
|
||||
}
|
||||
if strings.TrimSpace(resp.CoverURL) == "" && result.CoverURL != "" {
|
||||
resp.CoverURL = result.CoverURL
|
||||
}
|
||||
if strings.TrimSpace(resp.ISRC) == "" && result.ISRC != "" {
|
||||
resp.ISRC = result.ISRC
|
||||
}
|
||||
if strings.TrimSpace(resp.Genre) == "" && result.Genre != "" {
|
||||
resp.Genre = result.Genre
|
||||
}
|
||||
if strings.TrimSpace(resp.Label) == "" && result.Label != "" {
|
||||
resp.Label = result.Label
|
||||
}
|
||||
if strings.TrimSpace(resp.Copyright) == "" && result.Copyright != "" {
|
||||
resp.Copyright = result.Copyright
|
||||
}
|
||||
if strings.TrimSpace(resp.Composer) == "" && result.Composer != "" {
|
||||
resp.Composer = result.Composer
|
||||
}
|
||||
overlayStrTrim(&resp.Title, result.Title)
|
||||
overlayStrTrim(&resp.Artist, result.Artist)
|
||||
overlayStrTrim(&resp.Album, result.Album)
|
||||
overlayStrTrim(&resp.AlbumArtist, result.AlbumArtist)
|
||||
overlayInt(&resp.TrackNumber, result.TrackNumber, "")
|
||||
overlayInt(&resp.DiscNumber, result.DiscNumber, "")
|
||||
overlayInt(&resp.TotalTracks, result.TotalTracks, "")
|
||||
overlayInt(&resp.TotalDiscs, result.TotalDiscs, "")
|
||||
overlayStrTrim(&resp.ReleaseDate, result.ReleaseDate)
|
||||
overlayStrTrim(&resp.CoverURL, result.CoverURL)
|
||||
overlayStrTrim(&resp.ISRC, result.ISRC)
|
||||
overlayStrTrim(&resp.Genre, result.Genre)
|
||||
overlayStrTrim(&resp.Label, result.Label)
|
||||
overlayStrTrim(&resp.Copyright, result.Copyright)
|
||||
overlayStrTrim(&resp.Composer, result.Composer)
|
||||
if result.LyricsLRC != "" {
|
||||
resp.LyricsLRC = result.LyricsLRC
|
||||
}
|
||||
@@ -251,33 +253,15 @@ func applyExtensionRequestFallbacks(resp *DownloadResponse, req DownloadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.AlbumName != "" && resp.Album == "" {
|
||||
resp.Album = req.AlbumName
|
||||
}
|
||||
if req.AlbumArtist != "" && resp.AlbumArtist == "" {
|
||||
resp.AlbumArtist = req.AlbumArtist
|
||||
}
|
||||
if req.ReleaseDate != "" && resp.ReleaseDate == "" {
|
||||
resp.ReleaseDate = req.ReleaseDate
|
||||
}
|
||||
if req.ISRC != "" && resp.ISRC == "" {
|
||||
resp.ISRC = req.ISRC
|
||||
}
|
||||
if req.TrackNumber > 0 && resp.TrackNumber == 0 {
|
||||
resp.TrackNumber = req.TrackNumber
|
||||
}
|
||||
if req.TotalTracks > 0 && resp.TotalTracks == 0 {
|
||||
resp.TotalTracks = req.TotalTracks
|
||||
}
|
||||
if req.DiscNumber > 0 && resp.DiscNumber == 0 {
|
||||
resp.DiscNumber = req.DiscNumber
|
||||
}
|
||||
if req.TotalDiscs > 0 && resp.TotalDiscs == 0 {
|
||||
resp.TotalDiscs = req.TotalDiscs
|
||||
}
|
||||
if req.CoverURL != "" && resp.CoverURL == "" {
|
||||
resp.CoverURL = req.CoverURL
|
||||
}
|
||||
overlayStr(&resp.Album, req.AlbumName, "")
|
||||
overlayStr(&resp.AlbumArtist, req.AlbumArtist, "")
|
||||
overlayStr(&resp.ReleaseDate, req.ReleaseDate, "")
|
||||
overlayStr(&resp.ISRC, req.ISRC, "")
|
||||
overlayInt(&resp.TrackNumber, req.TrackNumber, "")
|
||||
overlayInt(&resp.TotalTracks, req.TotalTracks, "")
|
||||
overlayInt(&resp.DiscNumber, req.DiscNumber, "")
|
||||
overlayInt(&resp.TotalDiscs, req.TotalDiscs, "")
|
||||
overlayStr(&resp.CoverURL, req.CoverURL, "")
|
||||
}
|
||||
|
||||
func shouldStopProviderFallback(availability *ExtAvailabilityResult) bool {
|
||||
@@ -387,6 +371,119 @@ func normalizeExtensionDownloadErrorType(errorType, message string) string {
|
||||
return normalized
|
||||
}
|
||||
|
||||
// attemptExtensionDownload runs a single provider.Download attempt: builds the
|
||||
// output path, reports progress, and on success assembles the full
|
||||
// DownloadResponse (overlay, request fallbacks, optional title/artist/composer
|
||||
// fallback, metadata embed, ISRC index). On failure it writes into
|
||||
// lastErr/lastErrType/lastRetryAfterSeconds exactly as the inline code did
|
||||
// (leaving them untouched when neither branch applies) so callers can keep
|
||||
// their own verification_required/stop-fallback handling and error messages.
|
||||
// cancelledOuter true means the caller must return (nil, ErrDownloadCancelled).
|
||||
func attemptExtensionDownload(
|
||||
req DownloadRequest,
|
||||
ext *loadedExtension,
|
||||
provider *extensionProviderWrapper,
|
||||
trackID, quality, providerLabel string,
|
||||
applyTitleFallback bool,
|
||||
lastErr *error,
|
||||
lastErrType *string,
|
||||
lastRetryAfterSeconds *int,
|
||||
) (resp *DownloadResponse, cancelledOuter bool) {
|
||||
outputPath := buildOutputPathForExtension(req, ext)
|
||||
if req.ItemID != "" {
|
||||
StartItemProgress(req.ItemID)
|
||||
}
|
||||
|
||||
result, err := provider.Download(trackID, quality, outputPath, req.ItemID, func(percent int) {
|
||||
if req.ItemID != "" {
|
||||
normalized := float64(percent) / 100.0
|
||||
if normalized < 0 {
|
||||
normalized = 0
|
||||
}
|
||||
if normalized > 1 {
|
||||
normalized = 1
|
||||
}
|
||||
SetItemProgress(req.ItemID, normalized, 0, 0)
|
||||
}
|
||||
})
|
||||
if req.ItemID != "" {
|
||||
if err == nil && result != nil && result.Success {
|
||||
CompleteItemProgress(req.ItemID)
|
||||
} else {
|
||||
RemoveItemProgress(req.ItemID)
|
||||
}
|
||||
}
|
||||
if shouldAbortCancelledFallback(req.ItemID, err) {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if err == nil && result.Success {
|
||||
normalizedResult, alreadyExists := normalizeExtensionDownloadResult(result)
|
||||
message := "Downloaded from " + providerLabel
|
||||
if alreadyExists {
|
||||
message = "File already exists"
|
||||
}
|
||||
|
||||
built := buildDownloadSuccessResponse(
|
||||
req,
|
||||
normalizedResult,
|
||||
providerLabel,
|
||||
message,
|
||||
normalizedResult.FilePath,
|
||||
alreadyExists,
|
||||
)
|
||||
overlayExtensionDownloadMetadata(&built, result)
|
||||
if ext.Manifest.SkipMetadataEnrichment {
|
||||
built.SkipMetadataEnrichment = true
|
||||
}
|
||||
applyExtensionRequestFallbacks(&built, req)
|
||||
|
||||
if applyTitleFallback {
|
||||
if req.TrackName != "" && built.Title == "" {
|
||||
built.Title = req.TrackName
|
||||
}
|
||||
if req.ArtistName != "" && built.Artist == "" {
|
||||
built.Artist = req.ArtistName
|
||||
}
|
||||
if req.Composer != "" && built.Composer == "" {
|
||||
built.Composer = req.Composer
|
||||
}
|
||||
}
|
||||
|
||||
embedExtensionDownloadMetadata(built, req, alreadyExists)
|
||||
|
||||
if !alreadyExists && !isFDOutput(req.OutputFD) && strings.TrimSpace(req.OutputDir) != "" {
|
||||
indexISRC := strings.TrimSpace(built.ISRC)
|
||||
if indexISRC == "" {
|
||||
indexISRC = strings.TrimSpace(req.ISRC)
|
||||
}
|
||||
if indexISRC != "" && strings.TrimSpace(built.FilePath) != "" {
|
||||
AddToISRCIndex(req.OutputDir, indexISRC, built.FilePath)
|
||||
}
|
||||
}
|
||||
|
||||
return &built, false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrDownloadCancelled) {
|
||||
return &DownloadResponse{
|
||||
Success: false,
|
||||
Error: "Download cancelled",
|
||||
ErrorType: "cancelled",
|
||||
Service: providerLabel,
|
||||
}, false
|
||||
}
|
||||
*lastErr = err
|
||||
*lastErrType = ""
|
||||
} else if result.ErrorMessage != "" {
|
||||
*lastErr = fmt.Errorf("%s", result.ErrorMessage)
|
||||
*lastErrType = normalizeExtensionDownloadErrorType(result.ErrorType, result.ErrorMessage)
|
||||
*lastRetryAfterSeconds = result.RetryAfterSeconds
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, error) {
|
||||
priority := GetProviderPriority()
|
||||
extManager := getExtensionManager()
|
||||
@@ -507,60 +604,20 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
if enrichedTrack.Artists != "" {
|
||||
req.ArtistName = enrichedTrack.Artists
|
||||
}
|
||||
if enrichedTrack.AlbumName != "" && req.AlbumName == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] AlbumName from enrichment: %s\n", enrichedTrack.AlbumName)
|
||||
req.AlbumName = enrichedTrack.AlbumName
|
||||
}
|
||||
if enrichedTrack.AlbumArtist != "" && req.AlbumArtist == "" {
|
||||
req.AlbumArtist = enrichedTrack.AlbumArtist
|
||||
}
|
||||
if enrichedTrack.DurationMS > 0 && req.DurationMS == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] DurationMS from enrichment: %d\n", enrichedTrack.DurationMS)
|
||||
req.DurationMS = enrichedTrack.DurationMS
|
||||
}
|
||||
if enrichedTrack.CoverURL != "" && req.CoverURL == "" {
|
||||
req.CoverURL = enrichedTrack.CoverURL
|
||||
}
|
||||
if enrichedTrack.ID != "" && req.SpotifyID == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] Track ID from enrichment: %s\n", enrichedTrack.ID)
|
||||
req.SpotifyID = enrichedTrack.ID
|
||||
}
|
||||
if enrichedTrack.Label != "" && req.Label == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] Label from enrichment: %s\n", enrichedTrack.Label)
|
||||
req.Label = enrichedTrack.Label
|
||||
}
|
||||
if enrichedTrack.Copyright != "" && req.Copyright == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] Copyright from enrichment: %s\n", enrichedTrack.Copyright)
|
||||
req.Copyright = enrichedTrack.Copyright
|
||||
}
|
||||
if enrichedTrack.Genre != "" && req.Genre == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] Genre from enrichment: %s\n", enrichedTrack.Genre)
|
||||
req.Genre = enrichedTrack.Genre
|
||||
}
|
||||
if enrichedTrack.ReleaseDate != "" && req.ReleaseDate == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] ReleaseDate from enrichment: %s\n", enrichedTrack.ReleaseDate)
|
||||
req.ReleaseDate = enrichedTrack.ReleaseDate
|
||||
}
|
||||
if enrichedTrack.TrackNumber > 0 && req.TrackNumber == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] TrackNumber from enrichment: %d\n", enrichedTrack.TrackNumber)
|
||||
req.TrackNumber = enrichedTrack.TrackNumber
|
||||
}
|
||||
if enrichedTrack.TotalTracks > 0 && req.TotalTracks == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] TotalTracks from enrichment: %d\n", enrichedTrack.TotalTracks)
|
||||
req.TotalTracks = enrichedTrack.TotalTracks
|
||||
}
|
||||
if enrichedTrack.DiscNumber > 0 && req.DiscNumber == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] DiscNumber from enrichment: %d\n", enrichedTrack.DiscNumber)
|
||||
req.DiscNumber = enrichedTrack.DiscNumber
|
||||
}
|
||||
if enrichedTrack.TotalDiscs > 0 && req.TotalDiscs == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] TotalDiscs from enrichment: %d\n", enrichedTrack.TotalDiscs)
|
||||
req.TotalDiscs = enrichedTrack.TotalDiscs
|
||||
}
|
||||
if enrichedTrack.Composer != "" && req.Composer == "" {
|
||||
GoLog("[DownloadWithExtensionFallback] Composer from enrichment: %s\n", enrichedTrack.Composer)
|
||||
req.Composer = enrichedTrack.Composer
|
||||
}
|
||||
overlayStr(&req.AlbumName, enrichedTrack.AlbumName, "AlbumName")
|
||||
overlayStr(&req.AlbumArtist, enrichedTrack.AlbumArtist, "")
|
||||
overlayInt(&req.DurationMS, enrichedTrack.DurationMS, "DurationMS")
|
||||
overlayStr(&req.CoverURL, enrichedTrack.CoverURL, "")
|
||||
overlayStr(&req.SpotifyID, enrichedTrack.ID, "Track ID")
|
||||
overlayStr(&req.Label, enrichedTrack.Label, "Label")
|
||||
overlayStr(&req.Copyright, enrichedTrack.Copyright, "Copyright")
|
||||
overlayStr(&req.Genre, enrichedTrack.Genre, "Genre")
|
||||
overlayStr(&req.ReleaseDate, enrichedTrack.ReleaseDate, "ReleaseDate")
|
||||
overlayInt(&req.TrackNumber, enrichedTrack.TrackNumber, "TrackNumber")
|
||||
overlayInt(&req.TotalTracks, enrichedTrack.TotalTracks, "TotalTracks")
|
||||
overlayInt(&req.DiscNumber, enrichedTrack.DiscNumber, "DiscNumber")
|
||||
overlayInt(&req.TotalDiscs, enrichedTrack.TotalDiscs, "TotalDiscs")
|
||||
overlayStr(&req.Composer, enrichedTrack.Composer, "Composer")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -581,45 +638,19 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
GoLog("[DownloadWithExtensionFallback] Metadata match (%s): %s - %s (album: %s, date: %s, isrc: %s)\n",
|
||||
track.ProviderID, track.Name, track.Artists, track.AlbumName, track.ReleaseDate, track.ISRC)
|
||||
|
||||
if track.AlbumName != "" && req.AlbumName == "" {
|
||||
req.AlbumName = track.AlbumName
|
||||
}
|
||||
if track.AlbumArtist != "" && req.AlbumArtist == "" {
|
||||
req.AlbumArtist = track.AlbumArtist
|
||||
}
|
||||
if track.ReleaseDate != "" && req.ReleaseDate == "" {
|
||||
req.ReleaseDate = track.ReleaseDate
|
||||
}
|
||||
if track.ISRC != "" && req.ISRC == "" {
|
||||
req.ISRC = track.ISRC
|
||||
}
|
||||
if track.TrackNumber > 0 && req.TrackNumber == 0 {
|
||||
req.TrackNumber = track.TrackNumber
|
||||
}
|
||||
if track.TotalTracks > 0 && req.TotalTracks == 0 {
|
||||
req.TotalTracks = track.TotalTracks
|
||||
}
|
||||
if track.DiscNumber > 0 && req.DiscNumber == 0 {
|
||||
req.DiscNumber = track.DiscNumber
|
||||
}
|
||||
if track.TotalDiscs > 0 && req.TotalDiscs == 0 {
|
||||
req.TotalDiscs = track.TotalDiscs
|
||||
}
|
||||
if track.Composer != "" && req.Composer == "" {
|
||||
req.Composer = track.Composer
|
||||
}
|
||||
if track.CoverURL != "" && req.CoverURL == "" {
|
||||
req.CoverURL = track.CoverURL
|
||||
}
|
||||
if track.Genre != "" && req.Genre == "" {
|
||||
req.Genre = track.Genre
|
||||
}
|
||||
if track.Label != "" && req.Label == "" {
|
||||
req.Label = track.Label
|
||||
}
|
||||
if track.Copyright != "" && req.Copyright == "" {
|
||||
req.Copyright = track.Copyright
|
||||
}
|
||||
overlayStr(&req.AlbumName, track.AlbumName, "")
|
||||
overlayStr(&req.AlbumArtist, track.AlbumArtist, "")
|
||||
overlayStr(&req.ReleaseDate, track.ReleaseDate, "")
|
||||
overlayStr(&req.ISRC, track.ISRC, "")
|
||||
overlayInt(&req.TrackNumber, track.TrackNumber, "")
|
||||
overlayInt(&req.TotalTracks, track.TotalTracks, "")
|
||||
overlayInt(&req.DiscNumber, track.DiscNumber, "")
|
||||
overlayInt(&req.TotalDiscs, track.TotalDiscs, "")
|
||||
overlayStr(&req.Composer, track.Composer, "")
|
||||
overlayStr(&req.CoverURL, track.CoverURL, "")
|
||||
overlayStr(&req.Genre, track.Genre, "")
|
||||
overlayStr(&req.Label, track.Label, "")
|
||||
overlayStr(&req.Copyright, track.Copyright, "")
|
||||
} else if searchErr != nil {
|
||||
GoLog("[DownloadWithExtensionFallback] Metadata provider search failed (non-fatal): %v\n", searchErr)
|
||||
}
|
||||
@@ -655,95 +686,12 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
|
||||
GoLog("[DownloadWithExtensionFallback] Downloading from source extension with trackID: %s (stopProviderFallback: %v)\n", trackID, stopProviderFallback)
|
||||
|
||||
outputPath := buildOutputPathForExtension(req, ext)
|
||||
if req.ItemID != "" {
|
||||
StartItemProgress(req.ItemID)
|
||||
}
|
||||
|
||||
result, err := provider.Download(trackID, req.Quality, outputPath, req.ItemID, func(percent int) {
|
||||
if req.ItemID != "" {
|
||||
normalized := float64(percent) / 100.0
|
||||
if normalized < 0 {
|
||||
normalized = 0
|
||||
}
|
||||
if normalized > 1 {
|
||||
normalized = 1
|
||||
}
|
||||
SetItemProgress(req.ItemID, normalized, 0, 0)
|
||||
}
|
||||
})
|
||||
if req.ItemID != "" {
|
||||
if err == nil && result != nil && result.Success {
|
||||
CompleteItemProgress(req.ItemID)
|
||||
} else {
|
||||
RemoveItemProgress(req.ItemID)
|
||||
}
|
||||
}
|
||||
if shouldAbortCancelledFallback(req.ItemID, err) {
|
||||
resp, cancelledOuter := attemptExtensionDownload(req, ext, provider, trackID, req.Quality, req.Source, true, &lastErr, &lastErrType, &lastRetryAfterSeconds)
|
||||
if cancelledOuter {
|
||||
return nil, ErrDownloadCancelled
|
||||
}
|
||||
|
||||
if err == nil && result.Success {
|
||||
normalizedResult, alreadyExists := normalizeExtensionDownloadResult(result)
|
||||
message := "Downloaded from " + req.Source
|
||||
if alreadyExists {
|
||||
message = "File already exists"
|
||||
}
|
||||
|
||||
resp := buildDownloadSuccessResponse(
|
||||
req,
|
||||
normalizedResult,
|
||||
req.Source,
|
||||
message,
|
||||
normalizedResult.FilePath,
|
||||
alreadyExists,
|
||||
)
|
||||
overlayExtensionDownloadMetadata(&resp, result)
|
||||
if ext.Manifest.SkipMetadataEnrichment {
|
||||
resp.SkipMetadataEnrichment = true
|
||||
}
|
||||
applyExtensionRequestFallbacks(&resp, req)
|
||||
|
||||
if req.TrackName != "" && resp.Title == "" {
|
||||
resp.Title = req.TrackName
|
||||
}
|
||||
if req.ArtistName != "" && resp.Artist == "" {
|
||||
resp.Artist = req.ArtistName
|
||||
}
|
||||
if req.Composer != "" && resp.Composer == "" {
|
||||
resp.Composer = req.Composer
|
||||
}
|
||||
|
||||
embedExtensionDownloadMetadata(resp, req, alreadyExists)
|
||||
|
||||
if !alreadyExists && !isFDOutput(req.OutputFD) && strings.TrimSpace(req.OutputDir) != "" {
|
||||
indexISRC := strings.TrimSpace(resp.ISRC)
|
||||
if indexISRC == "" {
|
||||
indexISRC = strings.TrimSpace(req.ISRC)
|
||||
}
|
||||
if indexISRC != "" && strings.TrimSpace(resp.FilePath) != "" {
|
||||
AddToISRCIndex(req.OutputDir, indexISRC, resp.FilePath)
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrDownloadCancelled) {
|
||||
return &DownloadResponse{
|
||||
Success: false,
|
||||
Error: "Download cancelled",
|
||||
ErrorType: "cancelled",
|
||||
Service: req.Source,
|
||||
}, nil
|
||||
}
|
||||
lastErr = err
|
||||
lastErrType = ""
|
||||
} else if result.ErrorMessage != "" {
|
||||
lastErr = fmt.Errorf("%s", result.ErrorMessage)
|
||||
lastErrType = normalizeExtensionDownloadErrorType(result.ErrorType, result.ErrorMessage)
|
||||
lastRetryAfterSeconds = result.RetryAfterSeconds
|
||||
if resp != nil {
|
||||
return resp, nil
|
||||
}
|
||||
GoLog("[DownloadWithExtensionFallback] Source extension %s failed: %v\n", req.Source, lastErr)
|
||||
|
||||
@@ -840,10 +788,6 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
}
|
||||
|
||||
req.OutputExt = ""
|
||||
outputPath := buildOutputPathForExtension(req, ext)
|
||||
if req.ItemID != "" {
|
||||
StartItemProgress(req.ItemID)
|
||||
}
|
||||
|
||||
// Honor the requested quality when this provider recognizes it
|
||||
// (e.g. an explicit user selection). Only when the token is not
|
||||
@@ -868,80 +812,12 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
}
|
||||
}
|
||||
|
||||
result, err := provider.Download(availability.TrackID, fallbackQuality, outputPath, req.ItemID, func(percent int) {
|
||||
if req.ItemID != "" {
|
||||
normalized := float64(percent) / 100.0
|
||||
if normalized < 0 {
|
||||
normalized = 0
|
||||
}
|
||||
if normalized > 1 {
|
||||
normalized = 1
|
||||
}
|
||||
SetItemProgress(req.ItemID, normalized, 0, 0)
|
||||
}
|
||||
})
|
||||
if req.ItemID != "" {
|
||||
if err == nil && result != nil && result.Success {
|
||||
CompleteItemProgress(req.ItemID)
|
||||
} else {
|
||||
RemoveItemProgress(req.ItemID)
|
||||
}
|
||||
}
|
||||
if shouldAbortCancelledFallback(req.ItemID, err) {
|
||||
resp, cancelledOuter := attemptExtensionDownload(req, ext, provider, availability.TrackID, fallbackQuality, providerID, false, &lastErr, &lastErrType, &lastRetryAfterSeconds)
|
||||
if cancelledOuter {
|
||||
return nil, ErrDownloadCancelled
|
||||
}
|
||||
|
||||
if err == nil && result.Success {
|
||||
normalizedResult, alreadyExists := normalizeExtensionDownloadResult(result)
|
||||
message := "Downloaded from " + providerID
|
||||
if alreadyExists {
|
||||
message = "File already exists"
|
||||
}
|
||||
|
||||
resp := buildDownloadSuccessResponse(
|
||||
req,
|
||||
normalizedResult,
|
||||
providerID,
|
||||
message,
|
||||
normalizedResult.FilePath,
|
||||
alreadyExists,
|
||||
)
|
||||
overlayExtensionDownloadMetadata(&resp, result)
|
||||
if ext.Manifest.SkipMetadataEnrichment {
|
||||
resp.SkipMetadataEnrichment = true
|
||||
}
|
||||
applyExtensionRequestFallbacks(&resp, req)
|
||||
|
||||
embedExtensionDownloadMetadata(resp, req, alreadyExists)
|
||||
|
||||
if !alreadyExists && !isFDOutput(req.OutputFD) && strings.TrimSpace(req.OutputDir) != "" {
|
||||
indexISRC := strings.TrimSpace(resp.ISRC)
|
||||
if indexISRC == "" {
|
||||
indexISRC = strings.TrimSpace(req.ISRC)
|
||||
}
|
||||
if indexISRC != "" && strings.TrimSpace(resp.FilePath) != "" {
|
||||
AddToISRCIndex(req.OutputDir, indexISRC, resp.FilePath)
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrDownloadCancelled) {
|
||||
return &DownloadResponse{
|
||||
Success: false,
|
||||
Error: "Download cancelled",
|
||||
ErrorType: "cancelled",
|
||||
Service: providerID,
|
||||
}, nil
|
||||
}
|
||||
lastErr = err
|
||||
lastErrType = ""
|
||||
} else if result.ErrorMessage != "" {
|
||||
lastErr = fmt.Errorf("%s", result.ErrorMessage)
|
||||
lastErrType = normalizeExtensionDownloadErrorType(result.ErrorType, result.ErrorMessage)
|
||||
lastRetryAfterSeconds = result.RetryAfterSeconds
|
||||
if resp != nil {
|
||||
return resp, nil
|
||||
}
|
||||
GoLog("[DownloadWithExtensionFallback] %s failed: %v\n", providerID, lastErr)
|
||||
|
||||
@@ -989,11 +865,9 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildOutputPath(req DownloadRequest) string {
|
||||
if strings.TrimSpace(req.OutputPath) != "" {
|
||||
return strings.TrimSpace(req.OutputPath)
|
||||
}
|
||||
|
||||
// buildDownloadFilename renders the sanitized "<name><ext>" filename for req
|
||||
// from its template/metadata, defaulting to "<artist> - <title>.flac".
|
||||
func buildDownloadFilename(req DownloadRequest) string {
|
||||
metadata := map[string]any{
|
||||
"title": req.TrackName,
|
||||
"artist": req.ArtistName,
|
||||
@@ -1026,6 +900,14 @@ func buildOutputPath(req DownloadRequest) string {
|
||||
ext = "." + ext
|
||||
}
|
||||
|
||||
return filename + ext
|
||||
}
|
||||
|
||||
func buildOutputPath(req DownloadRequest) string {
|
||||
if strings.TrimSpace(req.OutputPath) != "" {
|
||||
return strings.TrimSpace(req.OutputPath)
|
||||
}
|
||||
|
||||
outputDir := req.OutputDir
|
||||
if strings.TrimSpace(outputDir) == "" {
|
||||
outputDir = filepath.Join(os.TempDir(), "spotiflac-downloads")
|
||||
@@ -1033,7 +915,7 @@ func buildOutputPath(req DownloadRequest) string {
|
||||
os.MkdirAll(outputDir, 0755)
|
||||
AddAllowedDownloadDir(outputDir)
|
||||
|
||||
return filepath.Join(outputDir, filename+ext)
|
||||
return filepath.Join(outputDir, buildDownloadFilename(req))
|
||||
}
|
||||
|
||||
func buildOutputPathForExtension(req DownloadRequest, ext *loadedExtension) string {
|
||||
@@ -1054,39 +936,7 @@ func buildOutputPathForExtension(req DownloadRequest, ext *loadedExtension) stri
|
||||
os.MkdirAll(tempDir, 0755)
|
||||
AddAllowedDownloadDir(tempDir)
|
||||
|
||||
metadata := map[string]any{
|
||||
"title": req.TrackName,
|
||||
"artist": req.ArtistName,
|
||||
"album": req.AlbumName,
|
||||
"album_artist": req.AlbumArtist,
|
||||
"track": req.TrackNumber,
|
||||
"track_number": req.TrackNumber,
|
||||
"total_tracks": req.TotalTracks,
|
||||
"playlist_position": req.PlaylistPosition,
|
||||
"disc": req.DiscNumber,
|
||||
"disc_number": req.DiscNumber,
|
||||
"total_discs": req.TotalDiscs,
|
||||
"year": extractYear(req.ReleaseDate),
|
||||
"date": req.ReleaseDate,
|
||||
"release_date": req.ReleaseDate,
|
||||
"isrc": req.ISRC,
|
||||
"composer": req.Composer,
|
||||
}
|
||||
|
||||
filename := buildFilenameFromTemplate(req.FilenameFormat, metadata)
|
||||
if strings.TrimSpace(filename) == "" {
|
||||
filename = fmt.Sprintf("%s - %s", req.ArtistName, req.TrackName)
|
||||
}
|
||||
filename = sanitizeFilename(filename)
|
||||
|
||||
outputExt := strings.TrimSpace(req.OutputExt)
|
||||
if outputExt == "" {
|
||||
outputExt = ".flac"
|
||||
} else if !strings.HasPrefix(outputExt, ".") {
|
||||
outputExt = "." + outputExt
|
||||
}
|
||||
|
||||
return filepath.Join(tempDir, filename+outputExt)
|
||||
return filepath.Join(tempDir, buildDownloadFilename(req))
|
||||
}
|
||||
|
||||
func canEmbedGenreLabel(filePath string) bool {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -252,57 +252,22 @@ func (m *extensionManager) GetPostProcessingProviders() []*extensionProviderWrap
|
||||
return providers
|
||||
}
|
||||
|
||||
func (m *extensionManager) RunPostProcessing(filePath string, metadata map[string]any) (*PostProcessResult, error) {
|
||||
providers := m.GetPostProcessingProviders()
|
||||
if len(providers) == 0 {
|
||||
return &PostProcessResult{Success: true, NewFilePath: filePath}, nil
|
||||
}
|
||||
|
||||
currentPath := filePath
|
||||
for _, provider := range providers {
|
||||
hooks := provider.extension.Manifest.GetPostProcessingHooks()
|
||||
for _, hook := range hooks {
|
||||
if !hook.DefaultEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(currentPath))
|
||||
if len(hook.SupportedFormats) > 0 {
|
||||
supported := false
|
||||
for _, format := range hook.SupportedFormats {
|
||||
if "."+format == ext || format == ext[1:] {
|
||||
supported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !supported {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
GoLog("[PostProcess] Running hook %s from %s on %s\n", hook.ID, provider.extension.ID, currentPath)
|
||||
|
||||
result, err := provider.PostProcess(currentPath, metadata, hook.ID)
|
||||
if err != nil {
|
||||
GoLog("[PostProcess] Hook %s failed: %v\n", hook.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result.Success && result.NewFilePath != "" {
|
||||
currentPath = result.NewFilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &PostProcessResult{Success: true, NewFilePath: currentPath}, nil
|
||||
}
|
||||
|
||||
func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]any) (*PostProcessResult, error) {
|
||||
// runPostProcessingCommon backs both RunPostProcessing (V1) and
|
||||
// RunPostProcessingV2. V1 delegates into this shared loop with an equivalent
|
||||
// PostProcessInput; preferV2 controls whether each hook is invoked via
|
||||
// provider.PostProcessV2 or provider.PostProcess, so V1 keeps calling
|
||||
// PostProcess (not PostProcessV2) exactly as it did before.
|
||||
func (m *extensionManager) runPostProcessingCommon(input PostProcessInput, metadata map[string]any, preferV2 bool) (*PostProcessResult, error) {
|
||||
providers := m.GetPostProcessingProviders()
|
||||
if len(providers) == 0 {
|
||||
return &PostProcessResult{Success: true, NewFilePath: input.Path, NewFileURI: input.URI}, nil
|
||||
}
|
||||
|
||||
logTag := "[PostProcess]"
|
||||
if preferV2 {
|
||||
logTag = "[PostProcessV2]"
|
||||
}
|
||||
|
||||
currentInput := input
|
||||
for _, provider := range providers {
|
||||
hooks := provider.extension.Manifest.GetPostProcessingHooks()
|
||||
@@ -328,11 +293,17 @@ func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata
|
||||
}
|
||||
}
|
||||
|
||||
GoLog("[PostProcessV2] Running hook %s from %s on %s\n", hook.ID, provider.extension.ID, currentInput.Path)
|
||||
GoLog("%s Running hook %s from %s on %s\n", logTag, hook.ID, provider.extension.ID, currentInput.Path)
|
||||
|
||||
result, err := provider.PostProcessV2(currentInput, metadata, hook.ID)
|
||||
var result *PostProcessResult
|
||||
var err error
|
||||
if preferV2 {
|
||||
result, err = provider.PostProcessV2(currentInput, metadata, hook.ID)
|
||||
} else {
|
||||
result, err = provider.PostProcess(currentInput.Path, metadata, hook.ID)
|
||||
}
|
||||
if err != nil {
|
||||
GoLog("[PostProcessV2] Hook %s failed: %v\n", hook.ID, err)
|
||||
GoLog("%s Hook %s failed: %v\n", logTag, hook.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -351,6 +322,18 @@ func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata
|
||||
return &PostProcessResult{Success: true, NewFilePath: currentInput.Path, NewFileURI: currentInput.URI}, nil
|
||||
}
|
||||
|
||||
func (m *extensionManager) RunPostProcessing(filePath string, metadata map[string]any) (*PostProcessResult, error) {
|
||||
result, err := m.runPostProcessingCommon(PostProcessInput{Path: filePath}, metadata, false)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
return &PostProcessResult{Success: result.Success, NewFilePath: result.NewFilePath}, nil
|
||||
}
|
||||
|
||||
func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]any) (*PostProcessResult, error) {
|
||||
return m.runPostProcessingCommon(input, metadata, true)
|
||||
}
|
||||
|
||||
func (m *extensionManager) GetLyricsProviders() []*extensionProviderWrapper {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user