mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-06 02:09:55 +02:00
See #75: initial subsonic implementation that works with http://p.subfireplayer.net
This commit is contained in:
parent
9682299480
commit
bbd273404a
15 changed files with 774 additions and 48 deletions
143
api/funkwhale_api/subsonic/views.py
Normal file
143
api/funkwhale_api/subsonic/views.py
Normal file
|
@ -0,0 +1,143 @@
|
|||
from rest_framework import exceptions
|
||||
from rest_framework import permissions as rest_permissions
|
||||
from rest_framework import response
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import list_route
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.music import views as music_views
|
||||
|
||||
from . import authentication
|
||||
from . import negotiation
|
||||
from . import serializers
|
||||
|
||||
|
||||
def find_object(queryset, model_field='pk', field='id', cast=int):
|
||||
def decorator(func):
|
||||
def inner(self, request, *args, **kwargs):
|
||||
data = request.GET or request.POST
|
||||
try:
|
||||
raw_value = data[field]
|
||||
except KeyError:
|
||||
return response.Response({
|
||||
'code': 10,
|
||||
'message': "required parameter '{}' not present".format(field)
|
||||
})
|
||||
try:
|
||||
value = cast(raw_value)
|
||||
except (TypeError, ValidationError):
|
||||
return response.Response({
|
||||
'code': 0,
|
||||
'message': 'For input string "{}"'.format(raw_value)
|
||||
})
|
||||
try:
|
||||
obj = queryset.get(**{model_field: value})
|
||||
except queryset.model.DoesNotExist:
|
||||
return response.Response({
|
||||
'code': 70,
|
||||
'message': '{} not found'.format(
|
||||
queryset.model.__class__.__name__)
|
||||
})
|
||||
kwargs['obj'] = obj
|
||||
return func(self, request, *args, **kwargs)
|
||||
return inner
|
||||
return decorator
|
||||
|
||||
|
||||
class SubsonicViewSet(viewsets.GenericViewSet):
|
||||
content_negotiation_class = negotiation.SubsonicContentNegociation
|
||||
authentication_classes = [authentication.SubsonicAuthentication]
|
||||
permissions_classes = [rest_permissions.IsAuthenticated]
|
||||
|
||||
def handle_exception(self, exc):
|
||||
# subsonic API sends 200 status code with custom error
|
||||
# codes in the payload
|
||||
mapping = {
|
||||
exceptions.AuthenticationFailed: (
|
||||
40, 'Wrong username or password.'
|
||||
)
|
||||
}
|
||||
payload = {
|
||||
'status': 'failed'
|
||||
}
|
||||
try:
|
||||
code, message = mapping[exc.__class__]
|
||||
except KeyError:
|
||||
return super().handle_exception(exc)
|
||||
else:
|
||||
payload['error'] = {
|
||||
'code': code,
|
||||
'message': message
|
||||
}
|
||||
|
||||
return response.Response(payload, status=200)
|
||||
|
||||
@list_route(
|
||||
methods=['get', 'post'],
|
||||
permission_classes=[])
|
||||
def ping(self, request, *args, **kwargs):
|
||||
data = {
|
||||
'status': 'ok',
|
||||
'version': '1.16.0'
|
||||
}
|
||||
return response.Response(data, status=200)
|
||||
|
||||
@list_route(
|
||||
methods=['get', 'post'],
|
||||
url_name='get_artists',
|
||||
url_path='getArtists')
|
||||
def get_artists(self, request, *args, **kwargs):
|
||||
artists = music_models.Artist.objects.all()
|
||||
data = serializers.GetArtistsSerializer(artists).data
|
||||
payload = {
|
||||
'artists': data
|
||||
}
|
||||
|
||||
return response.Response(payload, status=200)
|
||||
|
||||
@list_route(
|
||||
methods=['get', 'post'],
|
||||
url_name='get_artist',
|
||||
url_path='getArtist')
|
||||
@find_object(music_models.Artist.objects.all())
|
||||
def get_artist(self, request, *args, **kwargs):
|
||||
artist = kwargs.pop('obj')
|
||||
data = serializers.GetArtistSerializer(artist).data
|
||||
payload = {
|
||||
'artist': data
|
||||
}
|
||||
|
||||
return response.Response(payload, status=200)
|
||||
|
||||
@list_route(
|
||||
methods=['get', 'post'],
|
||||
url_name='get_album',
|
||||
url_path='getAlbum')
|
||||
@find_object(
|
||||
music_models.Album.objects.select_related('artist'))
|
||||
def get_album(self, request, *args, **kwargs):
|
||||
album = kwargs.pop('obj')
|
||||
data = serializers.GetAlbumSerializer(album).data
|
||||
payload = {
|
||||
'album': data
|
||||
}
|
||||
return response.Response(payload, status=200)
|
||||
|
||||
@list_route(
|
||||
methods=['get', 'post'],
|
||||
url_name='stream',
|
||||
url_path='stream')
|
||||
@find_object(
|
||||
music_models.Track.objects.all())
|
||||
def stream(self, request, *args, **kwargs):
|
||||
track = kwargs.pop('obj')
|
||||
queryset = track.files.select_related(
|
||||
'library_track',
|
||||
'track__album__artist',
|
||||
'track__artist',
|
||||
)
|
||||
track_file = queryset.first()
|
||||
if not track_file:
|
||||
return Response(status=404)
|
||||
return music_views.handle_serve(track_file)
|
Loading…
Add table
Add a link
Reference in a new issue