Merge branch 'maintenance' into devel

Conflicts:
	src/octoprint/filemanager/storage.py
	src/octoprint/templates/sidebar/state.jinja2
This commit is contained in:
Gina Häußge 2016-08-16 09:34:49 +02:00
commit 79c95aa3b4
8 changed files with 174 additions and 28 deletions

View file

@ -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)

View file

@ -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

View file

@ -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"));

View file

@ -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>";

View file

@ -433,7 +433,7 @@ $(function() {
} else {
var output = [];
output.push(gettext("Model size") + ": " + model.width.toFixed(2) + "mm &times; " + model.depth.toFixed(2) + "mm &times; " + 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>"));

View file

@ -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)");

View file

@ -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>&nbsp;<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>&nbsp;<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">&#9679;</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">&#9679;</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>

View file

@ -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