From 8ce1eafd5da3ef32540d21fb172500b4befe424d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 2 Jun 2016 10:11:54 +0200 Subject: [PATCH] Properly track unrendered timelapses that are being processed UI removes action buttons for them and displays a spinner indicating that the timelapse in question is either being recorded or being rendered at the moment. Should prevent users from accidentally trying to render an incomplete timelapse. --- src/octoprint/templates/tabs/timelapse.jinja2 | 5 +- src/octoprint/timelapse.py | 48 ++++++++++++++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/octoprint/templates/tabs/timelapse.jinja2 b/src/octoprint/templates/tabs/timelapse.jinja2 index cd99de8b..d7e7cf38 100644 --- a/src/octoprint/templates/tabs/timelapse.jinja2 +++ b/src/octoprint/templates/tabs/timelapse.jinja2 @@ -94,7 +94,10 @@ -  |  + + +  |  + diff --git a/src/octoprint/timelapse.py b/src/octoprint/timelapse.py index b72473e1..f4e349d1 100644 --- a/src/octoprint/timelapse.py +++ b/src/octoprint/timelapse.py @@ -47,6 +47,9 @@ _update_callbacks = [] # lock for timelapse cleanup, must be re-entrant _cleanup_lock = threading.RLock() +# lock for timelapse job +_job_lock = threading.RLock() + def _extract_prefix(filename): """ @@ -78,10 +81,14 @@ def get_finished_timelapses(): def get_unrendered_timelapses(): + global _job_lock + global current + delete_old_unrendered_timelapses() basedir = settings().getBaseFolder("timelapse_tmp") jobs = collections.defaultdict(lambda: dict(count=0, size=None, bytes=0, date=None, timestamp=None)) + for osFile in os.listdir(basedir): if not fnmatch.fnmatch(osFile, "*.jpg"): continue @@ -96,13 +103,23 @@ def get_unrendered_timelapses(): if jobs[prefix]["timestamp"] is None or statResult.st_ctime < jobs[prefix]["timestamp"]: jobs[prefix]["timestamp"] = statResult.st_ctime - def finalize_fields(job): - job["size"] = util.get_formatted_size(job["bytes"]) - job["date"] = util.get_formatted_datetime(datetime.datetime.fromtimestamp(job["timestamp"])) - del job["timestamp"] - return job + with _job_lock: + global current_render_job - return sorted([util.dict_merge(dict(name=key), finalize_fields(value)) for key, value in jobs.items()], key=lambda x: x["name"]) + def finalize_fields(prefix, job): + currently_recording = current is not None and current.prefix == prefix + currently_rendering = current_render_job is not None and current_render_job["prefix"] == prefix + + job["size"] = util.get_formatted_size(job["bytes"]) + job["date"] = util.get_formatted_datetime(datetime.datetime.fromtimestamp(job["timestamp"])) + job["recording"] = currently_recording + job["rendering"] = currently_rendering + job["processing"] = currently_recording or currently_rendering + del job["timestamp"] + + return job + + return sorted([util.dict_merge(dict(name=key), finalize_fields(key, value)) for key, value in jobs.items()], key=lambda x: x["name"]) def delete_unrendered_timelapse(name): @@ -175,11 +192,15 @@ def delete_old_unrendered_timelapses(): def _create_render_start_handler(name, gcode=None): def f(movie): - global current_render_job - event_payload = {"gcode": gcode if gcode is not None else "unknown", - "movie": movie, - "movie_basename": os.path.basename(movie)} - current_render_job = event_payload + global _job_lock + + with _job_lock: + global current_render_job + event_payload = {"gcode": gcode if gcode is not None else "unknown", + "movie": movie, + "movie_basename": os.path.basename(movie)} + current_render_job = dict(prefix=name) + current_render_job.update(event_payload) eventManager().fire(Events.MOVIE_RENDERING, event_payload) return f @@ -277,6 +298,7 @@ class Timelapse(object): self._image_number = None self._in_timelapse = False self._gcode_file = None + self._file_prefix = None self._post_roll = post_roll self._on_post_roll_done = None @@ -303,6 +325,10 @@ class Timelapse(object): for (event, callback) in self.event_subscriptions(): eventManager().subscribe(event, callback) + @property + def prefix(self): + return self._file_prefix + @property def post_roll(self): return self._post_roll