feat(subsonic): implement indexBasedQueue extension (#4244)

* redo this whole PR, but clearner now that better errata is in

* update play queue types
This commit is contained in:
Kendall Garner
2025-11-09 17:52:05 +00:00
committed by GitHub
parent 508670ecfb
commit 53ff33866d
12 changed files with 172 additions and 10 deletions
@@ -6,6 +6,7 @@
"openSubsonic": true,
"playQueue": {
"username": "",
"changed": "0001-01-01T00:00:00Z",
"changedBy": ""
}
}
@@ -1,3 +1,3 @@
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
<playQueue username="" changedBy=""></playQueue>
<playQueue username="" changed="0001-01-01T00:00:00Z" changedBy=""></playQueue>
</subsonic-response>
@@ -0,0 +1,22 @@
{
"status": "ok",
"version": "1.16.1",
"type": "navidrome",
"serverVersion": "v0.55.0",
"openSubsonic": true,
"playQueueByIndex": {
"entry": [
{
"id": "1",
"isDir": false,
"title": "title",
"isVideo": false
}
],
"currentIndex": 0,
"position": 243,
"username": "user1",
"changed": "0001-01-01T00:00:00Z",
"changedBy": "a_client"
}
}
@@ -0,0 +1,5 @@
<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">
<entry id="1" isDir="false" title="title" isVideo="false"></entry>
</playQueueByIndex>
</subsonic-response>
@@ -0,0 +1,12 @@
{
"status": "ok",
"version": "1.16.1",
"type": "navidrome",
"serverVersion": "v0.55.0",
"openSubsonic": true,
"playQueueByIndex": {
"username": "",
"changed": "0001-01-01T00:00:00Z",
"changedBy": ""
}
}
@@ -0,0 +1,3 @@
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
<playQueueByIndex username="" changed="0001-01-01T00:00:00Z" changedBy=""></playQueueByIndex>
</subsonic-response>
+16 -6
View File
@@ -60,6 +60,7 @@ type Subsonic struct {
// OpenSubsonic extensions
OpenSubsonicExtensions *OpenSubsonicExtensions `xml:"openSubsonicExtensions,omitempty" json:"openSubsonicExtensions,omitempty"`
LyricsList *LyricsList `xml:"lyricsList,omitempty" json:"lyricsList,omitempty"`
PlayQueueByIndex *PlayQueueByIndex `xml:"playQueueByIndex,omitempty" json:"playQueueByIndex,omitempty"`
}
const (
@@ -439,12 +440,21 @@ type TopSongs struct {
}
type PlayQueue struct {
Entry []Child `xml:"entry,omitempty" json:"entry,omitempty"`
Current string `xml:"current,attr,omitempty" json:"current,omitempty"`
Position int64 `xml:"position,attr,omitempty" json:"position,omitempty"`
Username string `xml:"username,attr" json:"username"`
Changed *time.Time `xml:"changed,attr,omitempty" json:"changed,omitempty"`
ChangedBy string `xml:"changedBy,attr" json:"changedBy"`
Entry []Child `xml:"entry,omitempty" json:"entry,omitempty"`
Current string `xml:"current,attr,omitempty" json:"current,omitempty"`
Position int64 `xml:"position,attr,omitempty" json:"position,omitempty"`
Username string `xml:"username,attr" json:"username"`
Changed time.Time `xml:"changed,attr" json:"changed"`
ChangedBy string `xml:"changedBy,attr" json:"changedBy"`
}
type PlayQueueByIndex struct {
Entry []Child `xml:"entry,omitempty" json:"entry,omitempty"`
CurrentIndex *int `xml:"currentIndex,attr,omitempty" json:"currentIndex,omitempty"`
Position int64 `xml:"position,attr,omitempty" json:"position,omitempty"`
Username string `xml:"username,attr" json:"username"`
Changed time.Time `xml:"changed,attr" json:"changed"`
ChangedBy string `xml:"changedBy,attr" json:"changedBy"`
}
type Bookmark struct {
+35 -1
View File
@@ -768,7 +768,7 @@ var _ = Describe("Responses", func() {
response.PlayQueue.Username = "user1"
response.PlayQueue.Current = "111"
response.PlayQueue.Position = 243
response.PlayQueue.Changed = &time.Time{}
response.PlayQueue.Changed = time.Time{}
response.PlayQueue.ChangedBy = "a_client"
child := make([]Child, 1)
child[0] = Child{Id: "1", Title: "title", IsDir: false}
@@ -783,6 +783,40 @@ var _ = Describe("Responses", func() {
})
})
Describe("PlayQueueByIndex", func() {
BeforeEach(func() {
response.PlayQueueByIndex = &PlayQueueByIndex{}
})
Context("without data", func() {
It("should match .XML", func() {
Expect(xml.MarshalIndent(response, "", " ")).To(MatchSnapshot())
})
It("should match .JSON", func() {
Expect(json.MarshalIndent(response, "", " ")).To(MatchSnapshot())
})
})
Context("with data", func() {
BeforeEach(func() {
response.PlayQueueByIndex.Username = "user1"
response.PlayQueueByIndex.CurrentIndex = gg.P(0)
response.PlayQueueByIndex.Position = 243
response.PlayQueueByIndex.Changed = time.Time{}
response.PlayQueueByIndex.ChangedBy = "a_client"
child := make([]Child, 1)
child[0] = Child{Id: "1", Title: "title", IsDir: false}
response.PlayQueueByIndex.Entry = child
})
It("should match .XML", func() {
Expect(xml.MarshalIndent(response, "", " ")).To(MatchSnapshot())
})
It("should match .JSON", func() {
Expect(json.MarshalIndent(response, "", " ")).To(MatchSnapshot())
})
})
})
Describe("Shares", func() {
BeforeEach(func() {
response.Shares = &Shares{}