1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-22 17:06:17 +00:00

Subsonic API 1.10.2

Except for changes required to comply to the XSD specification,
this does not include any feature this version brings

Closes #194
This commit is contained in:
Alban Féron 2020-11-08 18:00:36 +01:00
parent 8e3cd5ef4d
commit 1be526b8d2
No known key found for this signature in database
GPG Key ID: 8CE0313646D16165
6 changed files with 41 additions and 17 deletions

View File

@ -4,7 +4,7 @@ This page lists all the API methods and their parameters up to the version
1.16.0 (Subsonic 6.1.2). Here you'll find details about which API features 1.16.0 (Subsonic 6.1.2). Here you'll find details about which API features
_Supysonic_ support, plan on supporting, or won't. _Supysonic_ support, plan on supporting, or won't.
At the moment, the current target API version is 1.9.0. At the moment, the current target API version is 1.10.2.
The following information was gathered by _diff_-ing various snapshots of the The following information was gathered by _diff_-ing various snapshots of the
[Subsonic API page](http://www.subsonic.org/pages/api.jsp). [Subsonic API page](http://www.subsonic.org/pages/api.jsp).

View File

@ -1,13 +1,11 @@
# coding: utf-8
#
# This file is part of Supysonic. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # Supysonic is a Python implementation of the Subsonic server API.
# #
# Copyright (C) 2013-2018 Alban 'spl0k' Féron # Copyright (C) 2013-2020 Alban 'spl0k' Féron
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
API_VERSION = "1.9.0" API_VERSION = "1.10.2"
import binascii import binascii
import uuid import uuid

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # Supysonic is a Python implementation of the Subsonic server API.
# #
# Copyright (C) 2013-2018 Alban 'spl0k' Féron # Copyright (C) 2013-2020 Alban 'spl0k' Féron
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
@ -11,7 +11,7 @@ import string
import uuid import uuid
from flask import request from flask import request
from pony.orm import ObjectNotFound, select from pony.orm import ObjectNotFound, select, count
from ..db import Folder, Artist, Album, Track from ..db import Folder, Artist, Album, Track
@ -50,7 +50,9 @@ def list_indexes():
last_modif = max(map(lambda f: f.last_scan, folders)) last_modif = max(map(lambda f: f.last_scan, folders))
if ifModifiedSince is not None and last_modif < ifModifiedSince: if ifModifiedSince is not None and last_modif < ifModifiedSince:
return request.formatter("indexes", dict(lastModified=last_modif * 1000)) return request.formatter(
"indexes", dict(lastModified=last_modif * 1000, ignoredArticles="")
)
# The XSD lies, we don't return artists but a directory structure # The XSD lies, we don't return artists but a directory structure
artists = [] artists = []
@ -76,6 +78,7 @@ def list_indexes():
"indexes", "indexes",
dict( dict(
lastModified=last_modif * 1000, lastModified=last_modif * 1000,
ignoredArticles="",
index=[ index=[
dict( dict(
name=k, name=k,
@ -121,7 +124,10 @@ def list_genres():
"genres", "genres",
dict( dict(
genre=[ genre=[
dict(value=genre) for genre in select(t.genre for t in Track if t.genre) dict(value=genre, songCount=sc, albumCount=ac)
for genre, sc, ac in select(
(t.genre, count(), count(t.album)) for t in Track if t.genre
)
] ]
), ),
) )
@ -146,6 +152,7 @@ def list_artists():
return request.formatter( return request.formatter(
"artists", "artists",
dict( dict(
ignoredArticles="",
index=[ index=[
dict( dict(
name=k, name=k,
@ -155,7 +162,7 @@ def list_artists():
], ],
) )
for k, v in sorted(indexes.items()) for k, v in sorted(indexes.items())
] ],
), ),
) )

View File

@ -26,7 +26,7 @@ class ApiTestBase(TestBase):
def setUp(self): def setUp(self):
super(ApiTestBase, self).setUp() super(ApiTestBase, self).setUp()
xsd = etree.parse("tests/assets/subsonic-rest-api-1.9.0.xsd") xsd = etree.parse("tests/assets/subsonic-rest-api-1.10.2.xsd")
self.schema = etree.XMLSchema(xsd) self.schema = etree.XMLSchema(xsd)
def _find(self, xml, path): def _find(self, xml, path):

View File

@ -1,10 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding: utf-8
# #
# This file is part of Supysonic. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # Supysonic is a Python implementation of the Subsonic server API.
# #
# Copyright (C) 2017 Alban 'spl0k' Féron # Copyright (C) 2017-2020 Alban 'spl0k' Féron
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
@ -53,6 +52,7 @@ class BrowseTestCase(ApiTestBase):
duration=2, duration=2,
album=album, album=album,
artist=artist, artist=artist,
genre="Music!",
bitrate=320, bitrate=320,
path="tests/assets/{0}rtist/{0}{1}lbum/{2}".format( path="tests/assets/{0}rtist/{0}{1}lbum/{2}".format(
letter, lether, song letter, lether, song
@ -201,6 +201,13 @@ class BrowseTestCase(ApiTestBase):
def test_get_videos(self): def test_get_videos(self):
self._make_request("getVideos", error=0) self._make_request("getVideos", error=0)
def test_genres(self):
rv, child = self._make_request("getGenres", tag="genres")
self.assertEqual(len(child), 1)
self.assertEqual(child[0].text, "Music!")
self.assertEqual(child[0].get("songCount"), "18")
self.assertEqual(child[0].get("albumCount"), "6")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -4,7 +4,7 @@
targetNamespace="http://subsonic.org/restapi" targetNamespace="http://subsonic.org/restapi"
attributeFormDefault="unqualified" attributeFormDefault="unqualified"
elementFormDefault="qualified" elementFormDefault="qualified"
version="1.9.0"> version="1.10.2">
<xs:element name="subsonic-response" type="sub:Response"/> <xs:element name="subsonic-response" type="sub:Response"/>
@ -68,8 +68,8 @@
</xs:complexType> </xs:complexType>
<xs:complexType name="MusicFolder"> <xs:complexType name="MusicFolder">
<xs:attribute name="id" type="xs:int" use="required"/> <xs:attribute name="id" type="xs:int" use="required"/>
<xs:attribute name="name" type="xs:string" use="optional"/> <xs:attribute name="name" type="xs:string" use="optional"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="Indexes"> <xs:complexType name="Indexes">
@ -79,6 +79,7 @@
<xs:element name="child" type="sub:Child" minOccurs="0" maxOccurs="unbounded"/> <!-- Added in 1.7.0 --> <xs:element name="child" type="sub:Child" minOccurs="0" maxOccurs="unbounded"/> <!-- Added in 1.7.0 -->
</xs:sequence> </xs:sequence>
<xs:attribute name="lastModified" type="xs:long" use="required"/> <xs:attribute name="lastModified" type="xs:long" use="required"/>
<xs:attribute name="ignoredArticles" type="xs:string" use="required"/> <!-- Added in 1.10.0 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="Index"> <xs:complexType name="Index">
@ -91,18 +92,25 @@
<xs:complexType name="Artist"> <xs:complexType name="Artist">
<xs:attribute name="id" type="xs:string" use="required"/> <xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="name" type="xs:string" use="required"/> <xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.10.1 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="Genres"> <xs:complexType name="Genres">
<xs:sequence> <xs:sequence>
<xs:element name="genre" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="genre" type="sub:Genre" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="Genre" mixed="true">
<xs:attribute name="songCount" type="xs:int" use="required"/> <!-- Added in 1.10.2 -->
<xs:attribute name="albumCount" type="xs:int" use="required"/> <!-- Added in 1.10.2 -->
</xs:complexType>
<xs:complexType name="ArtistsID3"> <xs:complexType name="ArtistsID3">
<xs:sequence> <xs:sequence>
<xs:element name="index" type="sub:IndexID3" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="index" type="sub:IndexID3" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
<xs:attribute name="ignoredArticles" type="xs:string" use="required"/> <!-- Added in 1.10.0 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="IndexID3"> <xs:complexType name="IndexID3">
@ -140,6 +148,8 @@
<xs:attribute name="duration" type="xs:int" use="required"/> <xs:attribute name="duration" type="xs:int" use="required"/>
<xs:attribute name="created" type="xs:dateTime" use="required"/> <xs:attribute name="created" type="xs:dateTime" use="required"/>
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <xs:attribute name="starred" type="xs:dateTime" use="optional"/>
<xs:attribute name="year" type="xs:int" use="optional"/> <!-- Added in 1.10.1 -->
<xs:attribute name="genre" type="xs:string" use="optional"/> <!-- Added in 1.10.1 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="AlbumWithSongsID3"> <xs:complexType name="AlbumWithSongsID3">
@ -165,6 +175,7 @@
<xs:attribute name="id" type="xs:string" use="required"/> <xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="parent" type="xs:string" use="optional"/> <xs:attribute name="parent" type="xs:string" use="optional"/>
<xs:attribute name="name" type="xs:string" use="required"/> <xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.10.1 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="Child"> <xs:complexType name="Child">
@ -195,6 +206,7 @@
<xs:attribute name="albumId" type="xs:string" use="optional"/> <!-- Added in 1.8.0 --> <xs:attribute name="albumId" type="xs:string" use="optional"/> <!-- Added in 1.8.0 -->
<xs:attribute name="artistId" type="xs:string" use="optional"/> <!-- Added in 1.8.0 --> <xs:attribute name="artistId" type="xs:string" use="optional"/> <!-- Added in 1.8.0 -->
<xs:attribute name="type" type="sub:MediaType" use="optional"/> <!-- Added in 1.8.0 --> <xs:attribute name="type" type="sub:MediaType" use="optional"/> <!-- Added in 1.8.0 -->
<xs:attribute name="bookmarkPosition" type="xs:long" use="optional"/> <!-- In millis. Added in 1.10.1 -->
</xs:complexType> </xs:complexType>
<xs:simpleType name="MediaType"> <xs:simpleType name="MediaType">