diff --git a/flamenco_worker/runner.py b/flamenco_worker/runner.py index a1027eeb5fe8eb59d34c0a816a8c7197c44d154c..03a2f28d063ef5d3089df72b5bcee114cc06b604 100644 --- a/flamenco_worker/runner.py +++ b/flamenco_worker/runner.py @@ -356,6 +356,40 @@ class MoveOutOfWayCommand(AbstractCommand): src.rename(dst) +@command_executor('move_to_final') +class MoveToFinalCommand(AbstractCommand): + def validate(self, settings: dict): + _, err1 = self._setting(settings, 'src', True) + _, err2 = self._setting(settings, 'dest', True) + return err1 or err2 + + async def execute(self, settings: dict): + src = Path(settings['src']) + if not src.exists(): + msg = 'Path %s does not exist, not moving' % src + self._log.info(msg) + await self.worker.register_log('%s: %s', self.command_name, msg) + return + + dest = Path(settings['dest']) + if dest.exists(): + backup = _timestamped_path(dest) + self._log.debug('Destination %s exists, moving out of the way to %s', dest, backup) + + if backup.exists(): + self._log.debug('Destination %s exists, finding one that does not', backup) + backup = _unique_path(backup) + self._log.debug('New destination is %s', backup) + + self._log.info('Moving %s to %s', dest, backup) + await self.worker.register_log('%s: Moving %s to %s', self.command_name, dest, backup) + dest.rename(backup) + + self._log.info('Moving %s to %s', src, dest) + await self.worker.register_log('%s: Moving %s to %s', self.command_name, src, dest) + src.rename(dest) + + @attr.s class AbstractSubprocessCommand(AbstractCommand): readline_timeout = attr.ib(default=SUBPROC_READLINE_TIMEOUT) diff --git a/tests/test_runner_move_to_final.py b/tests/test_runner_move_to_final.py new file mode 100644 index 0000000000000000000000000000000000000000..fe5b3ce15f8ad013b0d6481c78c9415bb98483da --- /dev/null +++ b/tests/test_runner_move_to_final.py @@ -0,0 +1,94 @@ +import os +from pathlib import Path + +from test_runner import AbstractCommandTest + + +class MoveToFinalTest(AbstractCommandTest): + def setUp(self): + super().setUp() + + from flamenco_worker.runner import MoveToFinalCommand + import tempfile + + self.tmpdir = tempfile.TemporaryDirectory() + self.tmppath = Path(self.tmpdir.name) + + self.cmd = MoveToFinalCommand( + worker=self.fworker, + task_id='12345', + command_idx=0, + ) + self.cmd.__attrs_post_init__() + + def tearDown(self): + super().tearDown() + del self.tmpdir + + def test_nonexistant_source(self): + src = self.tmppath / 'nonexistant-dir' + dest = self.tmppath / 'dest' + + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + + # Should be fine. + self.assertTrue(ok) + self.assertFalse(src.exists()) + self.assertFalse(dest.exists()) + + def test_existing_source_and_dest(self): + src = self.tmppath / 'existing-dir' + src.mkdir() + (src / 'src-contents').touch() + + # Make sure that the destination already exists, with some contents. + dest = self.tmppath / 'dest' + dest.mkdir() + (dest / 'dest-contents').touch() + (dest / 'dest-subdir').mkdir() + (dest / 'dest-subdir' / 'sub-contents').touch() + + os.utime(str(dest), (1330712280, 1330712292)) # fixed (atime, mtime) for testing + + # Run the command. + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + self.assertTrue(ok) + + renamed_dest = dest.with_name('dest-2012-03-02_191812') + + # old 'dest' contents should exist at 'renamed_dest' + self.assertTrue(renamed_dest.exists()) + self.assertTrue((renamed_dest / 'dest-contents').exists()) + self.assertTrue((renamed_dest / 'dest-subdir').exists()) + self.assertTrue((renamed_dest / 'dest-subdir' / 'sub-contents').exists()) + + # old 'src' contents should exist at 'dest' + self.assertTrue(dest.exists()) + self.assertTrue((dest / 'src-contents').exists()) + + # old 'src' should no longer exist. + self.assertFalse(src.exists()) + + def test_nonexistant_dest(self): + src = self.tmppath / 'existing-dir' + src.mkdir() + (src / 'src-contents').touch() + + dest = self.tmppath / 'dest' + self.assertFalse(dest.exists()) + + task = self.cmd.run({'src': str(src), 'dest': str(dest)}) + ok = self.loop.run_until_complete(task) + self.assertTrue(ok) + + # 'dest-{timestamp}' shouldn't exist. + self.assertFalse(list(self.tmppath.glob('dest-*'))) + + # old 'src' contents should exist at 'dest' + self.assertTrue(dest.exists()) + self.assertTrue((dest / 'src-contents').exists()) + + # old 'src' should no longer exist. + self.assertFalse(src.exists())