From 77a99a735b723c25015b1306bbfd250c40afdaaf Mon Sep 17 00:00:00 2001
From: Deluan <deluan@navidrome.org>
Date: Sat, 31 Dec 2022 17:29:58 -0500
Subject: [PATCH] Always access artist images through Navidrome (proxy calls to
 external URLs)

---
 core/agents/local_agent.go     | 18 ------------------
 core/artwork/reader_artist.go  |  9 ++++++---
 server/subsonic/album_lists.go |  2 +-
 server/subsonic/browsing.go    | 22 ++++++++++++----------
 server/subsonic/helpers.go     | 21 +++++++++++++++------
 server/subsonic/searching.go   | 12 +++++++-----
 6 files changed, 41 insertions(+), 43 deletions(-)

diff --git a/core/agents/local_agent.go b/core/agents/local_agent.go
index e2ae4cb7d..9c25b1227 100644
--- a/core/agents/local_agent.go
+++ b/core/agents/local_agent.go
@@ -2,10 +2,7 @@ package agents
 
 import (
 	"context"
-	"path/filepath"
 
-	"github.com/navidrome/navidrome/consts"
-	"github.com/navidrome/navidrome/core/artwork"
 	"github.com/navidrome/navidrome/model"
 )
 
@@ -29,25 +26,10 @@ func (p *localAgent) GetBiography(ctx context.Context, id, name, mbid string) (s
 	return localBiography, nil
 }
 
-func (p *localAgent) GetImages(_ context.Context, id, name, mbid string) ([]ArtistImage, error) {
-	return []ArtistImage{
-		p.artistImage(id, 300),
-		p.artistImage(id, 174),
-		p.artistImage(id, 64),
-	}, nil
-}
-
 func (p *localAgent) GetTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error) {
 	return nil, nil // TODO return 5-stars and liked songs sorted by playCount
 }
 
-func (p *localAgent) artistImage(id string, size int) ArtistImage {
-	return ArtistImage{
-		filepath.Join(consts.URLPathPublicImages, artwork.PublicLink(model.NewArtworkID(model.KindArtistArtwork, id), size)),
-		size,
-	}
-}
-
 func init() {
 	Register(LocalAgentName, localsConstructor)
 }
diff --git a/core/artwork/reader_artist.go b/core/artwork/reader_artist.go
index da54b77ea..e2ad1d57c 100644
--- a/core/artwork/reader_artist.go
+++ b/core/artwork/reader_artist.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -16,7 +17,7 @@ type artistReader struct {
 	cacheKey
 	a      *artwork
 	artist model.Artist
-	files  []string
+	files  string
 }
 
 func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkID) (*artistReader, error) {
@@ -33,12 +34,14 @@ func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkI
 		artist: *ar,
 	}
 	a.cacheKey.lastUpdate = ar.ExternalInfoUpdatedAt
+	var files []string
 	for _, al := range als {
-		a.files = append(a.files, al.ImageFiles)
+		files = append(files, al.ImageFiles)
 		if a.cacheKey.lastUpdate.Before(al.UpdatedAt) {
 			a.cacheKey.lastUpdate = al.UpdatedAt
 		}
 	}
+	a.files = strings.Join(files, string(filepath.ListSeparator))
 	a.cacheKey.artID = artID
 	return a, nil
 }
@@ -49,7 +52,7 @@ func (a *artistReader) LastUpdated() time.Time {
 
 func (a *artistReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
 	return selectImageReader(ctx, a.artID,
-		//fromExternalFile()
+		fromExternalFile(ctx, a.files, "artist.*"),
 		fromExternalSource(ctx, a.artist),
 		fromArtistPlaceholder(),
 	)
diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go
index b0c32afb0..4944f8d53 100644
--- a/server/subsonic/album_lists.go
+++ b/server/subsonic/album_lists.go
@@ -124,7 +124,7 @@ func (api *Router) GetStarred(r *http.Request) (*responses.Subsonic, error) {
 
 	response := newResponse()
 	response.Starred = &responses.Starred{}
-	response.Starred.Artist = toArtists(ctx, artists)
+	response.Starred.Artist = toArtists(r, artists)
 	response.Starred.Album = childrenFromAlbums(r.Context(), albums)
 	response.Starred.Song = childrenFromMediaFiles(r.Context(), mediaFiles)
 	return response, nil
diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go
index 044a535b2..9b023e98b 100644
--- a/server/subsonic/browsing.go
+++ b/server/subsonic/browsing.go
@@ -28,7 +28,8 @@ func (api *Router) GetMusicFolders(r *http.Request) (*responses.Subsonic, error)
 	return response, nil
 }
 
-func (api *Router) getArtistIndex(ctx context.Context, mediaFolderId int, ifModifiedSince time.Time) (*responses.Indexes, error) {
+func (api *Router) getArtistIndex(r *http.Request, mediaFolderId int, ifModifiedSince time.Time) (*responses.Indexes, error) {
+	ctx := r.Context()
 	folder, err := api.ds.MediaFolder(ctx).Get(int32(mediaFolderId))
 	if err != nil {
 		log.Error(ctx, "Error retrieving MediaFolder", "id", mediaFolderId, err)
@@ -60,7 +61,7 @@ func (api *Router) getArtistIndex(ctx context.Context, mediaFolderId int, ifModi
 	res.Index = make([]responses.Index, len(indexes))
 	for i, idx := range indexes {
 		res.Index[i].Name = idx.ID
-		res.Index[i].Artists = toArtists(ctx, idx.Artists)
+		res.Index[i].Artists = toArtists(r, idx.Artists)
 	}
 	return res, nil
 }
@@ -69,7 +70,7 @@ func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) {
 	musicFolderId := utils.ParamInt(r, "musicFolderId", 0)
 	ifModifiedSince := utils.ParamTime(r, "ifModifiedSince", time.Time{})
 
-	res, err := api.getArtistIndex(r.Context(), musicFolderId, ifModifiedSince)
+	res, err := api.getArtistIndex(r, musicFolderId, ifModifiedSince)
 	if err != nil {
 		return nil, err
 	}
@@ -81,7 +82,7 @@ func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) {
 
 func (api *Router) GetArtists(r *http.Request) (*responses.Subsonic, error) {
 	musicFolderId := utils.ParamInt(r, "musicFolderId", 0)
-	res, err := api.getArtistIndex(r.Context(), musicFolderId, time.Time{})
+	res, err := api.getArtistIndex(r, musicFolderId, time.Time{})
 	if err != nil {
 		return nil, err
 	}
@@ -148,7 +149,7 @@ func (api *Router) GetArtist(r *http.Request) (*responses.Subsonic, error) {
 	}
 
 	response := newResponse()
-	response.ArtistWithAlbumsID3 = api.buildArtist(ctx, artist, albums)
+	response.ArtistWithAlbumsID3 = api.buildArtist(r, artist, albums)
 	return response, nil
 }
 
@@ -238,7 +239,7 @@ func (api *Router) GetArtistInfo(r *http.Request) (*responses.Subsonic, error) {
 	response.ArtistInfo.LastFmUrl = artist.ExternalUrl
 	response.ArtistInfo.MusicBrainzID = artist.MbzArtistID
 	for _, s := range artist.SimilarArtists {
-		similar := toArtist(ctx, s)
+		similar := toArtist(r, s)
 		response.ArtistInfo.SimilarArtist = append(response.ArtistInfo.SimilarArtist, similar)
 	}
 	return response, nil
@@ -260,7 +261,8 @@ func (api *Router) GetArtistInfo2(r *http.Request) (*responses.Subsonic, error)
 		similar.AlbumCount = s.AlbumCount
 		similar.Starred = s.Starred
 		similar.UserRating = s.UserRating
-		similar.ArtistImageUrl = server.AbsoluteURL(r, s.ArtistImageUrl)
+		similar.CoverArt = s.CoverArt
+		similar.ArtistImageUrl = s.ArtistImageUrl
 		response.ArtistInfo2.SimilarArtist = append(response.ArtistInfo2.SimilarArtist, similar)
 	}
 	return response, nil
@@ -342,10 +344,10 @@ func (api *Router) buildArtistDirectory(ctx context.Context, artist *model.Artis
 	return dir, nil
 }
 
-func (api *Router) buildArtist(ctx context.Context, artist *model.Artist, albums model.Albums) *responses.ArtistWithAlbumsID3 {
+func (api *Router) buildArtist(r *http.Request, artist *model.Artist, albums model.Albums) *responses.ArtistWithAlbumsID3 {
 	a := &responses.ArtistWithAlbumsID3{}
-	a.ArtistID3 = toArtistID3(ctx, *artist)
-	a.Album = childrenFromAlbums(ctx, albums)
+	a.ArtistID3 = toArtistID3(r, *artist)
+	a.Album = childrenFromAlbums(r.Context(), albums)
 	return a
 }
 
diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go
index 4ea5447df..f77cd05fa 100644
--- a/server/subsonic/helpers.go
+++ b/server/subsonic/helpers.go
@@ -5,11 +5,14 @@ import (
 	"fmt"
 	"mime"
 	"net/http"
+	"path/filepath"
 	"strings"
 
 	"github.com/navidrome/navidrome/consts"
+	"github.com/navidrome/navidrome/core/artwork"
 	"github.com/navidrome/navidrome/model"
 	"github.com/navidrome/navidrome/model/request"
+	"github.com/navidrome/navidrome/server"
 	"github.com/navidrome/navidrome/server/subsonic/responses"
 	"github.com/navidrome/navidrome/utils"
 )
@@ -72,22 +75,22 @@ func getUser(ctx context.Context) model.User {
 	return model.User{}
 }
 
-func toArtists(ctx context.Context, artists model.Artists) []responses.Artist {
+func toArtists(r *http.Request, artists model.Artists) []responses.Artist {
 	as := make([]responses.Artist, len(artists))
 	for i, artist := range artists {
-		as[i] = toArtist(ctx, artist)
+		as[i] = toArtist(r, artist)
 	}
 	return as
 }
 
-func toArtist(_ context.Context, a model.Artist) responses.Artist {
+func toArtist(r *http.Request, a model.Artist) responses.Artist {
 	artist := responses.Artist{
 		Id:             a.ID,
 		Name:           a.Name,
 		AlbumCount:     a.AlbumCount,
 		UserRating:     a.Rating,
 		CoverArt:       a.CoverArtID().String(),
-		ArtistImageUrl: a.ArtistImageUrl(),
+		ArtistImageUrl: artistCoverArtURL(r, a.CoverArtID(), 0),
 	}
 	if a.Starred {
 		artist.Starred = &a.StarredAt
@@ -95,13 +98,13 @@ func toArtist(_ context.Context, a model.Artist) responses.Artist {
 	return artist
 }
 
-func toArtistID3(_ context.Context, a model.Artist) responses.ArtistID3 {
+func toArtistID3(r *http.Request, a model.Artist) responses.ArtistID3 {
 	artist := responses.ArtistID3{
 		Id:             a.ID,
 		Name:           a.Name,
 		AlbumCount:     a.AlbumCount,
 		CoverArt:       a.CoverArtID().String(),
-		ArtistImageUrl: a.ArtistImageUrl(),
+		ArtistImageUrl: artistCoverArtURL(r, a.CoverArtID(), 0),
 		UserRating:     a.Rating,
 	}
 	if a.Starred {
@@ -110,6 +113,12 @@ func toArtistID3(_ context.Context, a model.Artist) responses.ArtistID3 {
 	return artist
 }
 
+func artistCoverArtURL(r *http.Request, artID model.ArtworkID, size int) string {
+	link := artwork.PublicLink(artID, size)
+	url := filepath.Join(consts.URLPathPublicImages, link)
+	return server.AbsoluteURL(r, url)
+}
+
 func toGenres(genres model.Genres) *responses.Genres {
 	response := make([]responses.Genre, len(genres))
 	for i, g := range genres {
diff --git a/server/subsonic/searching.go b/server/subsonic/searching.go
index f1d8c37d5..90759240b 100644
--- a/server/subsonic/searching.go
+++ b/server/subsonic/searching.go
@@ -107,10 +107,12 @@ func (api *Router) Search2(r *http.Request) (*responses.Subsonic, error) {
 	for i, artist := range as {
 		artist := artist
 		searchResult2.Artist[i] = responses.Artist{
-			Id:         artist.ID,
-			Name:       artist.Name,
-			AlbumCount: artist.AlbumCount,
-			UserRating: artist.Rating,
+			Id:             artist.ID,
+			Name:           artist.Name,
+			AlbumCount:     artist.AlbumCount,
+			UserRating:     artist.Rating,
+			CoverArt:       artist.CoverArtID().String(),
+			ArtistImageUrl: artistCoverArtURL(r, artist.CoverArtID(), 0),
 		}
 		if artist.Starred {
 			searchResult2.Artist[i].Starred = &as[i].StarredAt
@@ -134,7 +136,7 @@ func (api *Router) Search3(r *http.Request) (*responses.Subsonic, error) {
 	searchResult3 := &responses.SearchResult3{}
 	searchResult3.Artist = make([]responses.ArtistID3, len(as))
 	for i, artist := range as {
-		searchResult3.Artist[i] = toArtistID3(ctx, artist)
+		searchResult3.Artist[i] = toArtistID3(r, artist)
 	}
 	searchResult3.Album = childrenFromAlbums(ctx, als)
 	searchResult3.Song = childrenFromMediaFiles(ctx, mfs)