Merge branch 'maintenance' into devel
Conflicts: src/octoprint/static/js/app/dataupdater.js src/octoprint/static/js/app/viewmodels/timelapse.js
This commit is contained in:
commit
7d52aa4be2
5 changed files with 160 additions and 104 deletions
|
|
@ -112,7 +112,7 @@ class EventManager(object):
|
||||||
self._registeredListeners = collections.defaultdict(list)
|
self._registeredListeners = collections.defaultdict(list)
|
||||||
self._logger = logging.getLogger(__name__)
|
self._logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self._queue = Queue.PriorityQueue()
|
self._queue = Queue.Queue()
|
||||||
self._worker = threading.Thread(target=self._work)
|
self._worker = threading.Thread(target=self._work)
|
||||||
self._worker.daemon = True
|
self._worker.daemon = True
|
||||||
self._worker.start()
|
self._worker.start()
|
||||||
|
|
@ -120,7 +120,7 @@ class EventManager(object):
|
||||||
def _work(self):
|
def _work(self):
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
(event, payload) = self._queue.get(True)
|
event, payload = self._queue.get(True)
|
||||||
|
|
||||||
eventListeners = self._registeredListeners[event]
|
eventListeners = self._registeredListeners[event]
|
||||||
self._logger.debug("Firing event: %s (Payload: %r)" % (event, payload))
|
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.
|
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":
|
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"
|
# when sending UpdatedFiles with type "printables", also send another event with deprecated type "gcode"
|
||||||
|
|
@ -157,8 +157,7 @@ class EventManager(object):
|
||||||
import copy
|
import copy
|
||||||
legacy_payload = copy.deepcopy(payload)
|
legacy_payload = copy.deepcopy(payload)
|
||||||
legacy_payload["type"] = "gcode"
|
legacy_payload["type"] = "gcode"
|
||||||
self._queue.put((event, legacy_payload), 0)
|
self._queue.put((event, legacy_payload))
|
||||||
|
|
||||||
|
|
||||||
def subscribe(self, event, callback):
|
def subscribe(self, event, callback):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,10 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback):
|
||||||
self.refresh_sd_files(blocking=True)
|
self.refresh_sd_files(blocking=True)
|
||||||
existingSdFiles = map(lambda x: x[0], self._comm.getSdFiles())
|
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._timeEstimationData = TimeEstimationHelper()
|
||||||
self._comm.startFileTransfer(absolutePath, filename, "/" + remoteName)
|
self._comm.startFileTransfer(absolutePath, filename, "/" + remoteName)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ function DataUpdater(allViewModels) {
|
||||||
self._lastProcessingTimes = [];
|
self._lastProcessingTimes = [];
|
||||||
self._lastProcessingTimesSize = 20;
|
self._lastProcessingTimesSize = 20;
|
||||||
|
|
||||||
self._timelapse_popup = undefined;
|
|
||||||
|
|
||||||
self.increaseThrottle = function() {
|
self.increaseThrottle = function() {
|
||||||
self.setThrottle(self._throttleFactor + 1);
|
self.setThrottle(self._throttleFactor + 1);
|
||||||
};
|
};
|
||||||
|
|
@ -156,7 +154,6 @@ function DataUpdater(allViewModels) {
|
||||||
var type = event.data["type"];
|
var type = event.data["type"];
|
||||||
var payload = event.data["payload"];
|
var payload = event.data["payload"];
|
||||||
var html = "";
|
var html = "";
|
||||||
var format = {};
|
|
||||||
|
|
||||||
log.debug("Got event " + type + " with payload: " + JSON.stringify(payload));
|
log.debug("Got event " + type + " with payload: " + JSON.stringify(payload));
|
||||||
|
|
||||||
|
|
@ -164,91 +161,6 @@ function DataUpdater(allViewModels) {
|
||||||
if (payload && payload.hasOwnProperty("config_hash")) {
|
if (payload && payload.hasOwnProperty("config_hash")) {
|
||||||
self._configHash = payload.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 = "<p>" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "</p>";
|
|
||||||
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + payload.error + '</pre>');
|
|
||||||
|
|
||||||
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") {
|
} else if (type == "SlicingStarted") {
|
||||||
gcodeUploadProgress.addClass("progress-striped").addClass("active");
|
gcodeUploadProgress.addClass("progress-striped").addClass("active");
|
||||||
gcodeUploadProgressBar.css("width", "100%");
|
gcodeUploadProgressBar.css("width", "100%");
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ $(function() {
|
||||||
|
|
||||||
self.loginState = parameters[0];
|
self.loginState = parameters[0];
|
||||||
|
|
||||||
|
self.timelapsePopup = undefined;
|
||||||
|
|
||||||
self.defaultFps = 25;
|
self.defaultFps = 25;
|
||||||
self.defaultPostRoll = 0;
|
self.defaultPostRoll = 0;
|
||||||
self.defaultInterval = 10;
|
self.defaultInterval = 10;
|
||||||
|
|
@ -222,11 +224,88 @@ $(function() {
|
||||||
.done(self.fromResponse);
|
.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.onDataUpdaterReconnect = function() {
|
||||||
self.requestData();
|
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 = "<p>" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "</p>";
|
||||||
|
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + payload.error + '</pre>');
|
||||||
|
|
||||||
|
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();
|
self.requestData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ def get_free_bytes(path):
|
||||||
return psutil.disk_usage(path).free
|
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
|
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.
|
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.
|
Optional.
|
||||||
extension (string): The .3 file extension to use for the generated filename. If not provided, the extension of
|
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.
|
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`.
|
kwargs (dict): Additional keyword arguments to provide to :func:`find_collision_free_name`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -228,16 +230,43 @@ def get_dos_filename(origin, existing_filenames=None, extension=None, **kwargs):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: No 8.3 compatible name could be found that doesn't collide with the provided ``existing_filenames``.
|
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
|
return None
|
||||||
|
|
||||||
if existing_filenames is None:
|
if existing_filenames is None:
|
||||||
existing_filenames = []
|
existing_filenames = []
|
||||||
|
|
||||||
filename, ext = os.path.splitext(origin)
|
if extension is not None:
|
||||||
if extension is 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
|
extension = ext
|
||||||
|
|
||||||
return find_collision_free_name(filename, extension, existing_filenames, **kwargs)
|
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:
|
Raises:
|
||||||
ValueError: No collision free name could be found.
|
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):
|
if not isinstance(filename, unicode):
|
||||||
filename = unicode(filename)
|
filename = unicode(filename)
|
||||||
|
|
@ -302,14 +358,21 @@ def find_collision_free_name(filename, extension, existing_filenames, max_power=
|
||||||
extension = make_valid(extension)
|
extension = make_valid(extension)
|
||||||
extension = extension[:3] if len(extension) > 3 else 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
|
# early exit
|
||||||
return filename + "." + extension
|
return result
|
||||||
|
|
||||||
counter = 1
|
counter = 1
|
||||||
power = 1
|
power = 1
|
||||||
|
prefix_format = u"{segment}~{counter}"
|
||||||
while counter < (10 ** max_power):
|
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:
|
if result not in existing_filenames:
|
||||||
return result
|
return result
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue