From c046787cb34f964c2fb3014a4c46581d58c74350 Mon Sep 17 00:00:00 2001 From: ealgase Date: Sun, 9 Jun 2019 21:54:18 +0200 Subject: [PATCH 1/9] Fix websockets reverse proxy --- deploy/apache.conf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/apache.conf b/deploy/apache.conf index e1409a627..3b34dcdba 100644 --- a/deploy/apache.conf +++ b/deploy/apache.conf @@ -64,11 +64,6 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music Allow from all - # Activating WebSockets - - ProxyPass ${funkwhale-api-ws}/api/v1/activity - - # similar to nginx 'client_max_body_size 100M;' LimitRequestBody 104857600 @@ -107,6 +102,11 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music Alias /staticfiles /srv/funkwhale/data/static + # Activating WebSockets + + ProxyPass ${funkwhale-api-ws}/api/v1/activity + + # Setting appropriate access levels to serve frontend Options FollowSymLinks From bad39e0974763724319511fc78a4d8e4edb68739 Mon Sep 17 00:00:00 2001 From: Creak Date: Sat, 8 Jun 2019 21:03:18 +0200 Subject: [PATCH 2/9] Fix French translation for "Start radio" --- changes/changelog.d/849.bugfix | 1 + front/locales/fr_FR/LC_MESSAGES/app.po | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/changelog.d/849.bugfix diff --git a/changes/changelog.d/849.bugfix b/changes/changelog.d/849.bugfix new file mode 100644 index 000000000..61f1c2a2e --- /dev/null +++ b/changes/changelog.d/849.bugfix @@ -0,0 +1 @@ +Fixed issue with French translation for "Start radio" (#849) diff --git a/front/locales/fr_FR/LC_MESSAGES/app.po b/front/locales/fr_FR/LC_MESSAGES/app.po index c93e5d43f..bb9db8d34 100644 --- a/front/locales/fr_FR/LC_MESSAGES/app.po +++ b/front/locales/fr_FR/LC_MESSAGES/app.po @@ -3986,7 +3986,7 @@ msgstr "Membre de l'équipe" #: src/components/radios/Button.vue:4 msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Start radio" -msgstr "Arrêter la radio" +msgstr "Démarrer la radio" #: front/src/views/admin/Settings.vue:86 msgctxt "Content/Admin/Menu" From 024addfb4b73c9b492614e33eaeff832387051e8 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Fri, 7 Jun 2019 16:41:50 +0200 Subject: [PATCH 3/9] Added username and domain filter on /history/listenings endpoint --- api/funkwhale_api/history/filters.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/funkwhale_api/history/filters.py b/api/funkwhale_api/history/filters.py index 30bc78f6a..02549b3b1 100644 --- a/api/funkwhale_api/history/filters.py +++ b/api/funkwhale_api/history/filters.py @@ -1,9 +1,14 @@ +import django_filters + from funkwhale_api.moderation import filters as moderation_filters from . import models class ListeningFilter(moderation_filters.HiddenContentFilterSet): + username = django_filters.CharFilter("user__username") + domain = django_filters.CharFilter("user__actor__domain_id") + class Meta: model = models.Listening hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[ From ae8df9b03ebdfd5eb4e6dded61aa67e7838371b7 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Thu, 6 Jun 2019 13:53:05 +0200 Subject: [PATCH 4/9] Added some documentation about translations lifecycle --- TRANSLATORS.rst | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/TRANSLATORS.rst b/TRANSLATORS.rst index 5e2cf51f7..7fea7e399 100644 --- a/TRANSLATORS.rst +++ b/TRANSLATORS.rst @@ -1,6 +1,8 @@ Translating Funkwhale ===================== +A step-by-step, beginner friendly guide is available at https://contribute.funkwhale.audio/guides/translate/ + Thank you for reading this! If you want to help translate Funkwhale, you found the proper place :) @@ -21,8 +23,41 @@ Respecting those guidelines is mandatory if you want your translation to be incl - Use gender-neutral language and wording +Submitting a new language +------------------------- + +1. Pull the latest version of ``develop`` +2. Create a new branch, e.g ``git checkout -b translations-new-fr-ca`` +3. Add your new language code and name in ``front/src/locales.js``. Use the native language name, as it is what appears in the UI selector. +4. Create the ``po`` file from template: + +.. code-block:: shell + + export LOCALE=fr_CA # replace with your actual locale code + mkdir -p front/locales/$LOCALE/LC_MESSAGES + msginit --no-wrap --no-translator --locale=$LOCALE --input=front/locales/app.pot --output-file=front/locales/$LOCALE/LC_MESSAGES/app.po + +5. Then commit your changes, push, and submit a pull request on the ``develop`` branch + Requesting a new language ------------------------- -If you'd like to see a new language in Funkwhale, please open an issue here: +If you cannot submit a new language yourself, you can request it by opening an issue here: https://dev.funkwhale.audio/funkwhale/funkwhale/issues + +Extracting messages from source +------------------------------- + +We offer a script to update existing ``po`` and ``pot`` files with new translations +from the source code. This action should be run regularly, and in particular before +lots of translation work is expected (e.g a few weeks before a new release), or when +the UI code changes a lot. Otherwise, translators end up translating some obsolete messages, +or not translationg new messages. + +1. `Lock the translations on weblate `_ (``Lock`` button in the sidebar). This will prevent translators from working, and help prevent potential conflicts in the source code +2. `Commit and push changes from weblate `_ (``Commit`` and ``Push`` buttons in the sidebar) +3. Pull ``develop`` in your local git repository to ensure you have the latest version of the translations +4. Create a dedicated branch with ``git checkout -b translations-integration`` +5. Extract the translations with ``cd front && ./scripts/i18n-extract.sh``. This will update all ``po`` files as necessary +6. Review, commit and push the changes, then open a merge request on the ``develop`` branch +7. When the MR is merged, `Unlock the translations on weblate `_ (``Unlock`` button in the sidebar). From 3ce247763c12a67efbe0510b2e236af7d730ce83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Thu, 23 May 2019 19:18:37 +0100 Subject: [PATCH 5/9] Added fix + changelog fragment for #838 --- changes/changelog.d/838.bugfix | 1 + front/src/components/audio/Player.vue | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/changelog.d/838.bugfix diff --git a/changes/changelog.d/838.bugfix b/changes/changelog.d/838.bugfix new file mode 100644 index 000000000..339489756 --- /dev/null +++ b/changes/changelog.d/838.bugfix @@ -0,0 +1 @@ +Fixed issue with player changing height when hovering over the volume slider (#838) diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index ea15ebc87..8faaf670c 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -199,7 +199,6 @@ @click.prevent.stop="clean()" class="two wide column control"> - @@ -926,6 +925,11 @@ export default { animation-timing-function: linear; animation-iteration-count: infinite; } + +.icons { + position: absolute; +} + i.icons .corner.icon { font-size: 1em; right: -0.3em; From 59a3be1411d432d44cb21576d92824791e0aa10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Thu, 23 May 2019 10:37:16 +0200 Subject: [PATCH 6/9] Resolve "Add documentation for resolver conf" --- docs/admin/external-storages.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/admin/external-storages.rst b/docs/admin/external-storages.rst index c8dbbe963..bd136df62 100644 --- a/docs/admin/external-storages.rst +++ b/docs/admin/external-storages.rst @@ -115,3 +115,29 @@ If you are using ``awscli``, you can store this policy in a ``/tmp/policy`` file apply it using the following command:: aws s3api put-bucket-policy --bucket --policy file:///tmp/policy + +Troubleshooting +*************** + +No Resolver Found +^^^^^^^^^^^^^^^^^ + +Depending on your setup, you may experience the following issue when trying to stream +music directly from your S3-compatible store. + +.. code-block:: shell + + [error] 2832#2832: *1 no resolver defined to resolve [address] client: [IP], server: [servername], request: "GET API request", host: "[your_domain]", referrer: "[your_domain/library]" + +This happpens when the nginx config is unable to use your server's DNS resolver. This issue +is still under investigation, but in the meantime can be worked around by specifying a resolver +in your ``funkwhale.template`` under the ``location ~/_protected/media/(.+)`` section. + +.. code-block:: shell + + location ~ /_protected/media/(.+) { + resolver 1.1.1.1; + internal; + proxy_pass $1; + } + From c8ee67e7543c6893d5c1216c2d67a2b279cf12f7 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Mon, 10 Jun 2019 11:05:07 +0200 Subject: [PATCH 7/9] Fix #850: Ensure empty but optional fields in file metadata don't error during import --- api/funkwhale_api/music/metadata.py | 45 ++++++++++++++++++++++++----- api/tests/music/test_metadata.py | 27 +++++++++++++++++ changes/changelog.d/850.bugfix | 1 + 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 changes/changelog.d/850.bugfix diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py index 387b8ffe7..55044dbc7 100644 --- a/api/funkwhale_api/music/metadata.py +++ b/api/funkwhale_api/music/metadata.py @@ -481,14 +481,26 @@ class PermissiveDateField(serializers.CharField): return None +class MBIDField(serializers.UUIDField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("allow_null", True) + kwargs.setdefault("required", False) + super().__init__(*args, **kwargs) + + def to_internal_value(self, v): + if v in ["", None]: + return None + return super().to_internal_value(v) + + class ArtistSerializer(serializers.Serializer): name = serializers.CharField() - mbid = serializers.UUIDField(required=False, allow_null=True) + mbid = MBIDField() class AlbumSerializer(serializers.Serializer): title = serializers.CharField() - mbid = serializers.UUIDField(required=False, allow_null=True) + mbid = MBIDField() release_date = PermissiveDateField(required=False, allow_null=True) @@ -512,16 +524,35 @@ class PositionField(serializers.CharField): class TrackMetadataSerializer(serializers.Serializer): title = serializers.CharField() - position = PositionField(allow_null=True, required=False) - disc_number = PositionField(allow_null=True, required=False) - copyright = serializers.CharField(allow_null=True, required=False) - license = serializers.CharField(allow_null=True, required=False) - mbid = serializers.UUIDField(allow_null=True, required=False) + position = PositionField(allow_blank=True, allow_null=True, required=False) + disc_number = PositionField(allow_blank=True, allow_null=True, required=False) + copyright = serializers.CharField(allow_blank=True, allow_null=True, required=False) + license = serializers.CharField(allow_blank=True, allow_null=True, required=False) + mbid = MBIDField() album = AlbumField() artists = ArtistField() cover_data = CoverDataField() + remove_blank_null_fields = [ + "copyright", + "license", + "position", + "disc_number", + "mbid", + ] + + def validate(self, validated_data): + validated_data = super().validate(validated_data) + for field in self.remove_blank_null_fields: + try: + v = validated_data[field] + except KeyError: + continue + if v in ["", None]: + validated_data.pop(field) + return validated_data + class FakeMetadata(Mapping): def __init__(self, data, picture=None): diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py index 1656ece49..52c4e3026 100644 --- a/api/tests/music/test_metadata.py +++ b/api/tests/music/test_metadata.py @@ -539,6 +539,33 @@ def test_serializer_album_artist_missing(): assert serializer.validated_data == expected +@pytest.mark.parametrize( + "field_name", ["copyright", "license", "mbid", "position", "disc_number"] +) +def test_serializer_empty_fields(field_name): + data = { + "title": "Track Title", + "artist": "Track Artist", + "album": "Track Album", + # empty copyright/license field shouldn't fail, cf #850 + field_name: "", + } + expected = { + "title": "Track Title", + "artists": [{"name": "Track Artist", "mbid": None}], + "album": { + "title": "Track Album", + "mbid": None, + "release_date": None, + "artists": [], + }, + "cover_data": None, + } + serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data)) + assert serializer.is_valid(raise_exception=True) is True + assert serializer.validated_data == expected + + def test_artist_field_featuring(): data = { "artist": "Santana feat. Chris Cornell", diff --git a/changes/changelog.d/850.bugfix b/changes/changelog.d/850.bugfix new file mode 100644 index 000000000..0e26ce773 --- /dev/null +++ b/changes/changelog.d/850.bugfix @@ -0,0 +1 @@ +Ensure empty but optional fields in file metadata don't error during import (#850) From bb5e5460c4ca51cac5b9bb4216a7ddec0cdbf7ef Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Mon, 10 Jun 2019 11:55:09 +0200 Subject: [PATCH 8/9] Fix #848: Fixed invalid file extension for transcoded tracks --- api/funkwhale_api/music/models.py | 9 ++++++++- api/funkwhale_api/music/views.py | 8 ++++++-- api/tests/music/test_views.py | 7 +++++-- changes/changelog.d/848.bugfix | 1 + 4 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 changes/changelog.d/848.bugfix diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 309eb1266..cf6a0b7ba 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -870,7 +870,14 @@ class UploadVersion(models.Model): @property def filename(self): - return self.upload.filename + try: + return ( + self.upload.track.full_name + + "." + + utils.MIMETYPE_TO_EXTENSION[self.mimetype] + ) + except KeyError: + return self.upload.filename @property def audio_file_path(self): diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 391a4b333..4f8f35ced 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -285,6 +285,11 @@ def should_transcode(upload, format, max_bitrate=None): return format_need_transcoding or bitrate_need_transcoding +def get_content_disposition(filename): + filename = "filename*=UTF-8''{}".format(urllib.parse.quote(filename)) + return "attachment; {}".format(filename) + + def handle_serve(upload, user, format=None, max_bitrate=None, proxy_media=True): f = upload # we update the accessed_date @@ -342,8 +347,7 @@ def handle_serve(upload, user, format=None, max_bitrate=None, proxy_media=True): mapping = {"nginx": "X-Accel-Redirect", "apache2": "X-Sendfile"} file_header = mapping[settings.REVERSE_PROXY_TYPE] response[file_header] = file_path - filename = "filename*=UTF-8''{}".format(urllib.parse.quote(filename)) - response["Content-Disposition"] = "attachment; {}".format(filename) + response["Content-Disposition"] = get_content_disposition(filename) if mt: response["Content-Type"] = mt diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 102b5a790..25845e738 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -1,6 +1,7 @@ import io import magic import os +import urllib.parse import pytest from django.urls import reverse @@ -412,7 +413,7 @@ def test_handle_serve_create_mp3_version(factories, now): user = factories["users.User"]() upload = factories["music.Upload"](bitrate=42) response = views.handle_serve(upload, user, format="mp3") - + expected_filename = upload.track.full_name + ".mp3" version = upload.versions.latest("id") assert version.mimetype == "audio/mpeg" @@ -421,7 +422,9 @@ def test_handle_serve_create_mp3_version(factories, now): assert version.audio_file_path.endswith(".mp3") assert version.size == version.audio_file.size assert magic.from_buffer(version.audio_file.read(), mime=True) == "audio/mpeg" - + assert response["Content-Disposition"] == "attachment; filename*=UTF-8''{}".format( + urllib.parse.quote(expected_filename) + ) assert response.status_code == 200 diff --git a/changes/changelog.d/848.bugfix b/changes/changelog.d/848.bugfix new file mode 100644 index 000000000..478a8d42c --- /dev/null +++ b/changes/changelog.d/848.bugfix @@ -0,0 +1 @@ +Fixed invalid file extension for transcoded tracks (#848) From ce4a6b0412545d08940a420e25af3efc1470d662 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Mon, 10 Jun 2019 12:06:29 +0200 Subject: [PATCH 9/9] Fix #851: wrong og:image url when using S3 storage --- api/funkwhale_api/common/utils.py | 3 +++ api/tests/common/test_utils.py | 14 ++++++++++++++ changes/changelog.d/851.bugfix | 1 + 3 files changed, 18 insertions(+) create mode 100644 changes/changelog.d/851.bugfix diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py index 57bcba932..7763e9b7f 100644 --- a/api/funkwhale_api/common/utils.py +++ b/api/funkwhale_api/common/utils.py @@ -113,6 +113,9 @@ def chunk_queryset(source_qs, chunk_size): def join_url(start, end): + if end.startswith("http://") or end.startswith("https://"): + # alread a full URL, joining makes no sense + return end if start.endswith("/") and end.startswith("/"): return start + end[1:] diff --git a/api/tests/common/test_utils.py b/api/tests/common/test_utils.py index ea64ed9d2..74a3d0bca 100644 --- a/api/tests/common/test_utils.py +++ b/api/tests/common/test_utils.py @@ -85,3 +85,17 @@ def test_get_updated_fields(conf, mock_args, data, expected, mocker): obj = mocker.Mock(**mock_args) assert utils.get_updated_fields(conf, data, obj) == expected + + +@pytest.mark.parametrize( + "start, end, expected", + [ + ("https://domain", "/api", "https://domain/api"), + ("https://domain/", "/api", "https://domain/api"), + ("https://domain", "api", "https://domain/api"), + ("https://domain", "https://api", "https://api"), + ("https://domain", "http://api", "http://api"), + ], +) +def test_join_url(start, end, expected): + assert utils.join_url(start, end) == expected diff --git a/changes/changelog.d/851.bugfix b/changes/changelog.d/851.bugfix new file mode 100644 index 000000000..e6866b3e1 --- /dev/null +++ b/changes/changelog.d/851.bugfix @@ -0,0 +1 @@ +Fixed wrong og:image url when using S3 storage (#851)