diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py index d098279b282df59099c2d7913c9f180bbacaf4d1..5bc452886d7486bdc0ad7c1f5571ea69fc405895 100644 --- a/api/funkwhale_api/subsonic/serializers.py +++ b/api/funkwhale_api/subsonic/serializers.py @@ -72,7 +72,7 @@ def get_track_data(album, track, tf): 'title': track.title, 'album': album.title, 'artist': album.artist.name, - 'track': track.position, + 'track': track.position or 1, 'contentType': tf.mimetype, 'suffix': tf.extension or '', 'duration': tf.duration or 0, @@ -178,3 +178,38 @@ def get_playlist_detail_data(playlist): td = get_track_data(plt.track.album, plt.track, tf) data['entry'].append(td) return data + + +def get_music_directory_data(artist): + tracks = artist.tracks.select_related('album').prefetch_related('files') + data = { + 'id': artist.pk, + 'parent': 1, + 'name': artist.name, + 'child': [] + } + for track in tracks: + try: + tf = [tf for tf in track.files.all()][0] + except IndexError: + continue + album = track.album + td = { + 'id': track.pk, + 'isDir': 'false', + 'title': track.title, + 'album': album.title, + 'artist': artist.name, + 'track': track.position or 1, + 'year': track.album.release_date.year if track.album.release_date else 0, + 'contentType': tf.mimetype, + 'suffix': tf.extension or '', + 'duration': tf.duration or 0, + 'created': track.creation_date, + 'albumId': album.pk, + 'artistId': artist.pk, + 'parent': artist.id, + 'type': 'music', + } + data['child'].append(td) + return data diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index f428e81d95a4761d9416d3baa05626a1a34f2651..252afe81a3ba80d5fa5a473d659919cae82d18c4 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -125,6 +125,19 @@ class SubsonicViewSet(viewsets.GenericViewSet): return response.Response(payload, status=200) + @list_route( + methods=['get', 'post'], + url_name='get_indexes', + url_path='getIndexes') + def get_indexes(self, request, *args, **kwargs): + artists = music_models.Artist.objects.all() + data = serializers.GetArtistsSerializer(artists).data + payload = { + 'indexes': data + } + + return response.Response(payload, status=200) + @list_route( methods=['get', 'post'], url_name='get_artist', @@ -213,7 +226,22 @@ class SubsonicViewSet(viewsets.GenericViewSet): def get_starred2(self, request, *args, **kwargs): favorites = request.user.track_favorites.all() data = { - 'song': serializers.get_starred_tracks_data(favorites) + 'starred2': { + 'song': serializers.get_starred_tracks_data(favorites) + } + } + return response.Response(data) + + @list_route( + methods=['get', 'post'], + url_name='get_starred', + url_path='getStarred') + def get_starred(self, request, *args, **kwargs): + favorites = request.user.track_favorites.all() + data = { + 'starred': { + 'song': serializers.get_starred_tracks_data(favorites) + } } return response.Response(data) @@ -442,3 +470,18 @@ class SubsonicViewSet(viewsets.GenericViewSet): 'playlist': serializers.get_playlist_detail_data(playlist) } return response.Response(data) + + @list_route( + methods=['get', 'post'], + url_name='get_music_folders', + url_path='getMusicFolders') + def get_music_folders(self, request, *args, **kwargs): + data = { + 'musicFolders': { + 'musicFolder': [{ + 'id': 1, + 'name': 'Music' + }] + } + } + return response.Response(data) diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py index bb0e8407b3bd6cd560deb53bf2200d8262d5e8ee..6da9dd12e2e273643cf78ec0410535e77891fdad 100644 --- a/api/tests/subsonic/test_serializers.py +++ b/api/tests/subsonic/test_serializers.py @@ -173,3 +173,35 @@ def test_playlist_detail_serializer(factories): qs = playlist.__class__.objects.with_tracks_count() data = serializers.get_playlist_detail_data(qs.first()) assert data == expected + + +def test_directory_serializer_artist(factories): + track = factories['music.Track']() + tf = factories['music.TrackFile'](track=track) + album = track.album + artist = track.artist + + expected = { + 'id': artist.pk, + 'parent': 1, + 'name': artist.name, + 'child': [{ + 'id': track.pk, + 'isDir': 'false', + 'title': track.title, + 'album': album.title, + 'artist': artist.name, + 'track': track.position, + 'year': track.album.release_date.year, + 'contentType': tf.mimetype, + 'suffix': tf.extension or '', + 'duration': tf.duration or 0, + 'created': track.creation_date, + 'albumId': album.pk, + 'artistId': artist.pk, + 'parent': artist.pk, + 'type': 'music', + }] + } + data = serializers.get_music_directory_data(artist) + assert data == expected diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index 781c05331ce796f830780ee4caf8122f0ffe1aa7..b69be0d44a7e87dd4b4452bb3ad77213193f31f1 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -88,7 +88,7 @@ def test_get_artists(f, db, logged_in_api_client, factories): music_models.Artist.objects.all() ).data } - response = logged_in_api_client.get(url) + response = logged_in_api_client.get(url, {'f': f}) assert response.status_code == 200 assert response.data == expected @@ -197,7 +197,26 @@ def test_get_starred2(f, db, logged_in_api_client, factories): assert response.status_code == 200 assert response.data == { - 'song': serializers.get_starred_tracks_data([favorite]) + 'starred2': { + 'song': serializers.get_starred_tracks_data([favorite]) + } + } + + +@pytest.mark.parametrize('f', ['xml', 'json']) +def test_get_starred(f, db, logged_in_api_client, factories): + url = reverse('api:subsonic-get-starred') + assert url.endswith('getStarred') is True + track = factories['music.Track']() + favorite = factories['favorites.TrackFavorite']( + track=track, user=logged_in_api_client.user) + response = logged_in_api_client.get(url, {'f': f, 'id': track.pk}) + + assert response.status_code == 200 + assert response.data == { + 'starred': { + 'song': serializers.get_starred_tracks_data([favorite]) + } } @@ -333,3 +352,35 @@ def test_create_playlist(f, db, logged_in_api_client, factories): assert response.data == { 'playlist': serializers.get_playlist_detail_data(qs.first()) } + + +@pytest.mark.parametrize('f', ['xml', 'json']) +def test_get_music_folders(f, db, logged_in_api_client, factories): + url = reverse('api:subsonic-get-music-folders') + assert url.endswith('getMusicFolders') is True + response = logged_in_api_client.get(url, {'f': f}) + assert response.status_code == 200 + assert response.data == { + 'musicFolders': { + 'musicFolder': [{ + 'id': 1, + 'name': 'Music' + }] + } + } + + +@pytest.mark.parametrize('f', ['xml', 'json']) +def test_get_indexes(f, db, logged_in_api_client, factories): + url = reverse('api:subsonic-get-indexes') + assert url.endswith('getIndexes') is True + artists = factories['music.Artist'].create_batch(size=10) + expected = { + 'indexes': serializers.GetArtistsSerializer( + music_models.Artist.objects.all() + ).data + } + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data == expected