import_files.py 5.03 KB
Newer Older
1
import glob
2
3
4
import os

from django.core.files import File
5
from django.core.management.base import BaseCommand, CommandError
6
7
8
from django.db import transaction

from funkwhale_api.common import utils
9
10
from funkwhale_api.music import tasks
from funkwhale_api.users.models import User
11
12
13
14
15
16
17
18
19
20
21
22
23
24


class Command(BaseCommand):
    help = 'Import audio files mathinc given glob pattern'

    def add_arguments(self, parser):
        parser.add_argument('path', type=str)
        parser.add_argument(
            '--recursive',
            action='store_true',
            dest='recursive',
            default=False,
            help='Will match the pattern recursively (including subdirectories)',
        )
25
26
27
28
29
        parser.add_argument(
            '--username',
            dest='username',
            help='The username of the user you want to be bound to the import',
        )
30
31
32
33
34
35
36
        parser.add_argument(
            '--async',
            action='store_true',
            dest='async',
            default=False,
            help='Will launch celery tasks for each file to import instead of doing it synchronously and block the CLI',
        )
37
38
39
40
41
42
43
        parser.add_argument(
            '--exit', '-x',
            action='store_true',
            dest='exit_on_failure',
            default=False,
            help='use this flag to disable error catching',
        )
44
45
46
47
48
49
50
        parser.add_argument(
            '--no-acoustid',
            action='store_true',
            dest='no_acoustid',
            default=False,
            help='Use this flag to completely bypass acoustid completely',
        )
51
52
53
54
55
56
57
        parser.add_argument(
            '--noinput', '--no-input', action='store_false', dest='interactive',
            help="Do NOT prompt the user for input of any kind.",
        )

    def handle(self, *args, **options):
        # self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
58
59
60
61
62
63
64
65
66
67
68

        # Recursive is supported only on Python 3.5+, so we pass the option
        # only if it's True to avoid breaking on older versions of Python
        glob_kwargs = {}
        if options['recursive']:
            glob_kwargs['recursive'] = True
        try:
            matching = glob.glob(options['path'], **glob_kwargs)
        except TypeError:
            raise Exception('You need Python 3.5 to use the --recursive flag')

69
70
71
72
73
74
        self.stdout.write('This will import {} files matching this pattern: {}'.format(
            len(matching), options['path']))

        if not matching:
            raise CommandError('No file matching pattern, aborting')

75
76
77
78
79
80
81
82
83
84
85
86
87
88
        user = None
        if options['username']:
            try:
                user = User.objects.get(username=options['username'])
            except User.DoesNotExist:
                raise CommandError('Invalid username')
        else:
            # we bind the import to the first registered superuser
            try:
                user = User.objects.filter(is_superuser=True).order_by('pk').first()
                assert user is not None
            except AssertionError:
                raise CommandError(
                    'No superuser available, please provide a --username')
89
90
91
92
93
94
95
96
        if options['interactive']:
            message = (
                'Are you sure you want to do this?\n\n'
                "Type 'yes' to continue, or 'no' to cancel: "
            )
            if input(''.join(message)) != 'yes':
                raise CommandError("Import cancelled.")

97
98
99
100
101
102
        batch = self.do_import(matching, user=user, options=options)
        message = 'Successfully imported {} tracks'
        if options['async']:
            message = 'Successfully launched import for {} tracks'
        self.stdout.write(message.format(len(matching)))
        self.stdout.write(
103
            "For details, please refer to import batch #{}".format(batch.pk))
104

105
    @transaction.atomic
106
    def do_import(self, matching, user, options):
107
108
109
110
        message = 'Importing {}...'
        if options['async']:
            message = 'Launching import for {}...'

111
112
113
        # we create an import batch binded to the user
        batch = user.imports.create(source='shell')
        async = options['async']
114
        import_handler = tasks.import_job_run.delay if async else tasks.import_job_run
115
116
        for path in matching:
            try:
117
118
                self.stdout.write(message.format(path))
                self.import_file(path, batch, import_handler, options)
119
            except Exception as e:
120
121
122
123
124
                if options['exit_on_failure']:
                    raise
                m = 'Error while importing {}: {} {}'.format(
                    path, e.__class__.__name__, e)
                self.stderr.write(m)
125
        return batch
126
127
128
129
130
131
132
133
134
135
136
137
138
139

    def import_file(self, path, batch, import_handler, options):
        job = batch.jobs.create(
            source='file://' + path,
        )
        name = os.path.basename(path)
        with open(path, 'rb') as f:
            job.audio_file.save(name, File(f))

        job.save()
        utils.on_commit(
            import_handler,
            import_job_id=job.pk,
            use_acoustid=not options['no_acoustid'])