Merge branch 'maintenance' into devel
Conflicts: src/octoprint/filemanager/storage.py src/octoprint/templates/sidebar/state.jinja2
This commit is contained in:
commit
79c95aa3b4
8 changed files with 174 additions and 28 deletions
|
|
@ -404,7 +404,7 @@ class LocalFileStorage(StorageInterface):
|
|||
|
||||
from slugify import Slugify
|
||||
self._slugify = Slugify()
|
||||
self._slugify.safe_chars = "-_.() "
|
||||
self._slugify.safe_chars = "-_.()[] "
|
||||
|
||||
self._old_metadata = None
|
||||
self._initialize_metadata()
|
||||
|
|
@ -779,7 +779,11 @@ class LocalFileStorage(StorageInterface):
|
|||
if "/" in name or "\\" in name:
|
||||
raise ValueError("name must not contain / or \\")
|
||||
|
||||
return self._slugify(name).replace(" ", "_")
|
||||
result = self._slugify(name).replace(" ", "_")
|
||||
if result and result != "." and result != ".." and result[0] == ".":
|
||||
# hidden files under *nix
|
||||
result = result[1:]
|
||||
return result
|
||||
|
||||
def sanitize_path(self, path):
|
||||
"""
|
||||
|
|
@ -787,8 +791,11 @@ class LocalFileStorage(StorageInterface):
|
|||
relative path elements (e.g. ``..``) and sanitizes folder names using :func:`sanitize_name`. Final path is the
|
||||
absolute path including leading ``basefolder`` path.
|
||||
"""
|
||||
if path[0] == "/" or path[0] == ".":
|
||||
if path[0] == "/":
|
||||
path = path[1:]
|
||||
elif path[0] == "." and path[1] == "/":
|
||||
path = path[2:]
|
||||
|
||||
path_elements = path.split("/")
|
||||
joined_path = self.basefolder
|
||||
for path_element in path_elements:
|
||||
|
|
@ -1052,6 +1059,28 @@ class LocalFileStorage(StorageInterface):
|
|||
entry_path = os.path.join(path, entry)
|
||||
path_in_location = entry if not base else base + entry
|
||||
|
||||
sanitized = self.sanitize_name(entry)
|
||||
if sanitized != entry:
|
||||
# entry is not sanitized yet, let's take care of that
|
||||
sanitized_path = os.path.join(path, sanitized)
|
||||
sanitized_name, sanitized_ext = os.path.splitext(sanitized)
|
||||
|
||||
counter = 1
|
||||
while os.path.exists(sanitized_path):
|
||||
counter += 1
|
||||
sanitized = self.sanitize_name("{}_({}){}".format(sanitized_name, counter, sanitized_ext))
|
||||
sanitized_path = os.path.join(path, sanitized)
|
||||
|
||||
try:
|
||||
shutil.move(entry_path, sanitized_path)
|
||||
|
||||
self._logger.info("Sanitized \"{}\" to \"{}\"".format(entry_path, sanitized_path))
|
||||
entry = sanitized
|
||||
entry_path = sanitized_path
|
||||
except:
|
||||
self._logger.exception("Error while trying to rename \"{}\" to \"{}\", ignoring file".format(entry_path, sanitized_path))
|
||||
continue
|
||||
|
||||
# file handling
|
||||
if os.path.isfile(entry_path):
|
||||
type_path = octoprint.filemanager.get_file_type(entry)
|
||||
|
|
|
|||
|
|
@ -796,15 +796,16 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback):
|
|||
printTimeLeft = dumbTotalPrintTime - cleanedPrintTime
|
||||
printTimeLeftOrigin = "linear"
|
||||
|
||||
elif progress > self._timeEstimationForceDumbFromPercent or \
|
||||
cleanedPrintTime * 60 >= self._timeEstimationForceDumbAfterMin:
|
||||
# more than x% or y min printed and still no real estimate, ok, we'll use the dumb variant :/
|
||||
printTimeLeft = dumbTotalPrintTime - cleanedPrintTime
|
||||
else:
|
||||
printTimeLeftOrigin = "linear"
|
||||
if progress > self._timeEstimationForceDumbFromPercent or \
|
||||
cleanedPrintTime >= self._timeEstimationForceDumbAfterMin * 60:
|
||||
# more than x% or y min printed and still no real estimate, ok, we'll use the dumb variant :/
|
||||
printTimeLeft = dumbTotalPrintTime - cleanedPrintTime
|
||||
|
||||
if printTimeLeft < 0:
|
||||
if printTimeLeft is not None and printTimeLeft < 0:
|
||||
# shouldn't actually happen, but let's make sure
|
||||
return None, None
|
||||
printTimeLeft = None
|
||||
|
||||
return printTimeLeft, printTimeLeftOrigin
|
||||
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ function bytesFromSize(size) {
|
|||
|
||||
function formatDuration(seconds) {
|
||||
if (!seconds) return "-";
|
||||
if (seconds < 0) return "00:00:00";
|
||||
if (seconds < 1) return "00:00:00";
|
||||
|
||||
var s = seconds % 60;
|
||||
var m = (seconds % 3600) / 60;
|
||||
|
|
@ -373,8 +373,7 @@ function formatDuration(seconds) {
|
|||
}
|
||||
|
||||
function formatFuzzyEstimation(seconds, base) {
|
||||
if (!seconds) return "-";
|
||||
if (seconds < 0) return "-";
|
||||
if (!seconds || seconds < 1) return "-";
|
||||
|
||||
var m;
|
||||
if (base != undefined) {
|
||||
|
|
@ -387,6 +386,118 @@ function formatFuzzyEstimation(seconds, base) {
|
|||
return m.fromNow(true);
|
||||
}
|
||||
|
||||
function formatFuzzyPrintTime(totalSeconds) {
|
||||
/**
|
||||
* Formats a print time estimate in a very fuzzy way.
|
||||
*
|
||||
* Accuracy decreases the higher the estimation is:
|
||||
*
|
||||
* * less than 30s: "a couple of seconds"
|
||||
* * 30s to a minute: "less than a minute"
|
||||
* * 1 to 30min: rounded to full minutes, above 30s is minute + 1 ("27 minutes", "2 minutes")
|
||||
* * 30min to 40min: "40 minutes"
|
||||
* * 40min to 50min: "50 minutes"
|
||||
* * 50min to 1h: "1 hour"
|
||||
* * 1 to 12h: rounded to half hours, 15min to 45min is ".5", above that hour + 1 ("4 hours", "2.5 hours")
|
||||
* * 12 to 24h: rounded to full hours, above 30min is hour + 1, over 23.5h is "1 day"
|
||||
* * Over a day: rounded to half days, 8h to 16h is ".5", above that days + 1 ("1 day", "4 days", "2.5 days")
|
||||
*/
|
||||
|
||||
if (!totalSeconds || totalSeconds < 1) return "-";
|
||||
|
||||
var d = moment.duration(totalSeconds, "seconds");
|
||||
|
||||
var seconds = d.seconds();
|
||||
var minutes = d.minutes();
|
||||
var hours = d.hours();
|
||||
var days = d.asDays();
|
||||
|
||||
var replacements = {
|
||||
days: days,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds,
|
||||
totalSeconds: totalSeconds
|
||||
};
|
||||
|
||||
var text = "-";
|
||||
|
||||
if (days >= 1) {
|
||||
// days
|
||||
if (hours >= 16) {
|
||||
replacements.days += 1;
|
||||
text = gettext("%(days)d days");
|
||||
} else if (hours >= 8 && hours < 16) {
|
||||
text = gettext("%(days)d.5 days");
|
||||
} else {
|
||||
if (days == 1) {
|
||||
text = gettext("%(days)d day");
|
||||
} else {
|
||||
text = gettext("%(days)d days");
|
||||
}
|
||||
}
|
||||
} else if (hours >= 1) {
|
||||
// only hours
|
||||
if (hours < 12) {
|
||||
if (minutes < 15) {
|
||||
// less than .15 => .0
|
||||
if (hours == 1) {
|
||||
text = gettext("%(hours)d hour");
|
||||
} else {
|
||||
text = gettext("%(hours)d hours");
|
||||
}
|
||||
} else if (minutes >= 15 && minutes < 45) {
|
||||
// between .25 and .75 => .5
|
||||
text = gettext("%(hours)d.5 hours");
|
||||
} else {
|
||||
// over .75 => hours + 1
|
||||
replacements.hours += 1;
|
||||
text = gettext("%(hours)d hours");
|
||||
}
|
||||
} else {
|
||||
if (hours == 23 && minutes > 30) {
|
||||
// over 23.5 hours => 1 day
|
||||
text = gettext("1 day");
|
||||
} else {
|
||||
if (minutes > 30) {
|
||||
// over .5 => hours + 1
|
||||
replacements.hours += 1;
|
||||
}
|
||||
text = gettext("%(hours)d hours");
|
||||
}
|
||||
}
|
||||
} else if (minutes >= 1) {
|
||||
// only minutes
|
||||
if (minutes < 2) {
|
||||
if (seconds < 30) {
|
||||
text = gettext("a minute");
|
||||
} else {
|
||||
text = gettext("2 minutes");
|
||||
}
|
||||
} else if (minutes < 30) {
|
||||
if (seconds > 30) {
|
||||
replacements.minutes += 1;
|
||||
}
|
||||
text = gettext("%(minutes)d minutes");
|
||||
} else if (minutes <= 40) {
|
||||
text = gettext("40 minutes");
|
||||
} else if (minutes <= 50) {
|
||||
text = gettext("50 minutes");
|
||||
} else {
|
||||
text = gettext("1 hour");
|
||||
}
|
||||
} else {
|
||||
// only seconds
|
||||
if (seconds < 30) {
|
||||
text = gettext("a couple of seconds");
|
||||
} else {
|
||||
text = gettext("less than a minute");
|
||||
}
|
||||
}
|
||||
|
||||
return _.sprintf(text, replacements);
|
||||
}
|
||||
|
||||
function formatDate(unixTimestamp) {
|
||||
if (!unixTimestamp) return "-";
|
||||
return moment.unix(unixTimestamp).format(gettext(/* L10N: Date format */ "YYYY-MM-DD HH:mm"));
|
||||
|
|
|
|||
|
|
@ -559,7 +559,7 @@ $(function() {
|
|||
}
|
||||
}
|
||||
}
|
||||
output += gettext("Estimated Print Time") + ": " + formatDuration(data["gcodeAnalysis"]["estimatedPrintTime"]) + "<br>";
|
||||
output += gettext("Estimated Print Time") + ": " + formatFuzzyPrintTime(data["gcodeAnalysis"]["estimatedPrintTime"]) + "<br>";
|
||||
}
|
||||
if (data["prints"] && data["prints"]["last"]) {
|
||||
output += gettext("Last Printed") + ": " + formatTimeAgo(data["prints"]["last"]["date"]) + "<br>";
|
||||
|
|
|
|||
|
|
@ -433,7 +433,7 @@ $(function() {
|
|||
} else {
|
||||
var output = [];
|
||||
output.push(gettext("Model size") + ": " + model.width.toFixed(2) + "mm × " + model.depth.toFixed(2) + "mm × " + model.height.toFixed(2) + "mm");
|
||||
output.push(gettext("Estimated total print time") + ": " + formatFuzzyEstimation(model.printTime));
|
||||
output.push(gettext("Estimated total print time") + ": " + formatFuzzyPrintTime(model.printTime));
|
||||
output.push(gettext("Estimated layer height") + ": " + model.layerHeight.toFixed(2) + gettext("mm"));
|
||||
output.push(gettext("Layer count") + ": " + model.layersPrinted.toFixed(0) + " " + gettext("printed") + ", " + model.layersTotal.toFixed(0) + " " + gettext("visited"));
|
||||
|
||||
|
|
@ -471,7 +471,7 @@ $(function() {
|
|||
}
|
||||
}
|
||||
}
|
||||
output.push(gettext("Print time for layer") + ": " + formatFuzzyEstimation(layer.printTime));
|
||||
output.push(gettext("Print time for layer") + ": " + formatFuzzyPrintTime(layer.printTime));
|
||||
|
||||
self.ui_layerInfo(output.join("<br>"));
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ $(function() {
|
|||
|
||||
self.estimatedPrintTimeString = ko.pureComputed(function() {
|
||||
if (self.lastPrintTime())
|
||||
return formatFuzzyEstimation(self.lastPrintTime());
|
||||
return formatFuzzyPrintTime(self.lastPrintTime());
|
||||
if (self.estimatedPrintTime())
|
||||
return formatFuzzyEstimation(self.estimatedPrintTime());
|
||||
return formatFuzzyPrintTime(self.estimatedPrintTime());
|
||||
return "-";
|
||||
});
|
||||
self.byteString = ko.pureComputed(function() {
|
||||
|
|
@ -79,17 +79,17 @@ $(function() {
|
|||
if (!self.printTime() || !(self.isPrinting() || self.isPaused())) {
|
||||
return "-";
|
||||
} else {
|
||||
return gettext("Calculating...");
|
||||
return gettext("Still stabilizing...");
|
||||
}
|
||||
} else {
|
||||
return formatFuzzyEstimation(self.printTimeLeft());
|
||||
return formatFuzzyPrintTime(self.printTimeLeft());
|
||||
}
|
||||
});
|
||||
self.printTimeLeftOriginString = ko.pureComputed(function() {
|
||||
var value = self.printTimeLeftOrigin();
|
||||
switch (value) {
|
||||
case "linear": {
|
||||
return gettext("Based on a linear approximation (accuracy highly dependent on the model)");
|
||||
return gettext("Based on a linear approximation (very low accuracy, especially at the beginning of the print)");
|
||||
}
|
||||
case "analysis": {
|
||||
return gettext("Based on the estimate from analysis of file (medium accuracy)");
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
{{ _('Machine State') }}: <strong data-bind="text: stateString"></strong><br>
|
||||
<span title="{{ _('Current printer state') }}">{{ _('State') }}</span>: <strong data-bind="text: stateString"></strong><br>
|
||||
<hr>
|
||||
{{ _('File') }}: <strong data-bind="text: filename, attr: {title: filepath}"></strong> <strong data-bind="visible: sd">(SD)</strong><br>
|
||||
{{ _('Timelapse') }}: <strong data-bind="text: timelapseString"></strong><br>
|
||||
<span title="{{ _('Name of file currently selected for printing') }}">{{ _('File') }}</span>: <strong data-bind="text: filename, attr: {title: filepath}"></strong> <strong data-bind="visible: sd">(SD)</strong><br>
|
||||
<span title="{{ _('Current timelapse configuration') }}">{{ _('Timelapse') }}</span>: <strong data-bind="text: timelapseString"></strong><br>
|
||||
<!-- ko foreach: filament -->
|
||||
<span data-bind="text: 'Filament (' + name() + '): '"></span><strong data-bind="text: formatFilament(data())"></strong><br>
|
||||
<span data-bind="text: 'Filament (' + name() + '): ', title: 'Filament usage for ' + name()"></span><strong data-bind="text: formatFilament(data())"></strong><br>
|
||||
<!-- /ko -->
|
||||
{{ _('Approx. Total Print Time') }}: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
|
||||
<span title="{{ _('Estimated total print time base on statical analysis or past prints') }}">{{ _('Approx. Total Print Time') }}</span>: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
|
||||
<hr>
|
||||
{{ _('Print Time') }}: <strong data-bind="text: printTimeString"></strong><br>
|
||||
{{ _('Print Time Left') }}: <strong data-bind="text: printTimeLeftString"></strong> <span id="state_printtimeleft_popover" style="display: none" data-bind="visible: printTimeLeftOrigin, attr: {title: printTimeLeftOriginString}, css: printTimeLeftOriginClass">●</span><br>
|
||||
{{ _('Printed') }}: <strong data-bind="text: byteString"></strong><br>
|
||||
<span title="{{ _('Total print time so far') }}">{{ _('Print Time') }}</span>: <strong data-bind="text: printTimeString"></strong><br>
|
||||
<span title="{{ _('Estimated time until the print job is done. This is only an estimate and accuracy depends heavily on various factors!') }}">{{ _('Print Time Left') }}</span>: <strong data-bind="text: printTimeLeftString"></strong> <span id="state_printtimeleft_popover" style="display: none" data-bind="visible: printTimeLeftOrigin, attr: {title: printTimeLeftOriginString}, css: printTimeLeftOriginClass">●</span><br>
|
||||
<span title="{{ _('Bytes printed vs total bytes of file') }}">{{ _('Printed') }}</span>: <strong data-bind="text: byteString"></strong><br>
|
||||
|
||||
|
||||
<div class="progress progress-text-centered">
|
||||
<span class="progress-text-back" data-bind="text: progressBarString()"></span>
|
||||
|
|
|
|||
|
|
@ -1674,6 +1674,10 @@ class MachineCom(object):
|
|||
with self._sendNextLock:
|
||||
while self._active:
|
||||
# we loop until we've actually enqueued a line for sending
|
||||
if self._state != self.STATE_PRINTING:
|
||||
# we are no longer printing, return false
|
||||
return False
|
||||
|
||||
line = self._getNext()
|
||||
if line is None:
|
||||
# end of file, return false
|
||||
|
|
|
|||
Loading…
Reference in a new issue