feat: add SongLink region setting and fix track metadata lookup with name+artist fallback

- Add configurable SongLink region (userCountry) setting with picker UI
- Pass songLinkRegion through download request payload to Go backend
- Go backend: thread-safe global SongLink region with per-request override
- Fix downloaded track not recognized in collection tap: add findByTrackAndArtist
  fallback in download history lookup chain (Spotify ID → ISRC → name+artist)
- Apply same name+artist fallback to isDownloaded check in track options sheet
- Add missing library_database.dart import for LocalLibraryItem
This commit is contained in:
zarzet
2026-02-19 19:16:55 +07:00
parent ab72a10578
commit 882afd938b
9 changed files with 446 additions and 19 deletions
+12
View File
@@ -177,6 +177,7 @@ type DownloadRequest struct {
LyricsMode string `json:"lyrics_mode,omitempty"`
UseExtensions bool `json:"use_extensions,omitempty"`
UseFallback bool `json:"use_fallback,omitempty"`
SongLinkRegion string `json:"songlink_region,omitempty"`
}
type DownloadResponse struct {
@@ -378,11 +379,19 @@ func enrichRequestExtendedMetadata(req *DownloadRequest) {
}
}
func applySongLinkRegionFromRequest(req *DownloadRequest) {
if req == nil {
return
}
SetSongLinkRegion(req.SongLinkRegion)
}
func DownloadTrack(requestJSON string) (string, error) {
var req DownloadRequest
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
return errorResponse("Invalid request: " + err.Error())
}
applySongLinkRegionFromRequest(&req)
defer closeOwnedOutputFD(req.OutputFD)
req.TrackName = strings.TrimSpace(req.TrackName)
@@ -566,6 +575,7 @@ func DownloadWithFallback(requestJSON string) (string, error) {
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
return errorResponse("Invalid request: " + err.Error())
}
applySongLinkRegionFromRequest(&req)
defer closeOwnedOutputFD(req.OutputFD)
req.TrackName = strings.TrimSpace(req.TrackName)
@@ -1533,6 +1543,7 @@ func DownloadFromYouTube(requestJSON string) (string, error) {
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
return errorResponse("Invalid request: " + err.Error())
}
applySongLinkRegionFromRequest(&req)
defer closeOwnedOutputFD(req.OutputFD)
req.TrackName = strings.TrimSpace(req.TrackName)
@@ -2251,6 +2262,7 @@ func DownloadWithExtensionsJSON(requestJSON string) (string, error) {
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
return "", fmt.Errorf("invalid request: %w", err)
}
applySongLinkRegionFromRequest(&req)
defer closeOwnedOutputFD(req.OutputFD)
req.TrackName = strings.TrimSpace(req.TrackName)
+37 -2
View File
@@ -34,6 +34,8 @@ type TrackAvailability struct {
var (
globalSongLinkClient *SongLinkClient
songLinkClientOnce sync.Once
songLinkRegion = "US"
songLinkRegionMu sync.RWMutex
)
func NewSongLinkClient() *SongLinkClient {
@@ -45,6 +47,33 @@ func NewSongLinkClient() *SongLinkClient {
return globalSongLinkClient
}
func normalizeSongLinkRegion(region string) string {
normalized := strings.ToUpper(strings.TrimSpace(region))
if len(normalized) != 2 {
return "US"
}
for _, ch := range normalized {
if ch < 'A' || ch > 'Z' {
return "US"
}
}
return normalized
}
func SetSongLinkRegion(region string) {
normalized := normalizeSongLinkRegion(region)
songLinkRegionMu.Lock()
songLinkRegion = normalized
songLinkRegionMu.Unlock()
}
func GetSongLinkRegion() string {
songLinkRegionMu.RLock()
region := songLinkRegion
songLinkRegionMu.RUnlock()
return region
}
func songLinkBaseURL() string {
opts := GetNetworkCompatibilityOptions()
if opts.AllowHTTP {
@@ -54,6 +83,9 @@ func songLinkBaseURL() string {
}
func buildSongLinkURLFromTarget(targetURL string, userCountry string) string {
if userCountry == "" {
userCountry = GetSongLinkRegion()
}
apiURL := fmt.Sprintf("%s?url=%s", songLinkBaseURL(), url.QueryEscape(targetURL))
if userCountry != "" {
apiURL = fmt.Sprintf("%s&userCountry=%s", apiURL, url.QueryEscape(userCountry))
@@ -62,6 +94,9 @@ func buildSongLinkURLFromTarget(targetURL string, userCountry string) string {
}
func buildSongLinkURLByPlatform(platform, entityType, entityID, userCountry string) string {
if userCountry == "" {
userCountry = GetSongLinkRegion()
}
apiURL := fmt.Sprintf("%s?platform=%s&type=%s&id=%s",
songLinkBaseURL(),
url.QueryEscape(platform),
@@ -448,7 +483,7 @@ func (s *SongLinkClient) checkAvailabilityFromDeezerSongLink(deezerTrackID strin
songLinkRateLimiter.WaitForSlot()
deezerURL := fmt.Sprintf("https://www.deezer.com/track/%s", deezerTrackID)
apiURL := buildSongLinkURLFromTarget(deezerURL, "US")
apiURL := buildSongLinkURLFromTarget(deezerURL, "")
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
@@ -552,7 +587,7 @@ func (s *SongLinkClient) CheckAvailabilityByPlatform(platform, entityType, entit
songLinkRateLimiter.WaitForSlot()
apiURL := buildSongLinkURLByPlatform(platform, entityType, entityID, "US")
apiURL := buildSongLinkURLByPlatform(platform, entityType, entityID, "")
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {