mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-18 07:30:29 +00:00
Removing the artwork backfill left GetAllIDs with no caller outside the persistence package: each repository's own GetCursor is the only one, and radio's — which existed solely for that backfill — went with it. Off the model interfaces and unexported, which also drops four mock implementations that existed only to satisfy them.
194 lines
5.8 KiB
Go
194 lines
5.8 KiB
Go
package model
|
|
|
|
import (
|
|
"iter"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/consts"
|
|
"github.com/navidrome/navidrome/model/criteria"
|
|
)
|
|
|
|
type Playlist struct {
|
|
Annotations `structs:"-"`
|
|
ItemImage `structs:"-"`
|
|
|
|
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"`
|
|
ImportedHash string `structs:"imported_hash" json:"-"`
|
|
|
|
// SmartPlaylist attributes
|
|
Rules *criteria.Criteria `structs:"rules" json:"rules"`
|
|
EvaluatedAt *time.Time `structs:"evaluated_at" json:"evaluatedAt"`
|
|
}
|
|
|
|
func (pls Playlist) IsSmartPlaylist() bool {
|
|
return pls.Rules != nil && pls.Rules.Expression != nil
|
|
}
|
|
|
|
// TracksEditable reports whether the track list is user-owned rather than server-managed.
|
|
func (pls Playlist) TracksEditable() bool {
|
|
return !pls.IsSmartPlaylist() && !pls.Sync
|
|
}
|
|
|
|
// RefreshDelay returns the playlist's own refresh window when set, falling
|
|
// back to the global SmartPlaylistRefreshDelay.
|
|
func (pls Playlist) RefreshDelay() time.Duration {
|
|
if pls.IsSmartPlaylist() && pls.Rules.RefreshDelay > 0 {
|
|
return pls.Rules.RefreshDelay
|
|
}
|
|
return conf.Server.SmartPlaylistRefreshDelay
|
|
}
|
|
|
|
func (pls Playlist) MediaFiles() MediaFiles {
|
|
if len(pls.Tracks) == 0 {
|
|
return nil
|
|
}
|
|
return pls.Tracks.MediaFiles()
|
|
}
|
|
|
|
func (pls *Playlist) refreshStats() {
|
|
pls.SongCount = len(pls.Tracks)
|
|
pls.Duration = 0
|
|
pls.Size = 0
|
|
for _, t := range pls.Tracks {
|
|
pls.Duration += t.MediaFile.Duration
|
|
pls.Size += t.MediaFile.Size
|
|
}
|
|
}
|
|
|
|
func (pls *Playlist) SetTracks(tracks PlaylistTracks) {
|
|
pls.Tracks = tracks
|
|
pls.refreshStats()
|
|
}
|
|
|
|
func (pls *Playlist) RemoveTracks(idxToRemove []int) {
|
|
var newTracks PlaylistTracks
|
|
for i, t := range pls.Tracks {
|
|
if slices.Contains(idxToRemove, i) {
|
|
continue
|
|
}
|
|
newTracks = append(newTracks, t)
|
|
}
|
|
pls.Tracks = newTracks
|
|
pls.refreshStats()
|
|
}
|
|
|
|
// ToM3U8 exports the playlist to the Extended M3U8 format
|
|
func (pls *Playlist) ToM3U8() string {
|
|
return pls.MediaFiles().ToM3U8(pls.Name, true)
|
|
}
|
|
|
|
func (pls *Playlist) AddMediaFilesByID(mediaFileIds []string) {
|
|
pos := len(pls.Tracks)
|
|
for _, mfId := range mediaFileIds {
|
|
pos++
|
|
t := PlaylistTrack{
|
|
ID: strconv.Itoa(pos),
|
|
MediaFileID: mfId,
|
|
MediaFile: MediaFile{ID: mfId},
|
|
PlaylistID: pls.ID,
|
|
}
|
|
pls.Tracks = append(pls.Tracks, t)
|
|
}
|
|
pls.refreshStats()
|
|
}
|
|
|
|
func (pls *Playlist) AddMediaFiles(mfs MediaFiles) {
|
|
pos := len(pls.Tracks)
|
|
for _, mf := range mfs {
|
|
pos++
|
|
t := PlaylistTrack{
|
|
ID: strconv.Itoa(pos),
|
|
MediaFileID: mf.ID,
|
|
MediaFile: mf,
|
|
PlaylistID: pls.ID,
|
|
}
|
|
pls.Tracks = append(pls.Tracks, t)
|
|
}
|
|
pls.refreshStats()
|
|
}
|
|
|
|
func (pls Playlist) CoverArtID() ArtworkID {
|
|
return artworkIDFromPlaylist(pls)
|
|
}
|
|
|
|
// 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 {
|
|
return UploadedImagePath(consts.EntityPlaylist, pls.UploadedImage)
|
|
}
|
|
|
|
type Playlists []Playlist
|
|
|
|
type PlaylistCursor iter.Seq2[Playlist, error]
|
|
|
|
type PlaylistRepository interface {
|
|
ResourceRepository
|
|
AnnotatedRepository
|
|
CountAll(options ...QueryOptions) (int64, error)
|
|
Exists(id string) (bool, error)
|
|
Put(pls *Playlist, cols ...string) error
|
|
Get(id string) (*Playlist, error)
|
|
GetWithTracks(id string, refreshSmartPlaylist, includeMissing bool) (*Playlist, error)
|
|
GetAll(options ...QueryOptions) (Playlists, error)
|
|
GetCursor(options ...QueryOptions) (PlaylistCursor, error)
|
|
FindByPath(path string) (*Playlist, error)
|
|
Delete(id string) error
|
|
Tracks(playlistId string, refreshSmartPlaylist bool) PlaylistTrackRepository
|
|
GetPlaylists(mediaFileId string) (Playlists, error)
|
|
}
|
|
|
|
type PlaylistTrack struct {
|
|
ID string `json:"id"`
|
|
MediaFileID string `json:"mediaFileId"`
|
|
PlaylistID string `json:"playlistId"`
|
|
MediaFile
|
|
}
|
|
|
|
type PlaylistTracks []PlaylistTrack
|
|
|
|
func (plt PlaylistTracks) MediaFiles() MediaFiles {
|
|
mfs := make(MediaFiles, len(plt))
|
|
for i, t := range plt {
|
|
mfs[i] = t.MediaFile
|
|
}
|
|
return mfs
|
|
}
|
|
|
|
type PlaylistTrackCursor iter.Seq2[PlaylistTrack, error]
|
|
|
|
type PlaylistTrackRepository interface {
|
|
ResourceRepository
|
|
CountAll(options ...QueryOptions) (int64, error)
|
|
GetAll(options ...QueryOptions) (PlaylistTracks, error)
|
|
GetCursor(options ...QueryOptions) (PlaylistTrackCursor, error)
|
|
GetAlbumIDs(options ...QueryOptions) ([]string, error)
|
|
GetMediaFileIDs(options ...QueryOptions) ([]string, error)
|
|
Add(mediaFileIds []string) (int, error)
|
|
AddAlbums(albumIds []string) (int, error)
|
|
AddArtists(artistIds []string) (int, error)
|
|
AddDiscs(discs []DiscID) (int, error)
|
|
Delete(id ...string) error
|
|
DeleteAll() error
|
|
Reorder(pos int, newPos int) error
|
|
}
|