mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
refactor(playlist): rename ImageFile to UploadedImage and ArtworkPath to UploadedImagePath
Rename playlist model fields and methods for clarity in preparation for adding external image URL and sidecar image support. Add the new ExternalImageURL field to the Playlist model.
This commit is contained in:
parent
169d3b8d72
commit
beb5e85266
@ -45,15 +45,15 @@ func (a *playlistArtworkReader) LastUpdated() time.Time {
|
||||
|
||||
func (a *playlistArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
|
||||
return selectImageReader(ctx, a.artID,
|
||||
a.fromPlaylistImage(),
|
||||
a.fromPlaylistUploadedImage(),
|
||||
a.fromGeneratedTiledCover(ctx),
|
||||
fromAlbumPlaceholder(),
|
||||
)
|
||||
}
|
||||
|
||||
func (a *playlistArtworkReader) fromPlaylistImage() sourceFunc {
|
||||
func (a *playlistArtworkReader) fromPlaylistUploadedImage() sourceFunc {
|
||||
return func() (io.ReadCloser, string, error) {
|
||||
absPath := a.pl.ArtworkPath()
|
||||
absPath := a.pl.UploadedImagePath()
|
||||
if absPath == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ func (s *playlists) Delete(ctx context.Context, id string) error {
|
||||
}
|
||||
|
||||
// Clean up custom cover image file if one exists
|
||||
if path := pls.ArtworkPath(); path != "" {
|
||||
if path := pls.UploadedImagePath(); path != "" {
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
log.Warn(ctx, "Failed to remove playlist image on delete", "path", path, err)
|
||||
}
|
||||
@ -289,9 +289,9 @@ func (s *playlists) SetImage(ctx context.Context, playlistID string, reader io.R
|
||||
}
|
||||
|
||||
filename := playlistID + ext
|
||||
oldPath := pls.ArtworkPath()
|
||||
pls.ImageFile = filename
|
||||
absPath := pls.ArtworkPath()
|
||||
oldPath := pls.UploadedImagePath()
|
||||
pls.UploadedImage = filename
|
||||
absPath := pls.UploadedImagePath()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(absPath), 0755); err != nil {
|
||||
return fmt.Errorf("creating playlist images directory: %w", err)
|
||||
@ -324,12 +324,12 @@ func (s *playlists) RemoveImage(ctx context.Context, playlistID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if path := pls.ArtworkPath(); path != "" {
|
||||
if path := pls.UploadedImagePath(); path != "" {
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
log.Warn(ctx, "Failed to remove playlist image", "path", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
pls.ImageFile = ""
|
||||
pls.UploadedImage = ""
|
||||
return s.ds.Playlist(ctx).Put(pls)
|
||||
}
|
||||
|
||||
@ -315,13 +315,13 @@ var _ = Describe("Playlists", func() {
|
||||
ps = playlists.NewPlaylists(ds)
|
||||
})
|
||||
|
||||
It("saves image file and updates ImageFile", func() {
|
||||
It("saves image file and updates UploadedImage", func() {
|
||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||
reader := strings.NewReader("fake image data")
|
||||
err := ps.SetImage(ctx, "pls-1", reader, ".jpg")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(mockPlsRepo.Last.ImageFile).To(Equal("pls-1.jpg"))
|
||||
Expect(mockPlsRepo.Last.UploadedImage).To(Equal("pls-1.jpg"))
|
||||
absPath := filepath.Join(tmpDir, "artwork", "playlist", "pls-1.jpg")
|
||||
data, err := os.ReadFile(absPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -378,19 +378,19 @@ var _ = Describe("Playlists", func() {
|
||||
Expect(os.WriteFile(filepath.Join(imgDir, "pls-1.jpg"), []byte("img data"), 0600)).To(Succeed())
|
||||
|
||||
mockPlsRepo.Data = map[string]*model.Playlist{
|
||||
"pls-1": {ID: "pls-1", Name: "My Playlist", OwnerID: "user-1", ImageFile: "pls-1.jpg"},
|
||||
"pls-1": {ID: "pls-1", Name: "My Playlist", OwnerID: "user-1", UploadedImage: "pls-1.jpg"},
|
||||
"pls-empty": {ID: "pls-empty", Name: "No Cover", OwnerID: "user-1"},
|
||||
"pls-other": {ID: "pls-other", Name: "Other's", OwnerID: "other-user"},
|
||||
}
|
||||
ps = playlists.NewPlaylists(ds)
|
||||
})
|
||||
|
||||
It("removes file and clears ImageFile", func() {
|
||||
It("removes file and clears UploadedImage", func() {
|
||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||
err := ps.RemoveImage(ctx, "pls-1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(mockPlsRepo.Last.ImageFile).To(BeEmpty())
|
||||
Expect(mockPlsRepo.Last.UploadedImage).To(BeEmpty())
|
||||
absPath := filepath.Join(tmpDir, "artwork", "playlist", "pls-1.jpg")
|
||||
Expect(absPath).ToNot(BeAnExistingFile())
|
||||
})
|
||||
@ -399,7 +399,7 @@ var _ = Describe("Playlists", func() {
|
||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||
err := ps.RemoveImage(ctx, "pls-empty")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(mockPlsRepo.Last.ImageFile).To(BeEmpty())
|
||||
Expect(mockPlsRepo.Last.UploadedImage).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("denies non-owner", func() {
|
||||
|
||||
@ -12,21 +12,22 @@ import (
|
||||
)
|
||||
|
||||
type Playlist struct {
|
||||
ID string `structs:"id" json:"id"`
|
||||
Name string `structs:"name" json:"name"`
|
||||
Comment string `structs:"comment" json:"comment"`
|
||||
Duration float32 `structs:"duration" json:"duration"`
|
||||
Size int64 `structs:"size" json:"size"`
|
||||
SongCount int `structs:"song_count" json:"songCount"`
|
||||
OwnerName string `structs:"-" json:"ownerName"`
|
||||
OwnerID string `structs:"owner_id" json:"ownerId"`
|
||||
Public bool `structs:"public" json:"public"`
|
||||
Tracks PlaylistTracks `structs:"-" json:"tracks,omitempty"`
|
||||
Path string `structs:"path" json:"path"`
|
||||
Sync bool `structs:"sync" json:"sync"`
|
||||
ImageFile string `structs:"image_file" json:"imageFile"`
|
||||
CreatedAt time.Time `structs:"created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"`
|
||||
ID string `structs:"id" json:"id"`
|
||||
Name string `structs:"name" json:"name"`
|
||||
Comment string `structs:"comment" json:"comment"`
|
||||
Duration float32 `structs:"duration" json:"duration"`
|
||||
Size int64 `structs:"size" json:"size"`
|
||||
SongCount int `structs:"song_count" json:"songCount"`
|
||||
OwnerName string `structs:"-" json:"ownerName"`
|
||||
OwnerID string `structs:"owner_id" json:"ownerId"`
|
||||
Public bool `structs:"public" json:"public"`
|
||||
Tracks PlaylistTracks `structs:"-" json:"tracks,omitempty"`
|
||||
Path string `structs:"path" json:"path"`
|
||||
Sync bool `structs:"sync" json:"sync"`
|
||||
UploadedImage string `structs:"uploaded_image" json:"uploadedImage"`
|
||||
ExternalImageURL string `structs:"external_image_url" json:"externalImageUrl,omitempty"`
|
||||
CreatedAt time.Time `structs:"created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"`
|
||||
|
||||
// SmartPlaylist attributes
|
||||
Rules *criteria.Criteria `structs:"rules" json:"rules"`
|
||||
@ -110,11 +111,15 @@ func (pls Playlist) CoverArtID() ArtworkID {
|
||||
return artworkIDFromPlaylist(pls)
|
||||
}
|
||||
|
||||
func (pls Playlist) ArtworkPath() string {
|
||||
if pls.ImageFile == "" {
|
||||
// UploadedImagePath returns the absolute filesystem path for a manually uploaded
|
||||
// playlist cover image. Returns empty string if no image has been uploaded.
|
||||
// This does NOT cover sidecar images or external URLs — those are resolved
|
||||
// by the artwork reader's fallback chain.
|
||||
func (pls Playlist) UploadedImagePath() string {
|
||||
if pls.UploadedImage == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(conf.Server.DataFolder, consts.ArtworkFolder, "playlist", pls.ImageFile)
|
||||
return filepath.Join(conf.Server.DataFolder, consts.ArtworkFolder, "playlist", pls.UploadedImage)
|
||||
}
|
||||
|
||||
type Playlists []Playlist
|
||||
|
||||
@ -246,7 +246,7 @@ const PlaylistDetails = (props) => {
|
||||
<PhotoCameraIcon className={classes.overlayIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{record.imageFile && (
|
||||
{record.uploadedImage && (
|
||||
<Tooltip
|
||||
title={translate('resources.playlist.actions.removeCover')}
|
||||
>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user