diff --git a/src/octoprint/events.py b/src/octoprint/events.py index 33fc04cb..7e786519 100644 --- a/src/octoprint/events.py +++ b/src/octoprint/events.py @@ -112,7 +112,7 @@ class EventManager(object): self._registeredListeners = collections.defaultdict(list) self._logger = logging.getLogger(__name__) - self._queue = Queue.PriorityQueue() + self._queue = Queue.Queue() self._worker = threading.Thread(target=self._work) self._worker.daemon = True self._worker.start() @@ -120,7 +120,7 @@ class EventManager(object): def _work(self): try: while True: - (event, payload) = self._queue.get(True) + event, payload = self._queue.get(True) eventListeners = self._registeredListeners[event] self._logger.debug("Firing event: %s (Payload: %r)" % (event, payload)) @@ -149,7 +149,7 @@ class EventManager(object): payload being a payload object specific to the event. """ - self._queue.put((event, payload), 0) + self._queue.put((event, payload)) if event == Events.UPDATED_FILES and "type" in payload and payload["type"] == "printables": # when sending UpdatedFiles with type "printables", also send another event with deprecated type "gcode" @@ -157,8 +157,7 @@ class EventManager(object): import copy legacy_payload = copy.deepcopy(payload) legacy_payload["type"] = "gcode" - self._queue.put((event, legacy_payload), 0) - + self._queue.put((event, legacy_payload)) def subscribe(self, event, callback): """ diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index 3e91890d..276c365f 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -533,7 +533,10 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self.refresh_sd_files(blocking=True) existingSdFiles = map(lambda x: x[0], self._comm.getSdFiles()) - remoteName = util.get_dos_filename(filename, existing_filenames=existingSdFiles, extension="gco") + remoteName = util.get_dos_filename(filename, + existing_filenames=existingSdFiles, + extension="gco", + whitelisted_extensions=["gco", "g"]) self._timeEstimationData = TimeEstimationHelper() self._comm.startFileTransfer(absolutePath, filename, "/" + remoteName) diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index eb86cc7e..076a533a 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -11,8 +11,6 @@ function DataUpdater(allViewModels) { self._lastProcessingTimes = []; self._lastProcessingTimesSize = 20; - self._timelapse_popup = undefined; - self.increaseThrottle = function() { self.setThrottle(self._throttleFactor + 1); }; @@ -156,7 +154,6 @@ function DataUpdater(allViewModels) { var type = event.data["type"]; var payload = event.data["payload"]; var html = ""; - var format = {}; log.debug("Got event " + type + " with payload: " + JSON.stringify(payload)); @@ -164,91 +161,6 @@ function DataUpdater(allViewModels) { if (payload && payload.hasOwnProperty("config_hash")) { self._configHash = payload.config_hash; } - } else if (type == "MovieRendering") { - if (self._timelapse_popup !== undefined) { - self._timelapse_popup.remove(); - } - self._timelapse_popup = new PNotify({ - title: gettext("Rendering timelapse"), - text: _.sprintf(gettext("Now rendering timelapse %(movie_basename)s. Due to performance reasons it is not recommended to start a print job while a movie is still rendering."), payload), - hide: false, - callbacks: { - before_close: function() { - self._timelapse_popup = undefined; - } - } - }); - } else if (type == "MovieDone") { - if (self._timelapse_popup !== undefined) { - self._timelapse_popup.remove(); - } - self._timelapse_popup = new PNotify({ - title: gettext("Timelapse ready"), - text: _.sprintf(gettext("New timelapse %(movie_basename)s is done rendering."), payload), - type: "success", - callbacks: { - before_close: function(notice) { - if (self._timelapse_popup == notice) { - self._timelapse_popup = undefined; - } - } - } - }); - } else if (type == "MovieFailed") { - html = "
" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "
"; - html += pnotifyAdditionalInfo('' + payload.error + ''); - - if (self._timelapse_popup !== undefined) { - self._timelapse_popup.remove(); - } - self._timelapse_popup = new PNotify({ - title: gettext("Rendering failed"), - text: html, - type: "error", - hide: false, - callbacks: { - before_close: function(notice) { - if (self._timelapse_popup == notice) { - self._timelapse_popup = undefined; - } - } - } - }); - } else if (type == "PostRollStart") { - var title = gettext("Capturing timelapse postroll"); - - var text; - if (!payload.postroll_duration) { - text = _.sprintf(gettext("Now capturing timelapse post roll, this will take only a moment..."), format); - } else { - format = { - time: moment().add(payload.postroll_duration, "s").format("LT") - }; - - if (payload.postroll_duration > 60) { - format.duration = _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60}); - text = _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s (so until %(time)s)..."), format); - } else { - format.duration = _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration}); - text = _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format); - } - } - - if (self._timelapse_popup !== undefined) { - self._timelapse_popup.remove(); - } - self._timelapse_popup = new PNotify({ - title: title, - text: text, - hide: false, - callbacks: { - before_close: function(notice) { - if (self._timelapse_popup == notice) { - self._timelapse_popup = undefined; - } - } - } - }); } else if (type == "SlicingStarted") { gcodeUploadProgress.addClass("progress-striped").addClass("active"); gcodeUploadProgressBar.css("width", "100%"); diff --git a/src/octoprint/static/js/app/viewmodels/timelapse.js b/src/octoprint/static/js/app/viewmodels/timelapse.js index 06081eee..77320e7f 100644 --- a/src/octoprint/static/js/app/viewmodels/timelapse.js +++ b/src/octoprint/static/js/app/viewmodels/timelapse.js @@ -4,6 +4,8 @@ $(function() { self.loginState = parameters[0]; + self.timelapsePopup = undefined; + self.defaultFps = 25; self.defaultPostRoll = 0; self.defaultInterval = 10; @@ -222,11 +224,88 @@ $(function() { .done(self.fromResponse); }; + self.displayTimelapsePopup = function(options) { + if (self.timelapsePopup !== undefined) { + self.timelapsePopup.remove(); + } + + _.extend(options, { + callbacks: { + before_close: function(notice) { + if (self.timelapsePopup == notice) { + self.timelapsePopup = undefined; + } + } + } + }); + + self.timelapsePopup = new PNotify(options); + }; + self.onDataUpdaterReconnect = function() { self.requestData(); }; - self.onEventMovieDone = function() { + self.onEventPostRollStart = function(payload) { + var title = gettext("Capturing timelapse postroll"); + + var text; + if (!payload.postroll_duration) { + text = _.sprintf(gettext("Now capturing timelapse post roll, this will take only a moment..."), format); + } else { + var format = { + time: moment().add(payload.postroll_duration, "s").format("LT") + }; + + if (payload.postroll_duration > 60) { + format.duration = _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60}); + text = _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s (so until %(time)s)..."), format); + } else { + format.duration = _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration}); + text = _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format); + } + } + + self.displayTimelapsePopup({ + title: title, + text: text, + hide: false + }); + }; + + self.onEventMovieRendering = function(payload) { + self.displayTimelapsePopup({ + title: gettext("Rendering timelapse"), + text: _.sprintf(gettext("Now rendering timelapse %(movie_basename)s. Due to performance reasons it is not recommended to start a print job while a movie is still rendering."), payload), + hide: false + }); + }; + + self.onEventMovieFailed = function(payload) { + var html = "
" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "
"; + html += pnotifyAdditionalInfo('' + payload.error + ''); + + self.displayTimelapsePopup({ + title: gettext("Rendering failed"), + text: html, + type: "error", + hide: false + }); + }; + + self.onEventMovieDone = function(payload) { + self.displayTimelapsePopup({ + title: gettext("Timelapse ready"), + text: _.sprintf(gettext("New timelapse %(movie_basename)s is done rendering."), payload), + type: "success", + callbacks: { + before_close: function(notice) { + if (self.timelapsePopup == notice) { + self.timelapsePopup = undefined; + } + } + } + }); self.requestData(); }; diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 351c9582..391cddb5 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -206,7 +206,7 @@ def get_free_bytes(path): return psutil.disk_usage(path).free -def get_dos_filename(origin, existing_filenames=None, extension=None, **kwargs): +def get_dos_filename(input, existing_filenames=None, extension=None, whitelisted_extensions=None, **kwargs): """ Converts the provided input filename to a 8.3 DOS compatible filename. If ``existing_filenames`` is provided, the conversion result will be guaranteed not to collide with any of the filenames provided thus. @@ -219,6 +219,8 @@ def get_dos_filename(origin, existing_filenames=None, extension=None, **kwargs): Optional. extension (string): The .3 file extension to use for the generated filename. If not provided, the extension of the provided ``filename`` will simply be truncated to 3 characters. + whitelisted_extensions (list): A list of extensions on ``input`` that will be left as-is instead of + exchanging for ``extension``. kwargs (dict): Additional keyword arguments to provide to :func:`find_collision_free_name`. Returns: @@ -228,16 +230,43 @@ def get_dos_filename(origin, existing_filenames=None, extension=None, **kwargs): Raises: ValueError: No 8.3 compatible name could be found that doesn't collide with the provided ``existing_filenames``. + + Examples: + + >>> get_dos_filename("test1234.gco") + u'test1234.gco' + >>> get_dos_filename("test1234.gcode") + u'test1234.gco' + >>> get_dos_filename("test12345.gco") + u'test12~1.gco' + >>> get_dos_filename("test1234.fnord", extension="gco") + u'test1234.gco' + >>> get_dos_filename("auto0.g", extension="gco") + u'auto0.gco' + >>> get_dos_filename("auto0.g", extension="gco", whitelisted_extensions=["g"]) + u'auto0.g' + >>> get_dos_filename(None) + >>> get_dos_filename("foo") + u'foo' """ - if origin is None: + if input is None: return None if existing_filenames is None: existing_filenames = [] - filename, ext = os.path.splitext(origin) - if extension is None: + if extension is not None: + extension = extension.lower() + + if whitelisted_extensions is None: + whitelisted_extensions = [] + + filename, ext = os.path.splitext(input) + + ext = ext.lower() + ext = ext[1:] if ext.startswith(".") else ext + if ext in whitelisted_extensions or extension is None: extension = ext return find_collision_free_name(filename, extension, existing_filenames, **kwargs) @@ -286,9 +315,36 @@ def find_collision_free_name(filename, extension, existing_filenames, max_power= Raises: ValueError: No collision free name could be found. - """ - # TODO unit test! + Examples: + + >>> find_collision_free_name("test1234", "gco", []) + u'test1234.gco' + >>> find_collision_free_name("test1234", "gcode", []) + u'test1234.gco' + >>> find_collision_free_name("test12345", "gco", []) + u'test12~1.gco' + >>> find_collision_free_name("test 123", "gco", []) + u'test_123.gco' + >>> find_collision_free_name("test1234", "g o", []) + u'test1234.g_o' + >>> find_collision_free_name("test12345", "gco", ["test12~1.gco"]) + u'test12~2.gco' + >>> many_files = ["test12~{}.gco".format(x) for x in range(10)[1:]] + >>> find_collision_free_name("test12345", "gco", many_files) + u'test1~10.gco' + >>> many_more_files = many_files + ["test1~{}.gco".format(x) for x in range(10, 99)] + >>> find_collision_free_name("test12345", "gco", many_more_files) + u'test1~99.gco' + >>> many_more_files_plus_one = many_more_files + ["test1~99.gco"] + >>> find_collision_free_name("test12345", "gco", many_more_files_plus_one) + Traceback (most recent call last): + ... + ValueError: Can't create a collision free filename + >>> find_collision_free_name("test12345", "gco", many_more_files_plus_one, max_power=3) + u'test~100.gco' + + """ if not isinstance(filename, unicode): filename = unicode(filename) @@ -302,14 +358,21 @@ def find_collision_free_name(filename, extension, existing_filenames, max_power= extension = make_valid(extension) extension = extension[:3] if len(extension) > 3 else extension - if len(filename) <= 8 and not filename + "." + extension in existing_filenames: + full_name_format = u"{filename}.{extension}" if extension else u"{filename}" + + result = full_name_format.format(filename=filename, + extension=extension) + if len(filename) <= 8 and not result in existing_filenames: # early exit - return filename + "." + extension + return result counter = 1 power = 1 + prefix_format = u"{segment}~{counter}" while counter < (10 ** max_power): - result = filename[:(6 - power + 1)] + "~" + str(counter) + "." + extension + prefix = prefix_format.format(segment=filename[:(6 - power + 1)], counter=str(counter)) + result = full_name_format.format(filename=prefix, + extension=extension) if result not in existing_filenames: return result counter += 1