From 57bf43bb961c38e259688b830c5a407703221b85 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Thu, 12 Apr 2018 20:38:06 +0200
Subject: [PATCH] API Endpoint to list lirary tracks

---
 api/funkwhale_api/federation/api_urls.py    |  4 +++
 api/funkwhale_api/federation/filters.py     | 14 ++++++++
 api/funkwhale_api/federation/serializers.py | 23 +++++++++++++
 api/funkwhale_api/federation/tasks.py       |  4 +++
 api/funkwhale_api/federation/views.py       | 37 ++++++++++++++++++---
 api/tests/federation/test_tasks.py          |  4 +++
 api/tests/federation/test_views.py          | 19 ++++++++++-
 7 files changed, 100 insertions(+), 5 deletions(-)

diff --git a/api/funkwhale_api/federation/api_urls.py b/api/funkwhale_api/federation/api_urls.py
index ecb5c38f13..41dd1c0f99 100644
--- a/api/funkwhale_api/federation/api_urls.py
+++ b/api/funkwhale_api/federation/api_urls.py
@@ -7,5 +7,9 @@ router.register(
     r'libraries',
     views.LibraryViewSet,
     'libraries')
+router.register(
+    r'library-tracks',
+    views.LibraryTrackViewSet,
+    'library-tracks')
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/federation/filters.py b/api/funkwhale_api/federation/filters.py
index d76fe7ad0f..8166fe64db 100644
--- a/api/funkwhale_api/federation/filters.py
+++ b/api/funkwhale_api/federation/filters.py
@@ -17,6 +17,20 @@ class LibraryFilter(django_filters.FilterSet):
         }
 
 
+class LibraryTrackFilter(django_filters.FilterSet):
+    library = django_filters.CharFilter('library__uuid')
+
+    class Meta:
+        model = models.LibraryTrack
+        fields = {
+            'library': ['exact'],
+            'artist_name': ['exact', 'icontains'],
+            'title': ['exact', 'icontains'],
+            'album_title': ['exact', 'icontains'],
+            'audio_mimetype': ['exact', 'icontains'],
+        }
+
+
 class FollowFilter(django_filters.FilterSet):
     ordering = django_filters.OrderingFilter(
         # tuple-mapping retains order
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 25ed8d920e..e6ad0c0be0 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -253,6 +253,29 @@ class APILibraryCreateSerializer(serializers.ModelSerializer):
         return library
 
 
+class APILibraryTrackSerializer(serializers.ModelSerializer):
+    library = APILibrarySerializer()
+
+    class Meta:
+        model = models.LibraryTrack
+        fields = [
+            'id',
+            'url',
+            'audio_url',
+            'audio_mimetype',
+            'creation_date',
+            'modification_date',
+            'fetched_date',
+            'published_date',
+            'metadata',
+            'artist_name',
+            'album_title',
+            'title',
+            'library',
+            'local_track_file',
+        ]
+
+
 class FollowSerializer(serializers.Serializer):
     id = serializers.URLField()
     object = serializers.URLField()
diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py
index 5140eff6ed..c6a70174fe 100644
--- a/api/funkwhale_api/federation/tasks.py
+++ b/api/funkwhale_api/federation/tasks.py
@@ -2,6 +2,7 @@ import json
 import logging
 
 from django.conf import settings
+from django.utils import timezone
 
 from requests.exceptions import RequestException
 
@@ -58,6 +59,9 @@ def scan_library(library, until=None):
     data = lb.get_library_data(library.url)
     scan_library_page.delay(
         library_id=library.id, page_url=data['first'], until=until)
+    library.fetched_date = timezone.now()
+    library.tracks_count = data['totalItems']
+    library.save(update_fields=['fetched_date', 'tracks_count'])
 
 
 @celery.app.task(
diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py
index 1aaddf96d7..a3f02a3728 100644
--- a/api/funkwhale_api/federation/views.py
+++ b/api/funkwhale_api/federation/views.py
@@ -14,6 +14,7 @@ from rest_framework.decorators import list_route, detail_route
 from rest_framework.serializers import ValidationError
 
 from funkwhale_api.common import utils as funkwhale_utils
+from funkwhale_api.common.permissions import HasModelPermission
 from funkwhale_api.music.models import TrackFile
 
 from . import activity
@@ -166,12 +167,16 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
         return response.Response(data)
 
 
+class LibraryPermission(HasModelPermission):
+    model = models.Library
+
+
 class LibraryViewSet(
         mixins.RetrieveModelMixin,
         mixins.UpdateModelMixin,
         mixins.ListModelMixin,
         viewsets.GenericViewSet):
-    permission_classes = [rest_permissions.DjangoModelPermissions]
+    permission_classes = [LibraryPermission]
     queryset = models.Library.objects.all().select_related(
         'actor',
         'follow',
@@ -184,6 +189,7 @@ class LibraryViewSet(
         'creation_date',
         'fetched_date',
         'actor__domain',
+        'tracks_count',
     )
 
     @list_route(methods=['get'])
@@ -203,11 +209,11 @@ class LibraryViewSet(
             data=request.data
         )
         serializer.is_valid(raise_exception=True)
-        id = tasks.scan_library.delay(
+        result = tasks.scan_library.delay(
             library_id=library.pk,
-            until=serializer.validated_data['until']
+            until=serializer.validated_data.get('until')
         )
-        return response.Response({'task': id})
+        return response.Response({'task': result.id})
 
     @list_route(methods=['get'])
     def following(self, request, *args, **kwargs):
@@ -249,3 +255,26 @@ class LibraryViewSet(
         serializer.is_valid(raise_exception=True)
         library = serializer.save()
         return response.Response(serializer.data, status=201)
+
+
+class LibraryTrackViewSet(
+        mixins.ListModelMixin,
+        viewsets.GenericViewSet):
+    permission_classes = [LibraryPermission]
+    queryset = models.LibraryTrack.objects.all().select_related(
+        'library__actor',
+        'library__follow',
+        'local_track_file',
+    )
+    filter_class = filters.LibraryTrackFilter
+    serializer_class = serializers.APILibraryTrackSerializer
+    ordering_fields = (
+        'id',
+        'artist_name',
+        'title',
+        'album_title',
+        'creation_date',
+        'modification_date',
+        'fetched_date',
+        'published_date',
+    )
diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py
index 80f2365b6d..d164d62a68 100644
--- a/api/tests/federation/test_tasks.py
+++ b/api/tests/federation/test_tasks.py
@@ -1,4 +1,5 @@
 from django.core.paginator import Paginator
+from django.utils import timezone
 
 from funkwhale_api.federation import serializers
 from funkwhale_api.federation import tasks
@@ -21,6 +22,7 @@ def test_scan_library_page_does_nothing_if_federation_disabled(
 
 def test_scan_library_fetches_page_and_calls_scan_page(
         mocker, factories, r_mock):
+    now = timezone.now()
     library = factories['federation.Library'](federation_enabled=True)
     collection_conf = {
         'actor': library.actor,
@@ -39,6 +41,8 @@ def test_scan_library_fetches_page_and_calls_scan_page(
         page_url=collection.data['first'],
         until=None,
     )
+    library.refresh_from_db()
+    assert library.fetched_date > now
 
 
 def test_scan_page_fetches_page_and_creates_tracks(
diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py
index c54cc873ee..3e5bdf1a5e 100644
--- a/api/tests/federation/test_views.py
+++ b/api/tests/federation/test_views.py
@@ -312,7 +312,7 @@ def test_can_patch_library(factories, superuser_api_client):
 def test_scan_library(factories, mocker, superuser_api_client):
     scan = mocker.patch(
         'funkwhale_api.federation.tasks.scan_library.delay',
-        return_value='id')
+        return_value=mocker.Mock(id='id'))
     library = factories['federation.Library']()
     now = timezone.now()
     data = {
@@ -329,3 +329,20 @@ def test_scan_library(factories, mocker, superuser_api_client):
         library_id=library.pk,
         until=now
     )
+
+
+def test_list_library_tracks(factories, superuser_api_client):
+    library = factories['federation.Library']()
+    lts = list(reversed(factories['federation.LibraryTrack'].create_batch(
+        size=5, library=library)))
+    factories['federation.LibraryTrack'].create_batch(size=5)
+    url = reverse('api:v1:federation:library-tracks-list')
+    response = superuser_api_client.get(url, {'library': library.uuid})
+
+    assert response.status_code == 200
+    assert response.data == {
+        'results': serializers.APILibraryTrackSerializer(lts, many=True).data,
+        'count': 5,
+        'previous': None,
+        'next': None,
+    }
-- 
GitLab