diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 4759d7aab2b51b76281782498b47e510bda168c2..a445e102cfc5ef07487379243cf089db0217d0c6 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -412,7 +412,12 @@ CELERY_BEAT_SCHEDULE = { "task": "federation.clean_music_cache", "schedule": crontab(hour="*/2"), "options": {"expires": 60 * 2}, - } + }, + "music.clean_transcoding_cache": { + "task": "music.clean_transcoding_cache", + "schedule": crontab(hour="*"), + "options": {"expires": 60 * 2}, + }, } JWT_AUTH = { diff --git a/api/funkwhale_api/music/dynamic_preferences_registry.py b/api/funkwhale_api/music/dynamic_preferences_registry.py index d41225775ba4f4b93a22bf91075063be4ba51366..0a7d781aa65f523ff0da13d8677216709d930f8c 100644 --- a/api/funkwhale_api/music/dynamic_preferences_registry.py +++ b/api/funkwhale_api/music/dynamic_preferences_registry.py @@ -17,3 +17,18 @@ class MaxTracks(types.BooleanPreference): "load on the server." ) default = True + + +@global_preferences_registry.register +class MusicCacheDuration(types.IntPreference): + show_in_api = True + section = music + name = "transcoding_cache_duration" + default = 60 * 24 * 7 + verbose_name = "Transcoding cache duration" + help_text = ( + "How much minutes do you want to keep a copy of transcoded tracks" + "locally? Transcoded files that were not listened in this interval " + "will be erased and retranscoded from the remote on the next listening." + ) + field_kwargs = {"required": False} diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py index 9571f978516535b357665a6672235be8abbd3b47..0ec3fdb227a61ab77c88d994fb3a66e495495554 100644 --- a/api/funkwhale_api/music/factories.py +++ b/api/funkwhale_api/music/factories.py @@ -95,6 +95,18 @@ class UploadFactory(factory.django.DjangoModelFactory): ) +@registry.register +class UploadVersionFactory(factory.django.DjangoModelFactory): + upload = factory.SubFactory(UploadFactory, bitrate=200000) + bitrate = factory.SelfAttribute("upload.bitrate") + mimetype = "audio/mpeg" + audio_file = factory.django.FileField() + size = 2000000 + + class Meta: + model = "music.UploadVersion" + + @registry.register class WorkFactory(factory.django.DjangoModelFactory): mbid = factory.Faker("uuid4") diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py index d96471b961c5fcfe0efa5bf1809f0e93f7106fb8..7008b12fc51ebc094b69876f4823ee1ab5c94261 100644 --- a/api/funkwhale_api/music/tasks.py +++ b/api/funkwhale_api/music/tasks.py @@ -1,4 +1,5 @@ import collections +import datetime import logging import os @@ -10,7 +11,7 @@ from django.dispatch import receiver from musicbrainzngs import ResponseError from requests.exceptions import RequestException -from funkwhale_api.common import channels +from funkwhale_api.common import channels, preferences from funkwhale_api.federation import routes from funkwhale_api.federation import library as lb from funkwhale_api.taskapp import celery @@ -526,3 +527,19 @@ def broadcast_import_status_update_to_owner(old_status, new_status, upload, **kw }, }, ) + + +@celery.app.task(name="music.clean_transcoding_cache") +def clean_transcoding_cache(): + delay = preferences.get("music__transcoding_cache_duration") + if delay < 1: + return # cache clearing disabled + limit = timezone.now() - datetime.timedelta(minutes=delay) + candidates = ( + models.UploadVersion.objects.filter( + (Q(accessed_date__lt=limit) | Q(accessed_date=None)) + ) + .only("audio_file", "id") + .order_by("id") + ) + return candidates.delete() diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py index efa0e801f42dfe6c785a35ca4ee70d63dfd8be82..10d9fba7517dcaba213f8b4b40c8cb340a49fa4a 100644 --- a/api/tests/music/test_tasks.py +++ b/api/tests/music/test_tasks.py @@ -546,3 +546,20 @@ def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock scan.refresh_from_db() assert scan.status == "finished" + + +def test_clean_transcoding_cache(preferences, now, factories): + preferences['music__transcoding_cache_duration'] = 60 + u1 = factories['music.UploadVersion']( + accessed_date=now - datetime.timedelta(minutes=61) + ) + u2 = factories['music.UploadVersion']( + accessed_date=now - datetime.timedelta(minutes=59) + ) + + tasks.clean_transcoding_cache() + + u2.refresh_from_db() + + with pytest.raises(u1.__class__.DoesNotExist): + u1.refresh_from_db() \ No newline at end of file