Completely removed engine package, fewer abstraction layers \o/

This commit is contained in:
Deluan 2020-10-27 13:52:01 -04:00
parent d0bf37a8a9
commit 0f418a93cd
12 changed files with 137 additions and 378 deletions

View file

@ -13,7 +13,6 @@ import (
"github.com/deluan/navidrome/server" "github.com/deluan/navidrome/server"
"github.com/deluan/navidrome/server/app" "github.com/deluan/navidrome/server/app"
"github.com/deluan/navidrome/server/subsonic" "github.com/deluan/navidrome/server/subsonic"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/google/wire" "github.com/google/wire"
) )
@ -44,7 +43,6 @@ func CreateSubsonicAPIRouter() *subsonic.Router {
dataStore := persistence.New() dataStore := persistence.New()
artworkCache := core.NewImageCache() artworkCache := core.NewImageCache()
artwork := core.NewArtwork(dataStore, artworkCache) artwork := core.NewArtwork(dataStore, artworkCache)
playlists := engine.NewPlaylists(dataStore)
transcoderTranscoder := transcoder.New() transcoderTranscoder := transcoder.New()
transcodingCache := core.NewTranscodingCache() transcodingCache := core.NewTranscodingCache()
mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache) mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache)
@ -53,10 +51,10 @@ func CreateSubsonicAPIRouter() *subsonic.Router {
client := core.LastFMNewClient() client := core.LastFMNewClient()
spotifyClient := core.SpotifyNewClient() spotifyClient := core.SpotifyNewClient()
externalInfo := core.NewExternalInfo(dataStore, client, spotifyClient) externalInfo := core.NewExternalInfo(dataStore, client, spotifyClient)
router := subsonic.New(artwork, playlists, mediaStreamer, archiver, players, externalInfo, dataStore) router := subsonic.New(artwork, mediaStreamer, archiver, players, externalInfo, dataStore)
return router return router
} }
// wire_injectors.go: // wire_injectors.go:
var allProviders = wire.NewSet(engine.Set, core.Set, scanner.New, subsonic.New, app.New, persistence.New) var allProviders = wire.NewSet(core.Set, scanner.New, subsonic.New, app.New, persistence.New)

View file

@ -9,12 +9,10 @@ import (
"github.com/deluan/navidrome/server" "github.com/deluan/navidrome/server"
"github.com/deluan/navidrome/server/app" "github.com/deluan/navidrome/server/app"
"github.com/deluan/navidrome/server/subsonic" "github.com/deluan/navidrome/server/subsonic"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/google/wire" "github.com/google/wire"
) )
var allProviders = wire.NewSet( var allProviders = wire.NewSet(
engine.Set,
core.Set, core.Set,
scanner.New, scanner.New,
subsonic.New, subsonic.New,

View file

@ -19,6 +19,7 @@ var Set = wire.NewSet(
NewNowPlayingRepository, NewNowPlayingRepository,
NewExternalInfo, NewExternalInfo,
NewCacheWarmer, NewCacheWarmer,
NewPlayers,
LastFMNewClient, LastFMNewClient,
SpotifyNewClient, SpotifyNewClient,
transcoder.New, transcoder.New,

3
go.sum
View file

@ -311,8 +311,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=

View file

@ -11,7 +11,6 @@ import (
"github.com/deluan/navidrome/core" "github.com/deluan/navidrome/core"
"github.com/deluan/navidrome/log" "github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model" "github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/server/subsonic/responses"
"github.com/deluan/navidrome/utils" "github.com/deluan/navidrome/utils"
"github.com/go-chi/chi" "github.com/go-chi/chi"
@ -24,7 +23,6 @@ type Handler = func(http.ResponseWriter, *http.Request) (*responses.Subsonic, er
type Router struct { type Router struct {
Artwork core.Artwork Artwork core.Artwork
Playlists engine.Playlists
Streamer core.MediaStreamer Streamer core.MediaStreamer
Archiver core.Archiver Archiver core.Archiver
Players core.Players Players core.Players
@ -34,11 +32,16 @@ type Router struct {
mux http.Handler mux http.Handler
} }
func New(artwork core.Artwork, func New(artwork core.Artwork, streamer core.MediaStreamer, archiver core.Archiver, players core.Players,
playlists engine.Playlists, streamer core.MediaStreamer, externalInfo core.ExternalInfo, ds model.DataStore) *Router {
archiver core.Archiver, players core.Players, externalInfo core.ExternalInfo, ds model.DataStore) *Router { r := &Router{
r := &Router{Artwork: artwork, Playlists: playlists, Artwork: artwork,
Streamer: streamer, Archiver: archiver, Players: players, ExternalInfo: externalInfo, DataStore: ds} Streamer: streamer,
Archiver: archiver,
Players: players,
ExternalInfo: externalInfo,
DataStore: ds,
}
r.mux = r.routes() r.mux = r.routes()
return r return r
} }
@ -181,7 +184,7 @@ func HGone(r chi.Router, path string) {
func SendError(w http.ResponseWriter, r *http.Request, err error) { func SendError(w http.ResponseWriter, r *http.Request, err error) {
response := newResponse() response := newResponse()
code := responses.ErrorGeneric code := responses.ErrorGeneric
if e, ok := err.(SubsonicError); ok { if e, ok := err.(Error); ok {
code = e.code code = e.code
} }
response.Status = "fail" response.Status = "fail"

View file

@ -1,106 +0,0 @@
package engine
import (
"fmt"
"time"
"github.com/deluan/navidrome/consts"
"github.com/deluan/navidrome/model"
)
type Entry struct {
Id string
Title string
IsDir bool
Parent string
Album string
Year int
Artist string
Genre string
CoverArt string
Starred time.Time
Track int
Duration int
Size int64
Suffix string
BitRate int
ContentType string
Path string
PlayCount int32
DiscNumber int
Created time.Time
AlbumId string
ArtistId string
Type string
UserRating int
SongCount int
UserName string
MinutesAgo int
PlayerId int
PlayerName string
AlbumCount int
BookmarkPosition int64
AbsolutePath string
}
type Entries []Entry
func FromMediaFile(mf *model.MediaFile) Entry {
e := Entry{}
e.Id = mf.ID
e.Title = mf.Title
e.IsDir = false
e.Parent = mf.AlbumID
e.Album = mf.Album
e.Year = mf.Year
e.Artist = mf.Artist
e.Genre = mf.Genre
e.Track = mf.TrackNumber
e.Duration = int(mf.Duration)
e.Size = mf.Size
e.Suffix = mf.Suffix
e.BitRate = mf.BitRate
if mf.HasCoverArt {
e.CoverArt = mf.ID
} else {
e.CoverArt = "al-" + mf.AlbumID
}
e.ContentType = mf.ContentType()
e.AbsolutePath = mf.Path
// Creates a "pseudo" Path, to avoid sending absolute paths to the client
if mf.Path != "" {
e.Path = fmt.Sprintf("%s/%s/%s.%s", realArtistName(mf), mf.Album, mf.Title, mf.Suffix)
}
e.DiscNumber = mf.DiscNumber
e.Created = mf.CreatedAt
e.AlbumId = mf.AlbumID
e.ArtistId = mf.ArtistID
e.Type = "music"
e.PlayCount = int32(mf.PlayCount)
if mf.Starred {
e.Starred = mf.StarredAt
}
e.UserRating = mf.Rating
e.BookmarkPosition = mf.BookmarkPosition
return e
}
func realArtistName(mf *model.MediaFile) string {
switch {
case mf.Compilation:
return consts.VariousArtists
case mf.AlbumArtist != "":
return mf.AlbumArtist
}
return mf.Artist
}
func FromMediaFiles(mfs model.MediaFiles) Entries {
entries := make(Entries, len(mfs))
for i := range mfs {
mf := mfs[i]
entries[i] = FromMediaFile(&mf)
}
return entries
}

View file

@ -1,151 +0,0 @@
package engine
import (
"context"
"time"
"github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/model/request"
"github.com/deluan/navidrome/utils"
)
type Playlists interface {
GetAll(ctx context.Context) (model.Playlists, error)
Get(ctx context.Context, id string) (*PlaylistInfo, error)
Create(ctx context.Context, playlistId, name string, ids []string) error
Delete(ctx context.Context, playlistId string) error
Update(ctx context.Context, playlistId string, name *string, idsToAdd []string, idxToRemove []int) error
}
func NewPlaylists(ds model.DataStore) Playlists {
return &playlists{ds}
}
type playlists struct {
ds model.DataStore
}
func (p *playlists) Create(ctx context.Context, playlistId, name string, ids []string) error {
return p.ds.WithTx(func(tx model.DataStore) error {
owner := p.getUser(ctx)
var pls *model.Playlist
var err error
// If playlistID is present, override tracks
if playlistId != "" {
pls, err = tx.Playlist(ctx).Get(playlistId)
if err != nil {
return err
}
if owner != pls.Owner {
return model.ErrNotAuthorized
}
pls.Tracks = nil
} else {
pls = &model.Playlist{
Name: name,
Owner: owner,
}
}
for _, id := range ids {
pls.Tracks = append(pls.Tracks, model.MediaFile{ID: id})
}
return tx.Playlist(ctx).Put(pls)
})
}
func (p *playlists) getUser(ctx context.Context) string {
user, ok := request.UserFrom(ctx)
if ok {
return user.UserName
}
return ""
}
func (p *playlists) Delete(ctx context.Context, playlistId string) error {
return p.ds.WithTx(func(tx model.DataStore) error {
pls, err := tx.Playlist(ctx).Get(playlistId)
if err != nil {
return err
}
owner := p.getUser(ctx)
if owner != pls.Owner {
return model.ErrNotAuthorized
}
return tx.Playlist(ctx).Delete(playlistId)
})
}
func (p *playlists) Update(ctx context.Context, playlistId string, name *string, idsToAdd []string, idxToRemove []int) error {
return p.ds.WithTx(func(tx model.DataStore) error {
pls, err := tx.Playlist(ctx).Get(playlistId)
if err != nil {
return err
}
owner := p.getUser(ctx)
if owner != pls.Owner {
return model.ErrNotAuthorized
}
if name != nil {
pls.Name = *name
}
newTracks := model.MediaFiles{}
for i, t := range pls.Tracks {
if utils.IntInSlice(i, idxToRemove) {
continue
}
newTracks = append(newTracks, t)
}
for _, id := range idsToAdd {
newTracks = append(newTracks, model.MediaFile{ID: id})
}
pls.Tracks = newTracks
return tx.Playlist(ctx).Put(pls)
})
}
func (p *playlists) GetAll(ctx context.Context) (model.Playlists, error) {
return p.ds.Playlist(ctx).GetAll()
}
type PlaylistInfo struct {
Id string
Name string
Entries Entries
SongCount int
Duration int
Public bool
Owner string
Comment string
Created time.Time
Changed time.Time
}
func (p *playlists) Get(ctx context.Context, id string) (*PlaylistInfo, error) {
pl, err := p.ds.Playlist(ctx).Get(id)
if err != nil {
return nil, err
}
// TODO Use model.Playlist when got rid of Entries
plsInfo := &PlaylistInfo{
Id: pl.ID,
Name: pl.Name,
SongCount: pl.SongCount,
Duration: int(pl.Duration),
Public: pl.Public,
Owner: pl.Owner,
Comment: pl.Comment,
Changed: pl.UpdatedAt,
Created: pl.CreatedAt,
}
plsInfo.Entries = FromMediaFiles(pl.Tracks)
return plsInfo, nil
}

View file

@ -1,11 +0,0 @@
package engine
import (
"github.com/deluan/navidrome/core"
"github.com/google/wire"
)
var Set = wire.NewSet(
NewPlaylists,
core.NewPlayers,
)

View file

@ -9,7 +9,6 @@ import (
"github.com/deluan/navidrome/consts" "github.com/deluan/navidrome/consts"
"github.com/deluan/navidrome/model" "github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/model/request" "github.com/deluan/navidrome/model/request"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/server/subsonic/responses"
"github.com/deluan/navidrome/utils" "github.com/deluan/navidrome/utils"
) )
@ -42,19 +41,19 @@ func requiredParamInt(r *http.Request, param string, msg string) (int, error) {
return utils.ParamInt(r, param, 0), nil return utils.ParamInt(r, param, 0), nil
} }
type SubsonicError struct { type Error struct {
code int code int
messages []interface{} messages []interface{}
} }
func newError(code int, message ...interface{}) error { func newError(code int, message ...interface{}) error {
return SubsonicError{ return Error{
code: code, code: code,
messages: message, messages: message,
} }
} }
func (e SubsonicError) Error() string { func (e Error) Error() string {
var msg string var msg string
if len(e.messages) == 0 { if len(e.messages) == 0 {
msg = responses.ErrorMsg(e.code) msg = responses.ErrorMsg(e.code)
@ -64,6 +63,14 @@ func (e SubsonicError) Error() string {
return msg return msg
} }
func getUser(ctx context.Context) string {
user, ok := request.UserFrom(ctx)
if ok {
return user.UserName
}
return ""
}
func toArtists(ctx context.Context, artists model.Artists) []responses.Artist { func toArtists(ctx context.Context, artists model.Artists) []responses.Artist {
as := make([]responses.Artist, len(artists)) as := make([]responses.Artist, len(artists))
for i, artist := range artists { for i, artist := range artists {
@ -80,55 +87,6 @@ func toArtists(ctx context.Context, artists model.Artists) []responses.Artist {
return as return as
} }
func toChildren(ctx context.Context, entries engine.Entries) []responses.Child {
children := make([]responses.Child, len(entries))
for i, entry := range entries {
children[i] = toChild(ctx, entry)
}
return children
}
func toChild(ctx context.Context, entry engine.Entry) responses.Child {
child := responses.Child{}
child.Id = entry.Id
child.Title = entry.Title
child.IsDir = entry.IsDir
child.Parent = entry.Parent
child.Album = entry.Album
child.Year = entry.Year
child.Artist = entry.Artist
child.Genre = entry.Genre
child.CoverArt = entry.CoverArt
child.Track = entry.Track
child.Duration = entry.Duration
child.Size = entry.Size
child.Suffix = entry.Suffix
child.BitRate = entry.BitRate
child.ContentType = entry.ContentType
if !entry.Starred.IsZero() {
child.Starred = &entry.Starred
}
child.Path = entry.Path
child.PlayCount = int64(entry.PlayCount)
child.DiscNumber = entry.DiscNumber
if !entry.Created.IsZero() {
child.Created = &entry.Created
}
child.AlbumId = entry.AlbumId
child.ArtistId = entry.ArtistId
child.Type = entry.Type
child.IsVideo = false
child.UserRating = entry.UserRating
child.SongCount = entry.SongCount
format, _ := getTranscoding(ctx)
if entry.Suffix != "" && format != "" && entry.Suffix != format {
child.TranscodedSuffix = format
child.TranscodedContentType = mime.TypeByExtension("." + format)
}
child.BookmarkPosition = entry.BookmarkPosition
return child
}
func toGenres(genres model.Genres) *responses.Genres { func toGenres(genres model.Genres) *responses.Genres {
response := make([]responses.Genre, len(genres)) response := make([]responses.Genre, len(genres))
for i, g := range genres { for i, g := range genres {

View file

@ -8,36 +8,28 @@ import (
"github.com/deluan/navidrome/log" "github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model" "github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/server/subsonic/responses"
"github.com/deluan/navidrome/utils" "github.com/deluan/navidrome/utils"
) )
type PlaylistsController struct { type PlaylistsController struct {
pls engine.Playlists ds model.DataStore
} }
func NewPlaylistsController(pls engine.Playlists) *PlaylistsController { func NewPlaylistsController(ds model.DataStore) *PlaylistsController {
return &PlaylistsController{pls: pls} return &PlaylistsController{ds: ds}
} }
func (c *PlaylistsController) GetPlaylists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *PlaylistsController) GetPlaylists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
allPls, err := c.pls.GetAll(r.Context()) ctx := r.Context()
allPls, err := c.ds.Playlist(ctx).GetAll()
if err != nil { if err != nil {
log.Error(r, err) log.Error(r, err)
return nil, newError(responses.ErrorGeneric, "Internal error") return nil, newError(responses.ErrorGeneric, "Internal error")
} }
playlists := make([]responses.Playlist, len(allPls)) playlists := make([]responses.Playlist, len(allPls))
for i, p := range allPls { for i, p := range allPls {
playlists[i].Id = p.ID playlists[i] = *c.buildPlaylist(p)
playlists[i].Name = p.Name
playlists[i].Comment = p.Comment
playlists[i].SongCount = p.SongCount
playlists[i].Duration = int(p.Duration)
playlists[i].Owner = p.Owner
playlists[i].Public = p.Public
playlists[i].Created = p.CreatedAt
playlists[i].Changed = p.UpdatedAt
} }
response := newResponse() response := newResponse()
response.Playlists = &responses.Playlists{Playlist: playlists} response.Playlists = &responses.Playlists{Playlist: playlists}
@ -45,11 +37,12 @@ func (c *PlaylistsController) GetPlaylists(w http.ResponseWriter, r *http.Reques
} }
func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
id, err := requiredParamString(r, "id", "id parameter required") id, err := requiredParamString(r, "id", "id parameter required")
if err != nil { if err != nil {
return nil, err return nil, err
} }
pinfo, err := c.pls.Get(r.Context(), id) pls, err := c.ds.Playlist(ctx).Get(id)
switch { switch {
case err == model.ErrNotFound: case err == model.ErrNotFound:
log.Error(r, err.Error(), "id", id) log.Error(r, err.Error(), "id", id)
@ -60,18 +53,48 @@ func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request
} }
response := newResponse() response := newResponse()
response.Playlist = c.buildPlaylistWithSongs(r.Context(), pinfo) response.Playlist = c.buildPlaylistWithSongs(ctx, pls)
return response, nil return response, nil
} }
func (c *PlaylistsController) create(ctx context.Context, playlistId, name string, ids []string) error {
return c.ds.WithTx(func(tx model.DataStore) error {
owner := getUser(ctx)
var pls *model.Playlist
var err error
// If playlistID is present, override tracks
if playlistId != "" {
pls, err = tx.Playlist(ctx).Get(playlistId)
if err != nil {
return err
}
if owner != pls.Owner {
return model.ErrNotAuthorized
}
pls.Tracks = nil
} else {
pls = &model.Playlist{
Name: name,
Owner: owner,
}
}
for _, id := range ids {
pls.Tracks = append(pls.Tracks, model.MediaFile{ID: id})
}
return tx.Playlist(ctx).Put(pls)
})
}
func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
songIds := utils.ParamStrings(r, "songId") songIds := utils.ParamStrings(r, "songId")
playlistId := utils.ParamString(r, "playlistId") playlistId := utils.ParamString(r, "playlistId")
name := utils.ParamString(r, "name") name := utils.ParamString(r, "name")
if playlistId == "" && name == "" { if playlistId == "" && name == "" {
return nil, errors.New("Required parameter name is missing") return nil, errors.New("required parameter name is missing")
} }
err := c.pls.Create(r.Context(), playlistId, name, songIds) err := c.create(r.Context(), playlistId, name, songIds)
if err != nil { if err != nil {
log.Error(r, err) log.Error(r, err)
return nil, newError(responses.ErrorGeneric, "Internal Error") return nil, newError(responses.ErrorGeneric, "Internal Error")
@ -79,12 +102,27 @@ func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Requ
return newResponse(), nil return newResponse(), nil
} }
func (c *PlaylistsController) delete(ctx context.Context, playlistId string) error {
return c.ds.WithTx(func(tx model.DataStore) error {
pls, err := tx.Playlist(ctx).Get(playlistId)
if err != nil {
return err
}
owner := getUser(ctx)
if owner != pls.Owner {
return model.ErrNotAuthorized
}
return tx.Playlist(ctx).Delete(playlistId)
})
}
func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
id, err := requiredParamString(r, "id", "Required parameter id is missing") id, err := requiredParamString(r, "id", "Required parameter id is missing")
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = c.pls.Delete(r.Context(), id) err = c.delete(r.Context(), id)
if err == model.ErrNotAuthorized { if err == model.ErrNotAuthorized {
return nil, newError(responses.ErrorAuthorizationFail) return nil, newError(responses.ErrorAuthorizationFail)
} }
@ -95,6 +133,38 @@ func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Requ
return newResponse(), nil return newResponse(), nil
} }
func (p *PlaylistsController) update(ctx context.Context, playlistId string, name *string, idsToAdd []string, idxToRemove []int) error {
return p.ds.WithTx(func(tx model.DataStore) error {
pls, err := tx.Playlist(ctx).Get(playlistId)
if err != nil {
return err
}
owner := getUser(ctx)
if owner != pls.Owner {
return model.ErrNotAuthorized
}
if name != nil {
pls.Name = *name
}
newTracks := model.MediaFiles{}
for i, t := range pls.Tracks {
if utils.IntInSlice(i, idxToRemove) {
continue
}
newTracks = append(newTracks, t)
}
for _, id := range idsToAdd {
newTracks = append(newTracks, model.MediaFile{ID: id})
}
pls.Tracks = newTracks
return tx.Playlist(ctx).Put(pls)
})
}
func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
playlistId, err := requiredParamString(r, "playlistId", "Required parameter playlistId is missing") playlistId, err := requiredParamString(r, "playlistId", "Required parameter playlistId is missing")
if err != nil { if err != nil {
@ -103,20 +173,20 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ
songsToAdd := utils.ParamStrings(r, "songIdToAdd") songsToAdd := utils.ParamStrings(r, "songIdToAdd")
songIndexesToRemove := utils.ParamInts(r, "songIndexToRemove") songIndexesToRemove := utils.ParamInts(r, "songIndexToRemove")
var pname *string var plsName *string
if len(r.URL.Query()["name"]) > 0 { if len(r.URL.Query()["name"]) > 0 {
s := r.URL.Query()["name"][0] s := r.URL.Query()["name"][0]
pname = &s plsName = &s
} }
log.Debug(r, "Updating playlist", "id", playlistId) log.Debug(r, "Updating playlist", "id", playlistId)
if pname != nil { if plsName != nil {
log.Trace(r, fmt.Sprintf("-- New Name: '%s'", *pname)) log.Trace(r, fmt.Sprintf("-- New Name: '%s'", *plsName))
} }
log.Trace(r, fmt.Sprintf("-- Adding: '%v'", songsToAdd)) log.Trace(r, fmt.Sprintf("-- Adding: '%v'", songsToAdd))
log.Trace(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove)) log.Trace(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove))
err = c.pls.Update(r.Context(), playlistId, pname, songsToAdd, songIndexesToRemove) err = c.update(r.Context(), playlistId, plsName, songsToAdd, songIndexesToRemove)
if err == model.ErrNotAuthorized { if err == model.ErrNotAuthorized {
return nil, newError(responses.ErrorAuthorizationFail) return nil, newError(responses.ErrorAuthorizationFail)
} }
@ -127,24 +197,24 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ
return newResponse(), nil return newResponse(), nil
} }
func (c *PlaylistsController) buildPlaylistWithSongs(ctx context.Context, d *engine.PlaylistInfo) *responses.PlaylistWithSongs { func (c *PlaylistsController) buildPlaylistWithSongs(ctx context.Context, p *model.Playlist) *responses.PlaylistWithSongs {
pls := &responses.PlaylistWithSongs{ pls := &responses.PlaylistWithSongs{
Playlist: *c.buildPlaylist(d), Playlist: *c.buildPlaylist(*p),
} }
pls.Entry = toChildren(ctx, d.Entries) pls.Entry = childrenFromMediaFiles(ctx, p.Tracks)
return pls return pls
} }
func (c *PlaylistsController) buildPlaylist(d *engine.PlaylistInfo) *responses.Playlist { func (c *PlaylistsController) buildPlaylist(p model.Playlist) *responses.Playlist {
pls := &responses.Playlist{} pls := &responses.Playlist{}
pls.Id = d.Id pls.Id = p.ID
pls.Name = d.Name pls.Name = p.Name
pls.Comment = d.Comment pls.Comment = p.Comment
pls.SongCount = d.SongCount pls.SongCount = p.SongCount
pls.Owner = d.Owner pls.Owner = p.Owner
pls.Duration = d.Duration pls.Duration = int(p.Duration)
pls.Public = d.Public pls.Public = p.Public
pls.Created = d.Created pls.Created = p.CreatedAt
pls.Changed = d.Changed pls.Changed = p.UpdatedAt
return pls return pls
} }

View file

@ -39,8 +39,8 @@ func initMediaAnnotationController(router *Router) *MediaAnnotationController {
} }
func initPlaylistsController(router *Router) *PlaylistsController { func initPlaylistsController(router *Router) *PlaylistsController {
playlists := router.Playlists dataStore := router.DataStore
playlistsController := NewPlaylistsController(playlists) playlistsController := NewPlaylistsController(dataStore)
return playlistsController return playlistsController
} }
@ -87,5 +87,5 @@ var allProviders = wire.NewSet(
NewUsersController, NewUsersController,
NewMediaRetrievalController, NewMediaRetrievalController,
NewStreamController, NewStreamController,
NewBookmarksController, core.NewNowPlayingRepository, wire.FieldsOf(new(*Router), "Artwork", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"), NewBookmarksController, core.NewNowPlayingRepository, wire.FieldsOf(new(*Router), "Artwork", "Streamer", "Archiver", "DataStore", "ExternalInfo"),
) )

View file

@ -19,7 +19,7 @@ var allProviders = wire.NewSet(
NewStreamController, NewStreamController,
NewBookmarksController, NewBookmarksController,
core.NewNowPlayingRepository, core.NewNowPlayingRepository,
wire.FieldsOf(new(*Router), "Artwork", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"), wire.FieldsOf(new(*Router), "Artwork", "Streamer", "Archiver", "DataStore", "ExternalInfo"),
) )
func initSystemController(router *Router) *SystemController { func initSystemController(router *Router) *SystemController {