diff --git a/flamenco_worker/commands.py b/flamenco_worker/commands.py index 14a3156467bd022428da5760ac4a560ade97808c..8f419d1bfb36c3a6116cf5b9f7c37d07ac434cb6 100644 --- a/flamenco_worker/commands.py +++ b/flamenco_worker/commands.py @@ -324,6 +324,71 @@ class MoveToFinalCommand(AbstractCommand): src.rename(dest) +@command_executor('copy_file') +class CopyFileCommand(AbstractCommand): + def validate(self, settings: dict): + src, err = self._setting(settings, 'src', True) + if err: + return err + if not src: + return 'src may not be empty' + dest, err = self._setting(settings, 'dest', True) + if err: + return err + if not dest: + return 'dest may not be empty' + + async def execute(self, settings: dict): + src = Path(settings['src']) + if not src.exists(): + raise CommandExecutionError('Path %s does not exist, unable to copy' % src) + + dest = Path(settings['dest']) + if dest.exists(): + msg = 'Destination %s exists, going to overwrite it.' % dest + self._log.info(msg) + await self.worker.register_log('%s: %s', self.command_name, msg) + + self._log.info('Copying %s to %s', src, dest) + await self.worker.register_log('%s: Copying %s to %s', self.command_name, src, dest) + + if not dest.parent.exists(): + await self.worker.register_log('%s: Target directory %s does not exist; creating.', + self.command_name, dest.parent) + dest.parent.mkdir(parents=True) + + import shutil + shutil.copy(str(src), str(dest)) + + +@command_executor('remove_tree') +class RemoveTreeCommand(AbstractCommand): + def validate(self, settings: dict): + path, err = self._setting(settings, 'path', True) + if err: + return err + if not path: + return "'path' may not be empty" + + async def execute(self, settings: dict): + path = Path(settings['path']) + if not path.exists(): + msg = 'Path %s does not exist, so not removing.' % path + self._log.debug(msg) + await self.worker.register_log(msg) + return + + msg = 'Removing tree rooted at %s' % path + self._log.info(msg) + await self.worker.register_log(msg) + + if path.is_dir(): + import shutil + shutil.rmtree(str(path)) + else: + path.unlink() + + @attr.s class AbstractSubprocessCommand(AbstractCommand): readline_timeout = attr.ib(default=SUBPROC_READLINE_TIMEOUT) diff --git a/tests/test_commands_copy_file.py b/tests/test_commands_copy_file.py new file mode 100644 index 0000000000000000000000000000000000000000..78408d30cacb0453440d54e364689ec2e2ccf7f8 --- /dev/null +++ b/tests/test_commands_copy_file.py @@ -0,0 +1,109 @@ +from pathlib import Path +import os + +from test_runner import AbstractCommandTest + + +class CopyFileTest(AbstractCommandTest): + def setUp(self): + super().setUp() + + from flamenco_worker.commands import CopyFileCommand + import tempfile + + self.tmpdir = tempfile.TemporaryDirectory() + self.tmppath = Path(self.tmpdir.name) + + self.cmd = CopyFileCommand( + worker=self.fworker, + task_id='12345', + command_idx=0, + ) + + def tearDown(self): + super().tearDown() + self.tmpdir.cleanup() + + def test_validate_settings(self): + self.assertIn('src', self.cmd.validate({'src': 12, 'dest': '/valid/path'})) + self.assertIn('src', self.cmd.validate({'src': '', 'dest': '/valid/path'})) + self.assertIn('dest', self.cmd.validate({'src': '/valid/path', 'dest': 12})) + self.assertIn('dest', self.cmd.validate({'src': '/valid/path', 'dest': ''})) + self.assertTrue(self.cmd.validate({})) + self.assertFalse(self.cmd.validate({'src': '/some/path', 'dest': '/some/path'})) + + def test_nonexistant_source_and_dest(self): + src = self.tmppath / 'nonexisting' + dest = self.tmppath / 'dest' + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + + self.assertFalse(ok) + self.assertFalse(src.exists()) + self.assertFalse(dest.exists()) + + def test_existing_source__nonexisting_dest(self): + src = self.tmppath / 'existing' + src.touch() + dest = self.tmppath / 'dest' + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertTrue(src.exists()) + self.assertTrue(dest.exists()) + + def test_nonexisting_source__existing_dest(self): + src = self.tmppath / 'non-existing' + + dest = self.tmppath / 'dest' + with open(str(dest), 'w') as outfile: + outfile.write('dest') + + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + + self.assertFalse(ok) + self.assertFalse(src.exists()) + self.assertTrue(dest.exists()) + + with open(str(dest), 'r') as infile: + self.assertEqual('dest', infile.read()) + + def test_existing_source_and_dest(self): + src = self.tmppath / 'existing' + with open(str(src), 'w') as outfile: + outfile.write('src') + + dest = self.tmppath / 'dest' + with open(str(dest), 'w') as outfile: + outfile.write('dest') + + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertTrue(src.exists()) + self.assertTrue(dest.exists()) + + with open(str(src), 'r') as infile: + self.assertEqual('src', infile.read()) + + with open(str(dest), 'r') as infile: + self.assertEqual('src', infile.read()) + + def test_dest_in_nonexisting_subdir(self): + src = self.tmppath / 'existing' + with open(str(src), 'w') as outfile: + outfile.write('src') + + dest = self.tmppath / 'nonexisting' / 'subdir' / 'dest' + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertTrue(src.exists()) + self.assertTrue(dest.exists()) + + with open(str(dest), 'r') as infile: + self.assertEqual('src', infile.read()) diff --git a/tests/test_commands_remove_tree.py b/tests/test_commands_remove_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..8308612f041a8c56f659b30a4050c3ab84c7a437 --- /dev/null +++ b/tests/test_commands_remove_tree.py @@ -0,0 +1,93 @@ +from pathlib import Path +import os + +from test_runner import AbstractCommandTest + + +class RemoveTreeTest(AbstractCommandTest): + def setUp(self): + super().setUp() + + from flamenco_worker.commands import RemoveTreeCommand + import tempfile + + self.tmpdir = tempfile.TemporaryDirectory() + self.tmppath = Path(self.tmpdir.name) + + self.cmd = RemoveTreeCommand( + worker=self.fworker, + task_id='12345', + command_idx=0, + ) + + def tearDown(self): + super().tearDown() + self.tmpdir.cleanup() + + def test_validate_settings(self): + self.assertIn('path', self.cmd.validate({'path': 12})) + self.assertIn('path', self.cmd.validate({'path': ''})) + self.assertIn('path', self.cmd.validate({})) + self.assertFalse(self.cmd.validate({'path': '/some/path'})) + + def test_nonexistant_source(self): + path = self.tmppath / 'nonexisting' + task = self.cmd.run({'path': str(path)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertFalse(path.exists()) + + def test_source_file(self): + path = self.tmppath / 'existing' + path.touch() + task = self.cmd.run({'path': str(path)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertFalse(path.exists()) + + def test_soure_dir_with_files(self): + path = self.tmppath / 'dir' + path.mkdir() + (path / 'a.file').touch() + (path / 'b.file').touch() + (path / 'c.file').touch() + + task = self.cmd.run({'path': str(path)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertFalse(path.exists()) + + def test_soure_dir_with_files_and_dirs(self): + path = self.tmppath / 'dir' + path.mkdir() + (path / 'subdir-a' / 'subsub-1').mkdir(parents=True) + (path / 'subdir-a' / 'subsub-2').mkdir() + (path / 'subdir-a' / 'subsub-3').mkdir() + (path / 'subdir-b' / 'subsub-1').mkdir(parents=True) + (path / 'subdir-c' / 'subsub-1').mkdir(parents=True) + (path / 'subdir-c' / 'subsub-2').mkdir() + (path / 'a.file').touch() + (path / 'b.file').touch() + (path / 'c.file').touch() + + (path / 'subdir-a' / 'subsub-1' / 'a.file').touch() + (path / 'subdir-a' / 'subsub-1' / 'b.file').touch() + (path / 'subdir-a' / 'subsub-2' / 'a.file').touch() + (path / 'subdir-a' / 'subsub-2' / 'b.file').touch() + (path / 'subdir-a' / 'subsub-3' / 'a.file').touch() + (path / 'subdir-a' / 'subsub-3' / 'b.file').touch() + (path / 'subdir-b' / 'subsub-1' / 'a.file').touch() + (path / 'subdir-b' / 'subsub-1' / 'b.file').touch() + (path / 'subdir-c' / 'subsub-1' / 'a.file').touch() + (path / 'subdir-c' / 'subsub-1' / 'b.file').touch() + (path / 'subdir-c' / 'subsub-2' / 'a.file').touch() + (path / 'subdir-c' / 'subsub-2' / 'b.file').touch() + + task = self.cmd.run({'path': str(path)}) + ok = self.loop.run_until_complete(task) + + self.assertTrue(ok) + self.assertFalse(path.exists())