From e04a2a3c66499776277d84742b07a84ff70ccbf4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= <sybren@stuvel.eu>
Date: Fri, 27 Jan 2017 11:55:54 +0100
Subject: [PATCH] Server: blender-render task now first moves existing render
 output dir aside

The render output directory is moved to the same name, with its
modification timestamp appended in ISO-8601 format.

As a result, the render output directory MUST be known to Flamenco, and
thus from now on is a mandatory setting for the blender-render job type.
---
 .../flamenco/job_compilers/blender_render.py  | 39 +++++++++++++++++--
 .../flamenco/job_compilers/commands.py        | 13 +++++++
 packages/flamenco/tests/test_job_manager.py   |  6 ++-
 3 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/packages/flamenco/flamenco/job_compilers/blender_render.py b/packages/flamenco/flamenco/job_compilers/blender_render.py
index 98ef0d4d..65a464d4 100644
--- a/packages/flamenco/flamenco/job_compilers/blender_render.py
+++ b/packages/flamenco/flamenco/job_compilers/blender_render.py
@@ -7,10 +7,41 @@ class BlenderRender(AbstractJobCompiler):
     """Basic Blender render job."""
 
     def compile(self, job):
-        from flamenco.utils import iter_frame_range, frame_range_merge
-
         self._log.info('Compiling job %s', job['_id'])
 
+        move_existing_task_id = self._make_move_out_of_way_task(job)
+        task_count = 1 + self._make_render_tasks(job, move_existing_task_id)
+
+        self._log.info('Created %i tasks for job %s', task_count, job['_id'])
+
+    def _make_move_out_of_way_task(self, job):
+        """Creates a MoveOutOfWay command to back up existing frames, and wraps it in a task.
+
+        :returns: the ObjectId of the created task.
+        :rtype: bson.ObjectId
+        """
+
+        import os.path
+
+        # The render path contains a filename pattern, most likely '######' or
+        # something similar. This has to be removed, so that we end up with
+        # the directory that will contain the frames.
+        render_dest_dir = os.path.dirname(job['settings']['render_output'])
+        cmd = commands.MoveOutOfWay(src=render_dest_dir)
+
+        task_id = self.task_manager.api_create_task(
+            job, [cmd], 'move-existing-frames')
+
+        return task_id
+
+    def _make_render_tasks(self, job, parent_task_id):
+        """Creates the render tasks for this job.
+
+        :returns: the number of tasks created
+        :rtype: int
+        """
+        from flamenco.utils import iter_frame_range, frame_range_merge
+
         job_settings = job['settings']
 
         task_count = 0
@@ -28,7 +59,7 @@ class BlenderRender(AbstractJobCompiler):
             ]
 
             name = 'blender-render-%s' % frame_range
-            self.task_manager.api_create_task(job, task_cmds, name)
+            self.task_manager.api_create_task(job, task_cmds, name, parents=[parent_task_id])
             task_count += 1
 
-        self._log.info('Created %i tasks for job %s', task_count, job['_id'])
+        return task_count
diff --git a/packages/flamenco/flamenco/job_compilers/commands.py b/packages/flamenco/flamenco/job_compilers/commands.py
index 40254d19..1bd8df2d 100644
--- a/packages/flamenco/flamenco/job_compilers/commands.py
+++ b/packages/flamenco/flamenco/job_compilers/commands.py
@@ -48,3 +48,16 @@ class BlenderRender(AbstractCommand):
 
     # list of frames to render, as frame range string.
     frames = attr.ib(validator=attr.validators.instance_of(unicode))
+
+
+@attr.s
+class MoveOutOfWay(AbstractCommand):
+    """Moves a file or directory out of the way.
+
+    The destination is the same as the source, with the source's modification
+    timestamp appended to it.
+
+    :ivar src: source path
+    """
+
+    src = attr.ib(validator=attr.validators.instance_of(unicode))
diff --git a/packages/flamenco/tests/test_job_manager.py b/packages/flamenco/tests/test_job_manager.py
index b2cafb92..f2a266d2 100644
--- a/packages/flamenco/tests/test_job_manager.py
+++ b/packages/flamenco/tests/test_job_manager.py
@@ -87,6 +87,7 @@ class JobStatusChangeTest(AbstractFlamencoTest):
                     'frames': u'12-18, 20-25',
                     'chunk_size': 2,
                     'time_in_seconds': 3,
+                    'render_output': u'/not-relevant-now/####',
                 },
                 self.proj_id,
                 ctd.EXAMPLE_PROJECT_OWNER_ID,
@@ -96,7 +97,10 @@ class JobStatusChangeTest(AbstractFlamencoTest):
 
             # Fetch the task IDs and set the task statuses to a fixed list.
             tasks_coll = self.flamenco.db('tasks')
-            tasks = tasks_coll.find({'job': self.job_id}, projection={'_id': 1})
+            tasks = tasks_coll.find({
+                'job': self.job_id,
+                'name': {'$regex': '^blender-render-'},  # don't consider move-out-of-way task.
+            }, projection={'_id': 1})
             self.task_ids = [task['_id'] for task in tasks]
 
         self.assertEqual(len(tasks_schema['status']['allowed']), len(self.task_ids))
-- 
GitLab