Updated CLI to support in-place import
This commit is contained in:
		
							parent
							
								
									a8bf44a494
								
							
						
					
					
						commit
						de754b835e
					
				| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import glob
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.files import File
 | 
			
		||||
from django.core.management.base import BaseCommand, CommandError
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +39,20 @@ class Command(BaseCommand):
 | 
			
		|||
            action='store_true',
 | 
			
		||||
            dest='exit_on_failure',
 | 
			
		||||
            default=False,
 | 
			
		||||
            help='use this flag to disable error catching',
 | 
			
		||||
            help='Use this flag to disable error catching',
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--in-place', '-i',
 | 
			
		||||
            action='store_true',
 | 
			
		||||
            dest='in_place',
 | 
			
		||||
            default=False,
 | 
			
		||||
            help=(
 | 
			
		||||
                'Import files without duplicating them into the media directory.'
 | 
			
		||||
                'For in-place import to work, the music files must be readable'
 | 
			
		||||
                'by the web-server and funkwhale api and celeryworker processes.'
 | 
			
		||||
                'You may want to use this if you have a big music library to '
 | 
			
		||||
                'import and not much disk space available.'
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--no-acoustid',
 | 
			
		||||
| 
						 | 
				
			
			@ -53,10 +67,6 @@ class Command(BaseCommand):
 | 
			
		|||
        )
 | 
			
		||||
 | 
			
		||||
    def handle(self, *args, **options):
 | 
			
		||||
        # self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
 | 
			
		||||
 | 
			
		||||
        # 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
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +75,21 @@ class Command(BaseCommand):
 | 
			
		|||
        except TypeError:
 | 
			
		||||
            raise Exception('You need Python 3.5 to use the --recursive flag')
 | 
			
		||||
 | 
			
		||||
        if options['in_place']:
 | 
			
		||||
            self.stdout.write(
 | 
			
		||||
                'Checking imported paths against settings.MUSIC_DIRECTORY_PATH')
 | 
			
		||||
            p = settings.MUSIC_DIRECTORY_PATH
 | 
			
		||||
            if not p:
 | 
			
		||||
                raise CommandError(
 | 
			
		||||
                    'Importing in-place requires setting the '
 | 
			
		||||
                    'MUSIC_DIRECTORY_PATH variable')
 | 
			
		||||
            for m in matching:
 | 
			
		||||
                if not m.startswith(p):
 | 
			
		||||
                    raise CommandError(
 | 
			
		||||
                        'Importing in-place only works if importing'
 | 
			
		||||
                        'from {} (MUSIC_DIRECTORY_PATH), as this directory'
 | 
			
		||||
                        'needs to be accessible by the webserver.'
 | 
			
		||||
                        'Culprit: {}'.format(p, m))
 | 
			
		||||
        if not matching:
 | 
			
		||||
            raise CommandError('No file matching pattern, aborting')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +117,10 @@ class Command(BaseCommand):
 | 
			
		|||
        self.stdout.write('- {} new files'.format(
 | 
			
		||||
            len(filtered['new'])))
 | 
			
		||||
 | 
			
		||||
        self.stdout.write('Selected options: {}'.format(', '.join([
 | 
			
		||||
            'no acoustid' if options['no_acoustid'] else 'use acoustid',
 | 
			
		||||
            'in place' if options['in_place'] else 'copy music files',
 | 
			
		||||
        ])))
 | 
			
		||||
        if len(filtered['new']) == 0:
 | 
			
		||||
            self.stdout.write('Nothing new to import, exiting')
 | 
			
		||||
            return
 | 
			
		||||
| 
						 | 
				
			
			@ -164,11 +193,12 @@ class Command(BaseCommand):
 | 
			
		|||
        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))
 | 
			
		||||
        if not options['in_place']:
 | 
			
		||||
            name = os.path.basename(path)
 | 
			
		||||
            with open(path, 'rb') as f:
 | 
			
		||||
                job.audio_file.save(name, File(f))
 | 
			
		||||
 | 
			
		||||
        job.save()
 | 
			
		||||
            job.save()
 | 
			
		||||
        import_handler(
 | 
			
		||||
            import_job_id=job.pk,
 | 
			
		||||
            use_acoustid=not options['no_acoustid'])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,6 +58,20 @@ def test_management_command_requires_a_valid_username(factories, mocker):
 | 
			
		|||
    call_command('import_files', path, username='me', interactive=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_in_place_import_only_from_music_dir(factories, settings):
 | 
			
		||||
    user = factories['users.User'](username='me')
 | 
			
		||||
    settings.MUSIC_DIRECTORY_PATH = '/nope'
 | 
			
		||||
    path = os.path.join(DATA_DIR, 'dummy_file.ogg')
 | 
			
		||||
    with pytest.raises(CommandError):
 | 
			
		||||
        call_command(
 | 
			
		||||
            'import_files',
 | 
			
		||||
            path,
 | 
			
		||||
            in_place=True,
 | 
			
		||||
            username='me',
 | 
			
		||||
            interactive=False
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_import_files_creates_a_batch_and_job(factories, mocker):
 | 
			
		||||
    m = mocker.patch('funkwhale_api.music.tasks.import_job_run')
 | 
			
		||||
    user = factories['users.User'](username='me')
 | 
			
		||||
| 
						 | 
				
			
			@ -137,6 +151,27 @@ def test_import_files_works_with_utf8_file_name(factories, mocker):
 | 
			
		|||
        use_acoustid=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_import_files_in_place(factories, mocker, settings):
 | 
			
		||||
    settings.MUSIC_DIRECTORY_PATH = DATA_DIR
 | 
			
		||||
    m = mocker.patch('funkwhale_api.music.tasks.import_job_run')
 | 
			
		||||
    user = factories['users.User'](username='me')
 | 
			
		||||
    path = os.path.join(DATA_DIR, 'utf8-éà◌.ogg')
 | 
			
		||||
    call_command(
 | 
			
		||||
        'import_files',
 | 
			
		||||
        path,
 | 
			
		||||
        username='me',
 | 
			
		||||
        async=False,
 | 
			
		||||
        in_place=True,
 | 
			
		||||
        no_acoustid=True,
 | 
			
		||||
        interactive=False)
 | 
			
		||||
    batch = user.imports.latest('id')
 | 
			
		||||
    job = batch.jobs.first()
 | 
			
		||||
    assert bool(job.audio_file) is False
 | 
			
		||||
    m.assert_called_once_with(
 | 
			
		||||
        import_job_id=job.pk,
 | 
			
		||||
        use_acoustid=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_storage_rename_utf_8_files(factories):
 | 
			
		||||
    tf = factories['music.TrackFile'](audio_file__filename='été.ogg')
 | 
			
		||||
    assert tf.audio_file.name.endswith('ete.ogg')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue