feat: new "Subsonic Minimal Clients" configuration option (#4850)
* Add `.editorconfig` file Hints to users how to properly indent Go files (my setup was defaulting to 2 spaces). * Add Subsonic API minimal config option This will allow users to specify clients which can operate with or need the minimum required fields as per the [SubSonic API spec](https://subsonic.org/pages/api.jsp). * Return only required fields for Child Objects For a minimal client, only return the required fields for Child Objects. * Return only required fields for Playlist objects * refactor: simplify client list checks and improve playlist response handling Signed-off-by: Deluan <deluan@navidrome.org> * test: add unit tests for client list checks and playlist building logic Signed-off-by: Deluan <deluan@navidrome.org> * fix: revert Child.IsVideo and Playlist.Public fields from pointer to boolean, and add omitempty to XML tag Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org> Co-authored-by: Deluan Quintão <deluan@navidrome.org>
This commit is contained in:
@@ -153,6 +153,7 @@ type subsonicOptions struct {
|
|||||||
ArtistParticipations bool
|
ArtistParticipations bool
|
||||||
DefaultReportRealPath bool
|
DefaultReportRealPath bool
|
||||||
LegacyClients string
|
LegacyClients string
|
||||||
|
MinimalClients string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagConf struct {
|
type TagConf struct {
|
||||||
|
|||||||
@@ -166,11 +166,30 @@ func getTranscoding(ctx context.Context) (format string, bitRate int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isClientInList(clientList, client string) bool {
|
||||||
|
if clientList == "" || client == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
clients := strings.Split(clientList, ",")
|
||||||
|
for _, c := range clients {
|
||||||
|
if strings.TrimSpace(c) == client {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child {
|
func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child {
|
||||||
child := responses.Child{}
|
child := responses.Child{}
|
||||||
child.Id = mf.ID
|
child.Id = mf.ID
|
||||||
child.Title = mf.FullTitle()
|
child.Title = mf.FullTitle()
|
||||||
child.IsDir = false
|
child.IsDir = false
|
||||||
|
|
||||||
|
player, ok := request.PlayerFrom(ctx)
|
||||||
|
if ok && isClientInList(conf.Server.Subsonic.MinimalClients, player.Client) {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
child.Parent = mf.AlbumID
|
child.Parent = mf.AlbumID
|
||||||
child.Album = mf.Album
|
child.Album = mf.Album
|
||||||
child.Year = int32(mf.Year)
|
child.Year = int32(mf.Year)
|
||||||
@@ -183,7 +202,7 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child
|
|||||||
child.BitRate = int32(mf.BitRate)
|
child.BitRate = int32(mf.BitRate)
|
||||||
child.CoverArt = mf.CoverArtID().String()
|
child.CoverArt = mf.CoverArtID().String()
|
||||||
child.ContentType = mf.ContentType()
|
child.ContentType = mf.ContentType()
|
||||||
player, ok := request.PlayerFrom(ctx)
|
|
||||||
if ok && player.ReportRealPath {
|
if ok && player.ReportRealPath {
|
||||||
child.Path = mf.AbsolutePath()
|
child.Path = mf.AbsolutePath()
|
||||||
} else {
|
} else {
|
||||||
@@ -211,8 +230,8 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child
|
|||||||
}
|
}
|
||||||
|
|
||||||
func osChildFromMediaFile(ctx context.Context, mf model.MediaFile) *responses.OpenSubsonicChild {
|
func osChildFromMediaFile(ctx context.Context, mf model.MediaFile) *responses.OpenSubsonicChild {
|
||||||
player, _ := request.PlayerFrom(ctx)
|
player, ok := request.PlayerFrom(ctx)
|
||||||
if strings.Contains(conf.Server.Subsonic.LegacyClients, player.Client) {
|
if ok && isClientInList(conf.Server.Subsonic.MinimalClients, player.Client) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
child := responses.OpenSubsonicChild{}
|
child := responses.OpenSubsonicChild{}
|
||||||
|
|||||||
@@ -169,6 +169,190 @@ var _ = Describe("helpers", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
DescribeTable("isClientInList",
|
||||||
|
func(list, client string, expected bool) {
|
||||||
|
Expect(isClientInList(list, client)).To(Equal(expected))
|
||||||
|
},
|
||||||
|
Entry("returns false when clientList is empty", "", "some-client", false),
|
||||||
|
Entry("returns false when client is empty", "client1,client2", "", false),
|
||||||
|
Entry("returns false when both are empty", "", "", false),
|
||||||
|
Entry("returns true when client matches single entry", "my-client", "my-client", true),
|
||||||
|
Entry("returns true when client matches first in list", "client1,client2,client3", "client1", true),
|
||||||
|
Entry("returns true when client matches middle in list", "client1,client2,client3", "client2", true),
|
||||||
|
Entry("returns true when client matches last in list", "client1,client2,client3", "client3", true),
|
||||||
|
Entry("returns false when client does not match", "client1,client2", "client3", false),
|
||||||
|
Entry("trims whitespace from client list entries", "client1, client2 , client3", "client2", true),
|
||||||
|
Entry("does not trim the client parameter", "client1,client2", " client1", false),
|
||||||
|
)
|
||||||
|
|
||||||
|
Describe("childFromMediaFile", func() {
|
||||||
|
var mf model.MediaFile
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
mf = model.MediaFile{
|
||||||
|
ID: "mf-1",
|
||||||
|
Title: "Test Song",
|
||||||
|
Album: "Test Album",
|
||||||
|
AlbumID: "album-1",
|
||||||
|
Artist: "Test Artist",
|
||||||
|
ArtistID: "artist-1",
|
||||||
|
Year: 2023,
|
||||||
|
Genre: "Rock",
|
||||||
|
TrackNumber: 5,
|
||||||
|
Duration: 180.5,
|
||||||
|
Size: 5000000,
|
||||||
|
Suffix: "mp3",
|
||||||
|
BitRate: 320,
|
||||||
|
}
|
||||||
|
ctx = context.Background()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with minimal client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||||
|
player := model.Player{Client: "minimal-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns only basic fields", func() {
|
||||||
|
child := childFromMediaFile(ctx, mf)
|
||||||
|
Expect(child.Id).To(Equal("mf-1"))
|
||||||
|
Expect(child.Title).To(Equal("Test Song"))
|
||||||
|
Expect(child.IsDir).To(BeFalse())
|
||||||
|
|
||||||
|
// These should not be set
|
||||||
|
Expect(child.Album).To(BeEmpty())
|
||||||
|
Expect(child.Artist).To(BeEmpty())
|
||||||
|
Expect(child.Parent).To(BeEmpty())
|
||||||
|
Expect(child.Year).To(BeZero())
|
||||||
|
Expect(child.Genre).To(BeEmpty())
|
||||||
|
Expect(child.Track).To(BeZero())
|
||||||
|
Expect(child.Duration).To(BeZero())
|
||||||
|
Expect(child.Size).To(BeZero())
|
||||||
|
Expect(child.Suffix).To(BeEmpty())
|
||||||
|
Expect(child.BitRate).To(BeZero())
|
||||||
|
Expect(child.CoverArt).To(BeEmpty())
|
||||||
|
Expect(child.ContentType).To(BeEmpty())
|
||||||
|
Expect(child.Path).To(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("does not include OpenSubsonic extension", func() {
|
||||||
|
child := childFromMediaFile(ctx, mf)
|
||||||
|
Expect(child.OpenSubsonicChild).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with non-minimal client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||||
|
player := model.Player{Client: "regular-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns all fields", func() {
|
||||||
|
child := childFromMediaFile(ctx, mf)
|
||||||
|
Expect(child.Id).To(Equal("mf-1"))
|
||||||
|
Expect(child.Title).To(Equal("Test Song"))
|
||||||
|
Expect(child.IsDir).To(BeFalse())
|
||||||
|
Expect(child.Album).To(Equal("Test Album"))
|
||||||
|
Expect(child.Artist).To(Equal("Test Artist"))
|
||||||
|
Expect(child.Parent).To(Equal("album-1"))
|
||||||
|
Expect(child.Year).To(Equal(int32(2023)))
|
||||||
|
Expect(child.Genre).To(Equal("Rock"))
|
||||||
|
Expect(child.Track).To(Equal(int32(5)))
|
||||||
|
Expect(child.Duration).To(Equal(int32(180)))
|
||||||
|
Expect(child.Size).To(Equal(int64(5000000)))
|
||||||
|
Expect(child.Suffix).To(Equal("mp3"))
|
||||||
|
Expect(child.BitRate).To(Equal(int32(320)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when minimal clients list is empty", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = ""
|
||||||
|
player := model.Player{Client: "any-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns all fields", func() {
|
||||||
|
child := childFromMediaFile(ctx, mf)
|
||||||
|
Expect(child.Album).To(Equal("Test Album"))
|
||||||
|
Expect(child.Artist).To(Equal("Test Artist"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when no player in context", func() {
|
||||||
|
It("returns all fields", func() {
|
||||||
|
child := childFromMediaFile(ctx, mf)
|
||||||
|
Expect(child.Album).To(Equal("Test Album"))
|
||||||
|
Expect(child.Artist).To(Equal("Test Artist"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("osChildFromMediaFile", func() {
|
||||||
|
var mf model.MediaFile
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
mf = model.MediaFile{
|
||||||
|
ID: "mf-1",
|
||||||
|
Title: "Test Song",
|
||||||
|
Artist: "Test Artist",
|
||||||
|
Comment: "Test Comment",
|
||||||
|
}
|
||||||
|
ctx = context.Background()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with minimal client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||||
|
player := model.Player{Client: "minimal-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns nil", func() {
|
||||||
|
osChild := osChildFromMediaFile(ctx, mf)
|
||||||
|
Expect(osChild).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with non-minimal client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||||
|
player := model.Player{Client: "regular-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns OpenSubsonic child fields", func() {
|
||||||
|
osChild := osChildFromMediaFile(ctx, mf)
|
||||||
|
Expect(osChild).ToNot(BeNil())
|
||||||
|
Expect(osChild.Comment).To(Equal("Test Comment"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when minimal clients list is empty", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = ""
|
||||||
|
player := model.Player{Client: "any-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns OpenSubsonic child fields", func() {
|
||||||
|
osChild := osChildFromMediaFile(ctx, mf)
|
||||||
|
Expect(osChild).ToNot(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when no player in context", func() {
|
||||||
|
It("returns OpenSubsonic child fields", func() {
|
||||||
|
osChild := osChildFromMediaFile(ctx, mf)
|
||||||
|
Expect(osChild).ToNot(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Describe("selectedMusicFolderIds", func() {
|
Describe("selectedMusicFolderIds", func() {
|
||||||
var user model.User
|
var user model.User
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
"github.com/navidrome/navidrome/utils/req"
|
"github.com/navidrome/navidrome/utils/req"
|
||||||
"github.com/navidrome/navidrome/utils/slice"
|
"github.com/navidrome/navidrome/utils/slice"
|
||||||
@@ -23,7 +25,7 @@ func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) {
|
|||||||
}
|
}
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.Playlists = &responses.Playlists{
|
response.Playlists = &responses.Playlists{
|
||||||
Playlist: slice.Map(allPls, api.buildPlaylist),
|
Playlist: slice.MapWithArg(allPls, ctx, api.buildPlaylist),
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ func (api *Router) getPlaylist(ctx context.Context, id string) (*responses.Subso
|
|||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.Playlist = &responses.PlaylistWithSongs{
|
response.Playlist = &responses.PlaylistWithSongs{
|
||||||
Playlist: api.buildPlaylist(*pls),
|
Playlist: api.buildPlaylist(ctx, *pls),
|
||||||
}
|
}
|
||||||
response.Playlist.Entry = slice.MapWithArg(pls.MediaFiles(), ctx, childFromMediaFile)
|
response.Playlist.Entry = slice.MapWithArg(pls.MediaFiles(), ctx, childFromMediaFile)
|
||||||
return response, nil
|
return response, nil
|
||||||
@@ -152,21 +154,28 @@ func (api *Router) UpdatePlaylist(r *http.Request) (*responses.Subsonic, error)
|
|||||||
return newResponse(), nil
|
return newResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *Router) buildPlaylist(p model.Playlist) responses.Playlist {
|
func (api *Router) buildPlaylist(ctx context.Context, p model.Playlist) responses.Playlist {
|
||||||
pls := responses.Playlist{}
|
pls := responses.Playlist{}
|
||||||
pls.Id = p.ID
|
pls.Id = p.ID
|
||||||
pls.Name = p.Name
|
pls.Name = p.Name
|
||||||
pls.Comment = p.Comment
|
|
||||||
pls.SongCount = int32(p.SongCount)
|
pls.SongCount = int32(p.SongCount)
|
||||||
pls.Owner = p.OwnerName
|
|
||||||
pls.Duration = int32(p.Duration)
|
pls.Duration = int32(p.Duration)
|
||||||
pls.Public = p.Public
|
|
||||||
pls.Created = p.CreatedAt
|
pls.Created = p.CreatedAt
|
||||||
pls.CoverArt = p.CoverArtID().String()
|
|
||||||
if p.IsSmartPlaylist() {
|
if p.IsSmartPlaylist() {
|
||||||
pls.Changed = time.Now()
|
pls.Changed = time.Now()
|
||||||
} else {
|
} else {
|
||||||
pls.Changed = p.UpdatedAt
|
pls.Changed = p.UpdatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player, ok := request.PlayerFrom(ctx)
|
||||||
|
if ok && isClientInList(conf.Server.Subsonic.MinimalClients, player.Client) {
|
||||||
|
return pls
|
||||||
|
}
|
||||||
|
|
||||||
|
pls.Comment = p.Comment
|
||||||
|
pls.Owner = p.OwnerName
|
||||||
|
pls.Public = p.Public
|
||||||
|
pls.CoverArt = p.CoverArtID().String()
|
||||||
|
|
||||||
return pls
|
return pls
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package subsonic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/core"
|
"github.com/navidrome/navidrome/core"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@@ -12,6 +15,108 @@ import (
|
|||||||
|
|
||||||
var _ core.Playlists = (*fakePlaylists)(nil)
|
var _ core.Playlists = (*fakePlaylists)(nil)
|
||||||
|
|
||||||
|
var _ = Describe("buildPlaylist", func() {
|
||||||
|
var router *Router
|
||||||
|
var ds model.DataStore
|
||||||
|
var ctx context.Context
|
||||||
|
var playlist model.Playlist
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ds = &tests.MockDataStore{}
|
||||||
|
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||||
|
ctx = context.Background()
|
||||||
|
|
||||||
|
createdAt := time.Date(2023, 1, 15, 10, 30, 0, 0, time.UTC)
|
||||||
|
updatedAt := time.Date(2023, 2, 20, 14, 45, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
playlist = model.Playlist{
|
||||||
|
ID: "pls-1",
|
||||||
|
Name: "My Playlist",
|
||||||
|
Comment: "Test comment",
|
||||||
|
OwnerName: "admin",
|
||||||
|
Public: true,
|
||||||
|
SongCount: 10,
|
||||||
|
Duration: 600,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with minimal client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||||
|
player := model.Player{Client: "minimal-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns only basic fields", func() {
|
||||||
|
result := router.buildPlaylist(ctx, playlist)
|
||||||
|
|
||||||
|
Expect(result.Id).To(Equal("pls-1"))
|
||||||
|
Expect(result.Name).To(Equal("My Playlist"))
|
||||||
|
Expect(result.SongCount).To(Equal(int32(10)))
|
||||||
|
Expect(result.Duration).To(Equal(int32(600)))
|
||||||
|
Expect(result.Created).To(Equal(playlist.CreatedAt))
|
||||||
|
Expect(result.Changed).To(Equal(playlist.UpdatedAt))
|
||||||
|
|
||||||
|
// These should not be set
|
||||||
|
Expect(result.Comment).To(BeEmpty())
|
||||||
|
Expect(result.Owner).To(BeEmpty())
|
||||||
|
Expect(result.Public).To(BeFalse())
|
||||||
|
Expect(result.CoverArt).To(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with non-minimal client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||||
|
player := model.Player{Client: "regular-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns all fields", func() {
|
||||||
|
result := router.buildPlaylist(ctx, playlist)
|
||||||
|
|
||||||
|
Expect(result.Id).To(Equal("pls-1"))
|
||||||
|
Expect(result.Name).To(Equal("My Playlist"))
|
||||||
|
Expect(result.SongCount).To(Equal(int32(10)))
|
||||||
|
Expect(result.Duration).To(Equal(int32(600)))
|
||||||
|
Expect(result.Created).To(Equal(playlist.CreatedAt))
|
||||||
|
Expect(result.Changed).To(Equal(playlist.UpdatedAt))
|
||||||
|
Expect(result.Comment).To(Equal("Test comment"))
|
||||||
|
Expect(result.Owner).To(Equal("admin"))
|
||||||
|
Expect(result.Public).To(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when minimal clients list is empty", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Subsonic.MinimalClients = ""
|
||||||
|
player := model.Player{Client: "any-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns all fields", func() {
|
||||||
|
result := router.buildPlaylist(ctx, playlist)
|
||||||
|
|
||||||
|
Expect(result.Comment).To(Equal("Test comment"))
|
||||||
|
Expect(result.Owner).To(Equal("admin"))
|
||||||
|
Expect(result.Public).To(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when no player in context", func() {
|
||||||
|
It("returns all fields", func() {
|
||||||
|
result := router.buildPlaylist(ctx, playlist)
|
||||||
|
|
||||||
|
Expect(result.Comment).To(Equal("Test comment"))
|
||||||
|
Expect(result.Owner).To(Equal("admin"))
|
||||||
|
Expect(result.Public).To(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
var _ = Describe("UpdatePlaylist", func() {
|
var _ = Describe("UpdatePlaylist", func() {
|
||||||
var router *Router
|
var router *Router
|
||||||
var ds model.DataStore
|
var ds model.DataStore
|
||||||
|
|||||||
-1
@@ -9,7 +9,6 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"isVideo": false,
|
|
||||||
"bpm": 0,
|
"bpm": 0,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"sortName": "sort name",
|
"sortName": "sort name",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<albumList>
|
<albumList>
|
||||||
<album id="1" isDir="false" isVideo="false" sortName="sort name" mediaType="album" musicBrainzId="00000000-0000-0000-0000-000000000000" displayArtist="Display artist" displayAlbumArtist="Display album artist" explicitStatus="explicit">
|
<album id="1" isDir="false" sortName="sort name" mediaType="album" musicBrainzId="00000000-0000-0000-0000-000000000000" displayArtist="Display artist" displayAlbumArtist="Display album artist" explicitStatus="explicit">
|
||||||
<genres name="Genre 1"></genres>
|
<genres name="Genre 1"></genres>
|
||||||
<genres name="Genre 2"></genres>
|
<genres name="Genre 2"></genres>
|
||||||
<moods>mood1</moods>
|
<moods>mood1</moods>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<albumList>
|
<albumList>
|
||||||
<album id="1" isDir="false" title="title" isVideo="false"></album>
|
<album id="1" isDir="false" title="title"></album>
|
||||||
</albumList>
|
</albumList>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
-2
@@ -93,7 +93,6 @@
|
|||||||
"transcodedSuffix": "mp3",
|
"transcodedSuffix": "mp3",
|
||||||
"duration": 146,
|
"duration": 146,
|
||||||
"bitRate": 320,
|
"bitRate": 320,
|
||||||
"isVideo": false,
|
|
||||||
"bpm": 127,
|
"bpm": 127,
|
||||||
"comment": "a comment",
|
"comment": "a comment",
|
||||||
"sortName": "sorted song",
|
"sortName": "sorted song",
|
||||||
@@ -185,7 +184,6 @@
|
|||||||
"transcodedSuffix": "mp3",
|
"transcodedSuffix": "mp3",
|
||||||
"duration": 146,
|
"duration": 146,
|
||||||
"bitRate": 320,
|
"bitRate": 320,
|
||||||
"isVideo": false,
|
|
||||||
"bpm": 0,
|
"bpm": 0,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"sortName": "",
|
"sortName": "",
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@
|
|||||||
<moods>sad</moods>
|
<moods>sad</moods>
|
||||||
<artists id="1" name="artist1"></artists>
|
<artists id="1" name="artist1"></artists>
|
||||||
<artists id="2" name="artist2"></artists>
|
<artists id="2" name="artist2"></artists>
|
||||||
<song id="1" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320" isVideo="false" bpm="127" comment="a comment" sortName="sorted song" mediaType="song" musicBrainzId="4321" channelCount="2" samplingRate="44100" bitDepth="16" displayArtist="artist1 & artist2" displayAlbumArtist="album artist1 & album artist2" displayComposer="composer 1 & composer 2" explicitStatus="clean">
|
<song id="1" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320" bpm="127" comment="a comment" sortName="sorted song" mediaType="song" musicBrainzId="4321" channelCount="2" samplingRate="44100" bitDepth="16" displayArtist="artist1 & artist2" displayAlbumArtist="album artist1 & album artist2" displayComposer="composer 1 & composer 2" explicitStatus="clean">
|
||||||
<isrc>ISRC-1</isrc>
|
<isrc>ISRC-1</isrc>
|
||||||
<genres name="rock"></genres>
|
<genres name="rock"></genres>
|
||||||
<genres name="progressive"></genres>
|
<genres name="progressive"></genres>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<artist id="2" name="artist2"></artist>
|
<artist id="2" name="artist2"></artist>
|
||||||
</contributors>
|
</contributors>
|
||||||
</song>
|
</song>
|
||||||
<song id="2" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320" isVideo="false">
|
<song id="2" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320">
|
||||||
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>
|
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>
|
||||||
</song>
|
</song>
|
||||||
</album>
|
</album>
|
||||||
|
|||||||
+1
-2
@@ -10,8 +10,7 @@
|
|||||||
"entry": {
|
"entry": {
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
},
|
},
|
||||||
"position": 123,
|
"position": 123,
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<bookmarks>
|
<bookmarks>
|
||||||
<bookmark position="123" username="user2" comment="a comment" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z">
|
<bookmark position="123" username="user2" comment="a comment" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z">
|
||||||
<entry id="1" isDir="false" title="title" isVideo="false"></entry>
|
<entry id="1" isDir="false" title="title"></entry>
|
||||||
</bookmark>
|
</bookmark>
|
||||||
</bookmarks>
|
</bookmarks>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
"transcodedSuffix": "mp3",
|
"transcodedSuffix": "mp3",
|
||||||
"duration": 146,
|
"duration": 146,
|
||||||
"bitRate": 320,
|
"bitRate": 320,
|
||||||
"isVideo": false,
|
|
||||||
"bpm": 127,
|
"bpm": 127,
|
||||||
"comment": "a comment",
|
"comment": "a comment",
|
||||||
"sortName": "sorted title",
|
"sortName": "sorted title",
|
||||||
@@ -116,7 +115,6 @@
|
|||||||
{
|
{
|
||||||
"id": "",
|
"id": "",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"isVideo": false,
|
|
||||||
"bpm": 0,
|
"bpm": 0,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"sortName": "",
|
"sortName": "",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<directory id="1" name="N">
|
<directory id="1" name="N">
|
||||||
<child id="1" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320" isVideo="false" bpm="127" comment="a comment" sortName="sorted title" mediaType="song" musicBrainzId="4321" channelCount="2" samplingRate="44100" bitDepth="16" displayArtist="artist 1 & artist 2" displayAlbumArtist="album artist 1 & album artist 2" displayComposer="composer 1 & composer 2" explicitStatus="clean">
|
<child id="1" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320" bpm="127" comment="a comment" sortName="sorted title" mediaType="song" musicBrainzId="4321" channelCount="2" samplingRate="44100" bitDepth="16" displayArtist="artist 1 & artist 2" displayAlbumArtist="album artist 1 & album artist 2" displayComposer="composer 1 & composer 2" explicitStatus="clean">
|
||||||
<isrc>ISRC-1</isrc>
|
<isrc>ISRC-1</isrc>
|
||||||
<isrc>ISRC-2</isrc>
|
<isrc>ISRC-2</isrc>
|
||||||
<genres name="rock"></genres>
|
<genres name="rock"></genres>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<artist id="4" name="composer2"></artist>
|
<artist id="4" name="composer2"></artist>
|
||||||
</contributors>
|
</contributors>
|
||||||
</child>
|
</child>
|
||||||
<child id="" isDir="false" isVideo="false">
|
<child id="" isDir="false">
|
||||||
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>
|
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>
|
||||||
</child>
|
</child>
|
||||||
</directory>
|
</directory>
|
||||||
|
|||||||
+1
-2
@@ -8,8 +8,7 @@
|
|||||||
"child": [
|
"child": [
|
||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "",
|
"id": "",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<directory id="" name="">
|
<directory id="" name="">
|
||||||
<child id="1" isDir="false" isVideo="false"></child>
|
<child id="1" isDir="false"></child>
|
||||||
</directory>
|
</directory>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
-1
@@ -9,7 +9,6 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"isVideo": false,
|
|
||||||
"bpm": 0,
|
"bpm": 0,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"sortName": "",
|
"sortName": "",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<directory id="" name="">
|
<directory id="" name="">
|
||||||
<child id="1" isDir="false" isVideo="false"></child>
|
<child id="1" isDir="false"></child>
|
||||||
</directory>
|
</directory>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "1",
|
"id": "1",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<directory id="1" name="N">
|
<directory id="1" name="N">
|
||||||
<child id="1" isDir="false" title="title" isVideo="false"></child>
|
<child id="1" isDir="false" title="title"></child>
|
||||||
</directory>
|
</directory>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"current": "111",
|
"current": "111",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<playQueue current="111" position="243" username="user1" changed="0001-01-01T00:00:00Z" changedBy="a_client">
|
<playQueue current="111" position="243" username="user1" changed="0001-01-01T00:00:00Z" changedBy="a_client">
|
||||||
<entry id="1" isDir="false" title="title" isVideo="false"></entry>
|
<entry id="1" isDir="false" title="title"></entry>
|
||||||
</playQueue>
|
</playQueue>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"currentIndex": 0,
|
"currentIndex": 0,
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<playQueueByIndex currentIndex="0" position="243" username="user1" changed="0001-01-01T00:00:00Z" changedBy="a_client">
|
<playQueueByIndex currentIndex="0" position="243" username="user1" changed="0001-01-01T00:00:00Z" changedBy="a_client">
|
||||||
<entry id="1" isDir="false" title="title" isVideo="false"></entry>
|
<entry id="1" isDir="false" title="title"></entry>
|
||||||
</playQueueByIndex>
|
</playQueueByIndex>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
"name": "bbb",
|
"name": "bbb",
|
||||||
"songCount": 0,
|
"songCount": 0,
|
||||||
"duration": 0,
|
"duration": 0,
|
||||||
"public": false,
|
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"changed": "0001-01-01T00:00:00Z"
|
"changed": "0001-01-01T00:00:00Z"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<playlists>
|
<playlists>
|
||||||
<playlist id="111" name="aaa" comment="comment" songCount="2" duration="120" public="true" owner="admin" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z" coverArt="pl-123123123123"></playlist>
|
<playlist id="111" name="aaa" comment="comment" songCount="2" duration="120" public="true" owner="admin" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z" coverArt="pl-123123123123"></playlist>
|
||||||
<playlist id="222" name="bbb" songCount="0" duration="0" public="false" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z"></playlist>
|
<playlist id="222" name="bbb" songCount="0" duration="0" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z"></playlist>
|
||||||
</playlists>
|
</playlists>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
"title": "title",
|
"title": "title",
|
||||||
"album": "album",
|
"album": "album",
|
||||||
"artist": "artist",
|
"artist": "artist",
|
||||||
"duration": 120,
|
"duration": 120
|
||||||
"isVideo": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2",
|
"id": "2",
|
||||||
@@ -23,8 +22,7 @@
|
|||||||
"title": "title 2",
|
"title": "title 2",
|
||||||
"album": "album",
|
"album": "album",
|
||||||
"artist": "artist",
|
"artist": "artist",
|
||||||
"duration": 300,
|
"duration": 300
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "ABC123",
|
"id": "ABC123",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<shares>
|
<shares>
|
||||||
<share id="ABC123" url="http://localhost/p/ABC123" description="Check it out!" username="deluan" created="2016-03-02T20:30:00Z" expires="2016-03-02T20:30:00Z" lastVisited="2016-03-02T20:30:00Z" visitCount="2">
|
<share id="ABC123" url="http://localhost/p/ABC123" description="Check it out!" username="deluan" created="2016-03-02T20:30:00Z" expires="2016-03-02T20:30:00Z" lastVisited="2016-03-02T20:30:00Z" visitCount="2">
|
||||||
<entry id="1" isDir="false" title="title" album="album" artist="artist" duration="120" isVideo="false"></entry>
|
<entry id="1" isDir="false" title="title" album="album" artist="artist" duration="120"></entry>
|
||||||
<entry id="2" isDir="false" title="title 2" album="album" artist="artist" duration="300" isVideo="false"></entry>
|
<entry id="2" isDir="false" title="title 2" album="album" artist="artist" duration="300"></entry>
|
||||||
</share>
|
</share>
|
||||||
</shares>
|
</shares>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<similarSongs>
|
<similarSongs>
|
||||||
<song id="1" isDir="false" title="title" isVideo="false"></song>
|
<song id="1" isDir="false" title="title"></song>
|
||||||
</similarSongs>
|
</similarSongs>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<similarSongs2>
|
<similarSongs2>
|
||||||
<song id="1" isDir="false" title="title" isVideo="false"></song>
|
<song id="1" isDir="false" title="title"></song>
|
||||||
</similarSongs2>
|
</similarSongs2>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
{
|
{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDir": false,
|
"isDir": false,
|
||||||
"title": "title",
|
"title": "title"
|
||||||
"isVideo": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||||
<topSongs>
|
<topSongs>
|
||||||
<song id="1" isDir="false" title="title" isVideo="false"></song>
|
<song id="1" isDir="false" title="title"></song>
|
||||||
</topSongs>
|
</topSongs>
|
||||||
</subsonic-response>
|
</subsonic-response>
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ type Child struct {
|
|||||||
Type string `xml:"type,attr,omitempty" json:"type,omitempty"`
|
Type string `xml:"type,attr,omitempty" json:"type,omitempty"`
|
||||||
UserRating int32 `xml:"userRating,attr,omitempty" json:"userRating,omitempty"`
|
UserRating int32 `xml:"userRating,attr,omitempty" json:"userRating,omitempty"`
|
||||||
SongCount int32 `xml:"songCount,attr,omitempty" json:"songCount,omitempty"`
|
SongCount int32 `xml:"songCount,attr,omitempty" json:"songCount,omitempty"`
|
||||||
IsVideo bool `xml:"isVideo,attr" json:"isVideo"`
|
IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo,omitempty"`
|
||||||
BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty" json:"bookmarkPosition,omitempty"`
|
BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty" json:"bookmarkPosition,omitempty"`
|
||||||
/*
|
/*
|
||||||
<xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.6.0 -->
|
<xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.6.0 -->
|
||||||
@@ -177,7 +177,7 @@ type OpenSubsonicChild struct {
|
|||||||
SortName string `xml:"sortName,attr,omitempty" json:"sortName"`
|
SortName string `xml:"sortName,attr,omitempty" json:"sortName"`
|
||||||
MediaType MediaType `xml:"mediaType,attr,omitempty" json:"mediaType"`
|
MediaType MediaType `xml:"mediaType,attr,omitempty" json:"mediaType"`
|
||||||
MusicBrainzId string `xml:"musicBrainzId,attr,omitempty" json:"musicBrainzId"`
|
MusicBrainzId string `xml:"musicBrainzId,attr,omitempty" json:"musicBrainzId"`
|
||||||
Isrc Array[string] `xml:"isrc,omitempty" json:"isrc"`
|
Isrc Array[string] `xml:"isrc,omitempty" json:"isrc"`
|
||||||
Genres Array[ItemGenre] `xml:"genres,omitempty" json:"genres"`
|
Genres Array[ItemGenre] `xml:"genres,omitempty" json:"genres"`
|
||||||
ReplayGain ReplayGain `xml:"replayGain,omitempty" json:"replayGain"`
|
ReplayGain ReplayGain `xml:"replayGain,omitempty" json:"replayGain"`
|
||||||
ChannelCount int32 `xml:"channelCount,attr,omitempty" json:"channelCount"`
|
ChannelCount int32 `xml:"channelCount,attr,omitempty" json:"channelCount"`
|
||||||
@@ -308,7 +308,7 @@ type Playlist struct {
|
|||||||
Comment string `xml:"comment,attr,omitempty" json:"comment,omitempty"`
|
Comment string `xml:"comment,attr,omitempty" json:"comment,omitempty"`
|
||||||
SongCount int32 `xml:"songCount,attr" json:"songCount"`
|
SongCount int32 `xml:"songCount,attr" json:"songCount"`
|
||||||
Duration int32 `xml:"duration,attr" json:"duration"`
|
Duration int32 `xml:"duration,attr" json:"duration"`
|
||||||
Public bool `xml:"public,attr" json:"public"`
|
Public bool `xml:"public,attr,omitempty" json:"public,omitempty"`
|
||||||
Owner string `xml:"owner,attr,omitempty" json:"owner,omitempty"`
|
Owner string `xml:"owner,attr,omitempty" json:"owner,omitempty"`
|
||||||
Created time.Time `xml:"created,attr" json:"created"`
|
Created time.Time `xml:"created,attr" json:"created"`
|
||||||
Changed time.Time `xml:"changed,attr" json:"changed"`
|
Changed time.Time `xml:"changed,attr" json:"changed"`
|
||||||
|
|||||||
Reference in New Issue
Block a user