1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-23 01:16:18 +00:00

API: marked explicitly unsupported methods/parameters as such

This commit is contained in:
spl0k 2018-03-10 22:15:40 +01:00
parent adb4e7e89b
commit a6b894c586
9 changed files with 95 additions and 54 deletions

View File

@ -69,15 +69,15 @@ or with version 1.8.0.
| [`getCaptions`](#getcaptions) | 1.15.0 | 🔴 | | [`getCaptions`](#getcaptions) | 1.15.0 | 🔴 |
| [`getCoverArt`](#getcoverart) | | ✔️ | | [`getCoverArt`](#getcoverart) | | ✔️ |
| [`getLyrics`](#getlyrics) | | ✔️ | | [`getLyrics`](#getlyrics) | | ✔️ |
| [`getAvatar`](#getavatar) | | 🔴 | | [`getAvatar`](#getavatar) | | |
| [`star`](#star) | | ✔️ | | [`star`](#star) | | ✔️ |
| [`unstar`](#unstar) | | ✔️ | | [`unstar`](#unstar) | | ✔️ |
| [`setRating`](#setrating) | | ✔️ | | [`setRating`](#setrating) | | ✔️ |
| [`scrobble`](#scrobble) | | ✔️ | | [`scrobble`](#scrobble) | | ✔️ |
| [`getShares`](#getshares) | | 🔴 | | [`getShares`](#getshares) | | |
| [`createShare`](#createshare) | | 🔴 | | [`createShare`](#createshare) | | |
| [`updateShare`](#updateshare) | | 🔴 | | [`updateShare`](#updateshare) | | |
| [`deleteShare`](#deleteshare) | | 🔴 | | [`deleteShare`](#deleteshare) | | |
| [`getPodcasts`](#getpodcasts) | | ❔ | | [`getPodcasts`](#getpodcasts) | | ❔ |
| [`getNewestPodcasts`](#getnewestpodcasts) | 1.14.0 | ❔ | | [`getNewestPodcasts`](#getnewestpodcasts) | 1.14.0 | ❔ |
| [`refreshPodcasts`](#refreshpodcasts) | 1.9.0 | ❔ | | [`refreshPodcasts`](#refreshpodcasts) | 1.9.0 | ❔ |
@ -432,8 +432,8 @@ No parameter
| `id` | | ✔️ | | `id` | | ✔️ |
| `maxBitRate` | | ✔️ | | `maxBitRate` | | ✔️ |
| `format` | | ✔️ | | `format` | | ✔️ |
| `timeOffset` | | 🔴 | | `timeOffset` | | |
| `size` | | 🔴 | | `size` | | |
| `estimateContentLength` | | 📅 | | `estimateContentLength` | | 📅 |
| `converted` | 1.15.0 | 🔴 | | `converted` | 1.15.0 | 🔴 |
@ -478,11 +478,11 @@ No parameter
| `title` | | ✔️ | | `title` | | ✔️ |
#### `getAvatar` #### `getAvatar`
🔴
| Parameter | Vers. | | | Parameter | Vers. | |
|------------|-------|---| |------------|-------|---|
| `username` | | 🔴 | | `username` | | |
### Media annotation ### Media annotation
@ -524,33 +524,33 @@ No parameter
### Sharing ### Sharing
#### `getShares` #### `getShares`
🔴
No parameter No parameter
#### `createShare` #### `createShare`
🔴
| Parameter | Vers. | | | Parameter | Vers. | |
|---------------|-------|---| |---------------|-------|---|
| `id` | | 🔴 | | `id` | | |
| `description` | | 🔴 | | `description` | | |
| `expires` | | 🔴 | | `expires` | | |
#### `updateShare` #### `updateShare`
🔴
| Parameter | Vers. | | | Parameter | Vers. | |
|---------------|-------|---| |---------------|-------|---|
| `id` | | 🔴 | | `id` | | |
| `description` | | 🔴 | | `description` | | |
| `expires` | | 🔴 | | `expires` | | |
#### `deleteShare` #### `deleteShare`
🔴
| Parameter | Vers. | | | Parameter | Vers. | |
|-----------|-------|---| |-----------|-------|---|
| `id` | | 🔴 | | `id` | | |
### Podcast ### Podcast
@ -687,19 +687,19 @@ No parameter
| `username` | | ✔️ | | `username` | | ✔️ |
| `password` | | ✔️ | | `password` | | ✔️ |
| `email` | | ✔️ | | `email` | | ✔️ |
| `ldapAuthenticated` | | | | `ldapAuthenticated` | | |
| `adminRole` | | ✔️ | | `adminRole` | | ✔️ |
| `settingsRole` | | | | `settingsRole` | | |
| `streamRole` | | | | `streamRole` | | |
| `jukeboxRole` | | 📅 | | `jukeboxRole` | | 📅 |
| `downloadRole` | | | | `downloadRole` | | |
| `uploadRole` | | | | `uploadRole` | | |
| `playlistRole` | | | | `playlistRole` | | |
| `coverArtRole` | | | | `coverArtRole` | | |
| `commentRole` | | | | `commentRole` | | |
| `podcastRole` | | | | `podcastRole` | | |
| `shareRole` | | 🔴 | | `shareRole` | | |
| `videoConversionRole` | 1.14.0 | 🔴 | | `videoConversionRole` | 1.14.0 | |
| `musicFolderId` | 1.12.0 | 📅 | | `musicFolderId` | 1.12.0 | 📅 |
#### `updateUser` #### `updateUser`
@ -710,18 +710,18 @@ No parameter
| `username` | 1.10.2 | 📅 | | `username` | 1.10.2 | 📅 |
| `password` | 1.10.2 | 📅 | | `password` | 1.10.2 | 📅 |
| `email` | 1.10.2 | 📅 | | `email` | 1.10.2 | 📅 |
| `ldapAuthenticated` | 1.10.2 | | | `ldapAuthenticated` | 1.10.2 | |
| `adminRole` | 1.10.2 | 📅 | | `adminRole` | 1.10.2 | 📅 |
| `settingsRole` | 1.10.2 | | | `settingsRole` | 1.10.2 | |
| `streamRole` | 1.10.2 | | | `streamRole` | 1.10.2 | |
| `jukeboxRole` | 1.10.2 | 📅 | | `jukeboxRole` | 1.10.2 | 📅 |
| `downloadRole` | 1.10.2 | | | `downloadRole` | 1.10.2 | |
| `uploadRole` | 1.10.2 | | | `uploadRole` | 1.10.2 | |
| `coverArtRole` | 1.10.2 | | | `coverArtRole` | 1.10.2 | |
| `commentRole` | 1.10.2 | | | `commentRole` | 1.10.2 | |
| `podcastRole` | 1.10.2 | | | `podcastRole` | 1.10.2 | |
| `shareRole` | 1.10.2 | 🔴 | | `shareRole` | 1.10.2 | |
| `videoConversionRole` | 1.14.0 | 🔴 | | `videoConversionRole` | 1.14.0 | |
| `musicFolderId` | 1.12.0 | 📅 | | `musicFolderId` | 1.12.0 | 📅 |
| `maxBitRate` | 1.13.0 | 📅 | | `maxBitRate` | 1.13.0 | 📅 |

View File

@ -88,4 +88,5 @@ from .annotation import *
from .chat import * from .chat import *
from .search import * from .search import *
from .playlists import * from .playlists import *
from .unsupported import *

View File

@ -139,7 +139,3 @@ def track_info():
res = get_entity(Track) res = get_entity(Track)
return request.formatter('song', res.as_subsonic_child(request.user, request.client)) return request.formatter('song', res.as_subsonic_child(request.user, request.client))
@api.route('/getVideos.view', methods = [ 'GET', 'POST' ])
def list_videos():
return request.formatter.error(0, 'Video streaming not supported'), 501

View File

@ -38,5 +38,5 @@ def generic_error(e): # pragma: nocover
#@api.errorhandler(404) #@api.errorhandler(404)
@api.route('/<path:invalid>', methods = [ 'GET', 'POST' ]) # blueprint 404 workaround @api.route('/<path:invalid>', methods = [ 'GET', 'POST' ]) # blueprint 404 workaround
def not_found(*args, **kwargs): def not_found(*args, **kwargs):
return GenericError('Not implemented'), 501 return GenericError('Unknown method'), 404

View File

@ -34,6 +34,11 @@ class GenericError(SubsonicAPIException):
class ServerError(GenericError): class ServerError(GenericError):
code = 500 code = 500
class UnsupportedParameter(GenericError):
def __init__(self, parameter, *args, **kwargs):
message = "Unsupported parameter '{}'".format(parameter)
super(UnsupportedParameter, self).__init__(message, *args, **kwargs)
class MissingParameter(SubsonicAPIException): class MissingParameter(SubsonicAPIException):
api_code = 10 api_code = 10

View File

@ -23,7 +23,7 @@ from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now
from ..py23 import dict from ..py23 import dict
from . import api, get_entity from . import api, get_entity
from .exceptions import GenericError, MissingParameter, NotFound, ServerError from .exceptions import GenericError, MissingParameter, NotFound, ServerError, UnsupportedParameter
def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate): def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate):
if not base_cmdline: if not base_cmdline:
@ -39,7 +39,12 @@ def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_f
def stream_media(): def stream_media():
res = get_entity(Track) res = get_entity(Track)
maxBitRate, format, timeOffset, size, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ]) if 'timeOffset' in request.values:
raise UnsupportedParameter('timeOffset')
if 'size' in request.values:
raise UnsupportedParameter('size')
maxBitRate, format, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'estimateContentLength' ])
if format: if format:
format = format.lower() format = format.lower()
@ -94,7 +99,7 @@ def stream_media():
if not data: if not data:
break break
yield data yield data
except: except: # pragma: nocover
if dec_proc != None: if dec_proc != None:
dec_proc.terminate() dec_proc.terminate()
proc.terminate() proc.terminate()
@ -184,10 +189,10 @@ def lyrics():
title = root.find('cl:LyricSong', namespaces = ns).text, title = root.find('cl:LyricSong', namespaces = ns).text,
_value_ = root.find('cl:Lyric', namespaces = ns).text _value_ = root.find('cl:Lyric', namespaces = ns).text
)) ))
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e: # pragma: nocover
current_app.logger.warning('Error while requesting the ChartLyrics API: ' + str(e)) current_app.logger.warning('Error while requesting the ChartLyrics API: ' + str(e))
return request.formatter('lyrics', dict()) return request.formatter('lyrics', dict()) # pragma: nocover
def read_file_as_unicode(path): def read_file_as_unicode(path):
""" Opens a file trying with different encodings and returns the contents as a unicode string """ """ Opens a file trying with different encodings and returns the contents as a unicode string """

View File

@ -0,0 +1,22 @@
# coding: utf-8
#
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2018 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
from . import api
from .exceptions import GenericError
methods = (
'getVideos', 'getAvatar', 'getShares', 'createShare', 'updateShare', 'deleteShare',
)
def unsupported():
return GenericError('Not supported by Supysonic'), 501
for m in methods:
api.add_url_rule('/{}.view'.format(m), 'unsupported', unsupported, methods = [ 'GET', 'POST' ])

View File

@ -128,13 +128,23 @@ class ApiSetupTestCase(TestBase):
self.assertIn('license', json['subsonic-response']) self.assertIn('license', json['subsonic-response'])
def test_not_implemented(self): def test_not_implemented(self):
# Access to not implemented endpoint # Access to not implemented/unknown endpoint
rv = self.client.get('/rest/not-implemented', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' }) rv = self.client.get('/rest/unknown', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' })
self.assertEqual(rv.status_code, 404)
self.assertIn('status="failed"', rv.data)
self.assertIn('code="0"', rv.data)
rv = self.client.post('/rest/unknown', data = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' })
self.assertEqual(rv.status_code, 404)
self.assertIn('status="failed"', rv.data)
self.assertIn('code="0"', rv.data)
rv = self.client.get('/rest/getVideos.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' })
self.assertEqual(rv.status_code, 501) self.assertEqual(rv.status_code, 501)
self.assertIn('status="failed"', rv.data) self.assertIn('status="failed"', rv.data)
self.assertIn('code="0"', rv.data) self.assertIn('code="0"', rv.data)
rv = self.client.post('/rest/not-implemented', data = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' }) rv = self.client.post('/rest/getVideos.view', data = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' })
self.assertEqual(rv.status_code, 501) self.assertEqual(rv.status_code, 501)
self.assertIn('status="failed"', rv.data) self.assertIn('status="failed"', rv.data)
self.assertIn('code="0"', rv.data) self.assertIn('code="0"', rv.data)

View File

@ -57,6 +57,8 @@ class MediaTestCase(ApiTestBase):
self._make_request('stream', { 'id': str(uuid.uuid4()) }, error = 70) self._make_request('stream', { 'id': str(uuid.uuid4()) }, error = 70)
self._make_request('stream', { 'id': str(self.folderid) }, error = 70) self._make_request('stream', { 'id': str(self.folderid) }, error = 70)
self._make_request('stream', { 'id': str(self.trackid), 'maxBitRate': 'string' }, error = 0) self._make_request('stream', { 'id': str(self.trackid), 'maxBitRate': 'string' }, error = 0)
self._make_request('stream', { 'id': str(self.trackid), 'timeOffset': 2 }, error = 0)
self._make_request('stream', { 'id': str(self.trackid), 'size': '640x480' }, error = 0)
rv = self.client.get('/rest/stream.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.trackid) }) rv = self.client.get('/rest/stream.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.trackid) })
self.assertEqual(rv.status_code, 200) self.assertEqual(rv.status_code, 200)