Skip to content
Snippets Groups Projects
migrate_to_user_libraries.py 5.49 KiB
Newer Older
  • Learn to ignore specific revisions
  • """
    Mirate instance files to a library #463. For each user that imported music on an
    instance, we will create a "default" library with related files and an instance-level
    
    visibility (unless instance has common__api_authentication_required set to False,
    in which case the libraries will be public).
    
    
    Files without any import job will be bounded to a "default" library on the first
    superuser account found. This should now happen though.
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
    This command will also generate federation ids for existing resources.
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
    from django.conf import settings
    from django.db.models import functions, CharField, F, Value
    
    
    from funkwhale_api.music import models
    from funkwhale_api.users.models import User
    
    from funkwhale_api.federation import models as federation_models
    from funkwhale_api.common import preferences
    
    def create_libraries(open_api, stdout):
        local_actors = federation_models.Actor.objects.exclude(user=None).only("pk", "user")
        privacy_level = "everyone" if open_api else "instance"
        stdout.write(
            "* Creating {} libraries with {} visibility".format(
                len(local_actors), privacy_level
            )
    
        libraries_by_user = {}
    
        for a in local_actors:
            library, created = models.Library.objects.get_or_create(
                name="default", actor=a, defaults={"privacy_level": privacy_level}
            )
            libraries_by_user[library.actor.user.pk] = library.pk
            if created:
                stdout.write(
                    "  * Created library {} for user {}".format(library.pk, a.user.pk)
                )
            else:
                stdout.write(
                    "  * Found existing library {} for user {}".format(
                        library.pk, a.user.pk
                    )
                )
    
        return libraries_by_user
    
    
    def update_uploads(libraries_by_user, stdout):
        stdout.write("* Updating uploads with proper libraries...")
        for user_id, library_id in libraries_by_user.items():
            jobs = models.ImportJob.objects.filter(
                upload__library=None, batch__submitted_by=user_id
            )
            candidates = models.Upload.objects.filter(
                pk__in=jobs.values_list("upload", flat=True)
            )
            total = candidates.update(library=library_id, import_status="finished")
            if total:
                stdout.write(
                    "  * Assigned {} uploads to user {}'s library".format(total, user_id)
                )
            else:
                stdout.write(
                    "  * No uploads to assign to user {}'s library".format(user_id)
                )
    
    
    def update_orphan_uploads(open_api, stdout):
        privacy_level = "everyone" if open_api else "instance"
    
        first_superuser = (
            User.objects.filter(is_superuser=True)
            .exclude(actor=None)
            .order_by("pk")
            .first()
        )
        if not first_superuser:
            stdout.write("* No superuser found, skipping update orphan uploads")
            return
    
        library, _ = models.Library.objects.get_or_create(
            name="default",
            actor=first_superuser.actor,
            defaults={"privacy_level": privacy_level},
    
        candidates = (
            models.Upload.objects.filter(library=None, jobs__isnull=True)
            .exclude(audio_file=None)
            .exclude(audio_file="")
    
    
        total = candidates.update(library=library, import_status="finished")
        if total:
            stdout.write(
                "* Assigned {} orphaned uploads to superuser {}".format(
                    total, first_superuser.pk
                )
    
        else:
            stdout.write("* No orphaned uploads found")
    
    
    def set_fid(queryset, path, stdout):
        model = queryset.model._meta.label
        qs = queryset.filter(fid=None)
        base_url = "{}{}".format(settings.FUNKWHALE_URL, path)
        stdout.write(
            "* Assigning federation ids to {} entries (path: {})".format(model, base_url)
    
        new_fid = functions.Concat(Value(base_url), F("uuid"), output_field=CharField())
        total = qs.update(fid=new_fid)
    
        stdout.write("  * {} entries updated".format(total))
    
    
    def update_shared_inbox_url(stdout):
        stdout.write("* Update shared inbox url for local actors...")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        candidates = federation_models.Actor.objects.local()
    
        url = federation_models.get_shared_inbox_url()
        candidates.update(shared_inbox_url=url)
    
    def generate_actor_urls(part, stdout):
        field = "{}_url".format(part)
        stdout.write("* Update {} for local actors...".format(field))
    
        queryset = federation_models.Actor.objects.local().filter(**{field: None})
        base_url = "{}/federation/actors/".format(settings.FUNKWHALE_URL)
    
        new_field = functions.Concat(
            Value(base_url),
            F("preferred_username"),
            Value("/{}".format(part)),
            output_field=CharField(),
    
    
        queryset.update(**{field: new_field})
    
    
    def main(command, **kwargs):
        open_api = not preferences.get("common__api_authentication_required")
        libraries_by_user = create_libraries(open_api, command.stdout)
        update_uploads(libraries_by_user, command.stdout)
        update_orphan_uploads(open_api, command.stdout)
    
        set_fid_params = [
            (
                models.Upload.objects.exclude(library__actor__user=None),
                "/federation/music/uploads/",
            ),
            (models.Artist.objects.all(), "/federation/music/artists/"),
            (models.Album.objects.all(), "/federation/music/albums/"),
            (models.Track.objects.all(), "/federation/music/tracks/"),
        ]
        for qs, path in set_fid_params:
            set_fid(qs, path, command.stdout)
    
        update_shared_inbox_url(command.stdout)
    
        for part in ["followers", "following"]:
            generate_actor_urls(part, command.stdout)