from django import forms from django.conf import settings from django.core import paginator from django.http import HttpResponse from django.urls import reverse from rest_framework import permissions as rest_permissions from rest_framework import response from rest_framework import views from rest_framework import viewsets from rest_framework.decorators import list_route, detail_route from funkwhale_api.music.models import TrackFile from . import actors from . import authentication from . import library from . import models from . import permissions from . import renderers from . import serializers from . import utils from . import webfinger class FederationMixin(object): def dispatch(self, request, *args, **kwargs): if not settings.FEDERATION_ENABLED: return HttpResponse(status=405) return super().dispatch(request, *args, **kwargs) class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet): lookup_field = 'actor' lookup_value_regex = '[a-z]*' authentication_classes = [ authentication.SignatureAuthentication] permission_classes = [] renderer_classes = [renderers.ActivityPubRenderer] def get_object(self): try: return actors.SYSTEM_ACTORS[self.kwargs['actor']] except KeyError: raise Http404 def retrieve(self, request, *args, **kwargs): system_actor = self.get_object() actor = system_actor.get_actor_instance() data = actor.system_conf.serialize() return response.Response(data, status=200) @detail_route(methods=['get', 'post']) def inbox(self, request, *args, **kwargs): system_actor = self.get_object() handler = getattr(system_actor, '{}_inbox'.format( request.method.lower() )) try: data = handler(request.data, actor=request.actor) except NotImplementedError: return response.Response(status=405) return response.Response(data, status=200) @detail_route(methods=['get', 'post']) def outbox(self, request, *args, **kwargs): system_actor = self.get_object() handler = getattr(system_actor, '{}_outbox'.format( request.method.lower() )) try: data = handler(request.data, actor=request.actor) except NotImplementedError: return response.Response(status=405) return response.Response(data, status=200) class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet): authentication_classes = [] permission_classes = [] renderer_classes = [renderers.WebfingerRenderer] @list_route(methods=['get']) def webfinger(self, request, *args, **kwargs): try: resource_type, resource = webfinger.clean_resource( request.GET['resource']) cleaner = getattr(webfinger, 'clean_{}'.format(resource_type)) result = cleaner(resource) except forms.ValidationError as e: return response.Response({ 'errors': { 'resource': e.message } }, status=400) except KeyError: return response.Response({ 'errors': { 'resource': 'This field is required', } }, status=400) handler = getattr(self, 'handler_{}'.format(resource_type)) data = handler(result) return response.Response(data) def handler_acct(self, clean_result): username, hostname = clean_result actor = actors.SYSTEM_ACTORS[username].get_actor_instance() return serializers.ActorWebfingerSerializer(actor).data class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet): authentication_classes = [ authentication.SignatureAuthentication] permission_classes = [permissions.LibraryFollower] renderer_classes = [renderers.ActivityPubRenderer] def list(self, request, *args, **kwargs): page = request.GET.get('page') library = actors.SYSTEM_ACTORS['library'].get_actor_instance() qs = TrackFile.objects.order_by('-creation_date').select_related( 'track__artist', 'track__album__artist' ).filter(library_track__isnull=True) if page is None: conf = { 'id': utils.full_url(reverse('federation:music:files-list')), 'page_size': settings.FEDERATION_COLLECTION_PAGE_SIZE, 'items': qs, 'item_serializer': serializers.AudioSerializer, 'actor': library, } serializer = serializers.PaginatedCollectionSerializer(conf) data = serializer.data else: try: page_number = int(page) except: return response.Response( {'page': ['Invalid page number']}, status=400) p = paginator.Paginator( qs, settings.FEDERATION_COLLECTION_PAGE_SIZE) try: page = p.page(page_number) conf = { 'id': utils.full_url(reverse('federation:music:files-list')), 'page': page, 'item_serializer': serializers.AudioSerializer, 'actor': library, } serializer = serializers.CollectionPageSerializer(conf) data = serializer.data except paginator.EmptyPage: return response.Response(status=404) return response.Response(data) class LibraryViewSet(viewsets.GenericViewSet): permission_classes = [rest_permissions.DjangoModelPermissions] queryset = models.Library.objects.all() @list_route(methods=['get']) def scan(self, request, *args, **kwargs): account = request.GET.get('account') if not account: return response.Response( {'account': 'This field is mandatory'}, status=400) data = library.scan_from_account_name(account) return response.Response(data)