From 3a31248a3dabc6ef94bdcb74ca15003ec2266935 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Thu, 12 Apr 2018 23:03:26 +0200 Subject: [PATCH] Can now import library tracks from front-end --- api/funkwhale_api/federation/models.py | 7 + api/funkwhale_api/music/forms.py | 2 +- api/funkwhale_api/music/serializers.py | 25 ++- api/funkwhale_api/music/views.py | 23 +++ api/tests/music/test_views.py | 20 +++ .../federation/LibraryTrackTable.vue | 143 ++++++++++++++++++ front/src/views/federation/LibraryDetail.vue | 33 +++- 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 front/src/components/federation/LibraryTrackTable.vue diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 76dbfd1ad..e841b6394 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -163,3 +163,10 @@ class LibraryTrack(models.Model): title = models.CharField(max_length=500) metadata = JSONField( default={}, max_length=10000, encoder=DjangoJSONEncoder) + + @property + def mbid(self): + try: + return self.metadata['recording']['musicbrainz_id'] + except KeyError: + pass diff --git a/api/funkwhale_api/music/forms.py b/api/funkwhale_api/music/forms.py index 04e4bfe05..e68ab73cc 100644 --- a/api/funkwhale_api/music/forms.py +++ b/api/funkwhale_api/music/forms.py @@ -19,5 +19,5 @@ class TranscodeForm(forms.Form): choices=BITRATE_CHOICES, required=False) track_file = forms.ModelChoiceField( - queryset=models.TrackFile.objects.all() + queryset=models.TrackFile.objects.exclude(audio_file__isnull=True) ) diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py index 42795dbea..b5f69eb1d 100644 --- a/api/funkwhale_api/music/serializers.py +++ b/api/funkwhale_api/music/serializers.py @@ -3,8 +3,9 @@ from rest_framework import serializers from taggit.models import Tag from funkwhale_api.activity import serializers as activity_serializers -from funkwhale_api.federation.serializers import AP_CONTEXT from funkwhale_api.federation import utils as federation_utils +from funkwhale_api.federation.models import LibraryTrack +from funkwhale_api.federation.serializers import AP_CONTEXT from . import models @@ -153,3 +154,25 @@ class TrackActivitySerializer(activity_serializers.ModelSerializer): def get_type(self, obj): return 'Audio' + + +class SubmitFederationTracksSerializer(serializers.Serializer): + library_tracks = serializers.PrimaryKeyRelatedField( + many=True, + queryset=LibraryTrack.objects.filter(local_track_file__isnull=True), + ) + + @transaction.atomic + def save(self, **kwargs): + batch = models.ImportBatch.objects.create( + source='federation', + **kwargs + ) + for lt in self.validated_data['library_tracks']: + models.ImportJob.objects.create( + batch=batch, + library_track=lt, + mbid=lt.mbid, + source=lt.url, + ) + return batch diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 98048b41d..d5247fbf6 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -1,6 +1,7 @@ import ffmpeg import os import json +import logging import subprocess import unicodedata import urllib @@ -40,6 +41,8 @@ from . import serializers from . import tasks from . import utils +logger = logging.getLogger(__name__) + class SearchMixin(object): search_fields = [] @@ -223,6 +226,8 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet): headers={ 'Content-Type': 'application/activity+json' }) + logger.debug( + 'Proxying media request to %s', library_track.audio_url) response = StreamingHttpResponse(remote_response.iter_content()) else: response = Response() @@ -249,6 +254,8 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet): return Response(form.errors, status=400) f = form.cleaned_data['track_file'] + if not f.audio_file: + return Response(status=400) output_kwargs = { 'format': form.cleaned_data['to'] } @@ -392,6 +399,22 @@ class SubmitViewSet(viewsets.ViewSet): data, request, batch=None, import_request=import_request) return Response(import_data) + @list_route(methods=['post']) + @transaction.non_atomic_requests + def federation(self, request, *args, **kwargs): + serializer = serializers.SubmitFederationTracksSerializer( + data=request.data) + serializer.is_valid(raise_exception=True) + batch = serializer.save(submitted_by=request.user) + for job in batch.jobs.all(): + funkwhale_utils.on_commit( + tasks.import_job_run.delay, + import_job_id=job.pk, + use_acoustid=False, + ) + + return Response({'id': batch.id}, status=201) + @transaction.atomic def _import_album(self, data, request, batch=None, import_request=None): # we import the whole album here to prevent race conditions that occurs diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 468ea77e3..f18d18c86 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -1,6 +1,8 @@ import io import pytest +from django.urls import reverse + from funkwhale_api.music import views from funkwhale_api.federation import actors @@ -83,3 +85,21 @@ def test_can_proxy_remote_track( assert response.status_code == 200 assert list(response.streaming_content) == [b't', b'e', b's', b't'] assert response['Content-Type'] == track_file.library_track.audio_mimetype + + +def test_can_create_import_from_federation_tracks( + factories, superuser_api_client, mocker): + lts = factories['federation.LibraryTrack'].create_batch(size=5) + mocker.patch('funkwhale_api.music.tasks.import_job_run') + + payload = { + 'library_tracks': [l.pk for l in lts] + } + url = reverse('api:v1:submit-federation') + response = superuser_api_client.post(url, payload) + + assert response.status_code == 201 + batch = superuser_api_client.user.imports.latest('id') + assert batch.jobs.count() == 5 + for i, job in enumerate(batch.jobs.all()): + assert job.library_track == lts[i] diff --git a/front/src/components/federation/LibraryTrackTable.vue b/front/src/components/federation/LibraryTrackTable.vue new file mode 100644 index 000000000..dc6eb9d21 --- /dev/null +++ b/front/src/components/federation/LibraryTrackTable.vue @@ -0,0 +1,143 @@ + + + diff --git a/front/src/views/federation/LibraryDetail.vue b/front/src/views/federation/LibraryDetail.vue index d33fcc212..6d9ab2eeb 100644 --- a/front/src/views/federation/LibraryDetail.vue +++ b/front/src/views/federation/LibraryDetail.vue @@ -82,6 +82,16 @@ + + + @@ -91,6 +101,7 @@

Tracks available in this library

+
@@ -102,13 +113,19 @@ import axios from 'axios' import logger from '@/logging' +import LibraryTrackTable from '@/components/federation/LibraryTrackTable' + export default { props: ['id'], - components: {}, + components: { + LibraryTrackTable + }, data () { return { isLoading: true, - object: null + isScanLoading: false, + object: null, + scanTrigerred: false } }, created () { @@ -125,6 +142,18 @@ export default { self.isLoading = false }) }, + scan (until) { + var self = this + this.isScanLoading = true + let data = {} + let url = 'federation/libraries/' + this.id + '/scan/' + logger.default.debug('Triggering scan for library "' + this.id + '"') + axios.post(url, data).then((response) => { + self.scanTrigerred = true + logger.default.debug('Scan triggered with id', response.data) + self.isScanLoading = false + }) + }, update (attr) { let newValue = this.object[attr] let params = {}