Merge branch 'devel' into dep-up

This commit is contained in:
Kyle Evans 2016-12-27 23:46:39 -06:00
commit 0a08ef0ed4
21 changed files with 257 additions and 49 deletions

View file

@ -286,9 +286,9 @@ Files
- Type
- Description
* - ``hash``
- 1
- 0..1
- String
- MD5 hash of the file
- MD5 hash of the file. Only available for ``local`` files.
* - ``size``
- 0..1
- Number

View file

@ -16,9 +16,14 @@ Retrieve all files
Retrieve information regarding all files currently available and regarding the disk space still available
locally in the system.
By default only returns the files and folders in the root directory. If the query parameter ``recursive``
is provided and set to ``true``, returns all files and folders.
Returns a :ref:`Retrieve response <sec-api-fileops-datamodel-retrieveresponse>`.
**Example**:
**Example 1**:
Fetch only the files and folders from the root folder.
.. sourcecode:: http
@ -35,6 +40,10 @@ Retrieve all files
"files": [
{
"name": "whistle_v2.gcode",
"path": "whistle_v2.gcode",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"hash": "...",
"size": 1468987,
"date": 1378847754,
"origin": "local",
@ -60,15 +69,140 @@ Retrieve all files
},
{
"name": "whistle_.gco",
"path": "whistle_.gco",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"origin": "sdcard",
"refs": {
"resource": "http://example.com/api/files/sdcard/whistle_.gco"
}
},
{
"name": "folderA",
"path": "folderA",
"type": "folder",
"typePath": ["folder"],
"children": [],
"size": 1334
}
],
"free": "3.2GB"
}
**Example 2**
Recursively fetch all files and folders.
Fetch only the files and folders from the root folder.
.. sourcecode:: http
GET /api/files?recursive=true HTTP/1.1
Host: example.com
X-Api-Key: abcdef...
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"files": [
{
"name": "whistle_v2.gcode",
"path": "whistle_v2.gcode",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"hash": "...",
"size": 1468987,
"date": 1378847754,
"origin": "local",
"refs": {
"resource": "http://example.com/api/files/local/whistle_v2.gcode",
"download": "http://example.com/downloads/files/local/whistle_v2.gcode"
},
"gcodeAnalysis": {
"estimatedPrintTime": 1188,
"filament": {
"length": 810,
"volume": 5.36
}
},
"print": {
"failure": 4,
"success": 23,
"last": {
"date": 1387144346,
"success": true
}
}
},
{
"name": "whistle_.gco",
"path": "whistle_.gco",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"origin": "sdcard",
"refs": {
"resource": "http://example.com/api/files/sdcard/whistle_.gco"
}
},
{
"name": "folderA",
"path": "folderA",
"type": "folder",
"typePath": ["folder"],
"children": [
{
"name": "test.gcode",
"path": "folderA/test.gcode",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"hash": "...",
"size": 1234,
"date": 1378847754,
"origin": "local",
"refs": {
"resource": "http://example.com/api/files/local/folderA/test.gcode",
"download": "http://example.com/downloads/files/local/folderA/test.gcode"
}
},
{
"name": "subfolder",
"path": "folderA/subfolder",
"type": "folder",
"typePath": ["folder"],
"children": [
{
"name": "test.gcode",
"path": "folderA/subfolder/test2.gcode",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"hash": "...",
"size": 100,
"date": 1378847754,
"origin": "local",
"refs": {
"resource": "http://example.com/api/files/local/folderA/subfolder/test2.gcode",
"download": "http://example.com/downloads/files/local/folderA/subfolder/test2.gcode"
}
},
],
"size": 100,
"refs": {
"resource": "http://example.com/api/files/local/folderA/subfolder",
}
],
"size": 1334,
"refs": {
"resource": "http://example.com/api/files/local/folderA",
}
}
],
"free": "3.2GB"
}
:param recursive: If set to ``true``, return all files and folders recursively. Otherwise only return items on same level.
:statuscode 200: No error
.. _sec-api-fileops-retrievelocation:
@ -81,6 +215,9 @@ Retrieve files from specific location
Retrieve information regarding the files currently available on the selected `location` and -- if targeting
the ``local`` location -- regarding the disk space still available locally in the system.
By default only returns the files and folders in the root directory. If the query parameter ``recursive``
is provided and set to ``true``, returns all files and folders.
Returns a :ref:`Retrieve response <sec-api-fileops-datamodel-retrieveresponse>`.
**Example**:
@ -100,6 +237,10 @@ Retrieve files from specific location
"files": [
{
"name": "whistle_v2.gcode",
"path": "whistle_v2.gcode",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"hash": "...",
"size": 1468987,
"date": 1378847754,
"origin": "local",
@ -130,6 +271,7 @@ Retrieve files from specific location
:param location: The origin location from which to retrieve the files. Currently only ``local`` and ``sdcard`` are
supported, with ``local`` referring to files stored in OctoPrint's ``uploads`` folder and ``sdcard``
referring to files stored on the printer's SD card (if available).
:param recursive: If set to ``true``, return all files and folders recursively. Otherwise only return items on same level.
:statuscode 200: No error
:statuscode 404: If `location` is neither ``local`` nor ``sdcard``
@ -196,6 +338,8 @@ Upload file or create folder
"local": {
"name": "whistle_v2.gcode",
"path": "whistle_v2.gcode",
"type": "machinecode",
"typePath": ["machinecode", "gcode"],
"origin": "local",
"refs": {
"resource": "http://example.com/api/files/local/whistle_v2.gcode",
@ -324,6 +468,9 @@ Retrieve a specific file's or folder's information
If the file is unknown, a :http:statuscode:`404` is returned.
If the targeted path is a folder, by default only its direct children will be returned. If ``recursive`` is
provided and set to ``true``, all sub folders and their children will be returned too.
On success, a :http:statuscode:`200` is returned, with a :ref:`file information item <sec-api-datamodel-files-file>`
as the response body.
@ -368,7 +515,7 @@ Retrieve a specific file's or folder's information
:param location: The location of the file for which to retrieve the information, either ``local`` or ``sdcard``.
:param filename: The filename of the file for which to retrieve the information
:param children: Whether to include children of folders (true, default) or not (false)
:param recursive: If set to ``true``, return all files and folders recursively. Otherwise only return items on same level.
:statuscode 200: No error
:statuscode 404: If ``target`` is neither ``local`` nor ``sdcard``, ``sdcard`` but SD card support is disabled or the
requested file was not found

View file

@ -25,6 +25,10 @@ SCRIPTNAME=/etc/init.d/$PKGNAME
# Read configuration variable file if it is present
[ -r /etc/default/$PKGNAME ] && . /etc/default/$PKGNAME
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
# Exit if the DAEMON is not set
if [ -z "$DAEMON" ]
then
@ -38,10 +42,6 @@ fi
# Load the VERBOSE setting and other rcS variables
[ -f /etc/default/rcS ] && . /etc/default/rcS
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
if [ -z "$START" -o "$START" != "yes" ]
then
log_warning_msg "Not starting $PKGNAME, edit /etc/default/$PKGNAME to start it."

View file

@ -40,7 +40,8 @@ INSTALL_REQUIRES = [
"feedparser>=5.2.1,<5.3",
"chainmap>=1.0.2,<1.1",
"future>=0.15,<0.16",
"scandir>=1.3,<1.4"
"scandir>=1.3,<1.4",
"websocket-client>=0.40,<0.41"
]
# Additional requirements for optional install options

View file

@ -129,7 +129,7 @@ from .config import config_commands
sources=[server_commands, plugin_commands, dev_commands, client_commands, config_commands])
@standard_options()
@legacy_options
@click.version_option(version=octoprint.__version__)
@click.version_option(version=octoprint.__version__, allow_from_autoenv=False)
@click.pass_context
def octo(ctx, debug, host, port, logging, daemon, pid, allow_root):
@ -146,7 +146,7 @@ def octo(ctx, debug, host, port, logging, daemon, pid, allow_root):
"\"octoprint daemon start|stop|restart\" from now on")
from octoprint.cli.server import daemon_command
ctx.invoke(daemon_command, debug=debug, pid=pid, daemon=daemon, allow_root=allow_root)
ctx.invoke(daemon_command, debug=debug, host=host, port=port, logging=logging, allow_root=allow_root, command=daemon, pid=pid)
else:
click.echo("Starting the server via \"octoprint\" is deprecated, "
"please use \"octoprint serve\" from now on.")

View file

@ -511,7 +511,7 @@ class LocalFileStorage(StorageInterface):
filepath = self.sanitize_path(filepath)
path = self.sanitize_path(path)
return filepath.startswith(path)
return filepath == path or filepath.startswith(path + "/")
def file_exists(self, path):
path, name = self.sanitize(path)
@ -1123,11 +1123,16 @@ class LocalFileStorage(StorageInterface):
# no hidden files and folders
continue
entry_name = entry.name
entry_path = entry.path
entry_is_file = entry.is_file()
entry_is_dir = entry.is_dir()
entry_stat = entry.stat()
try:
entry_name = entry.name
entry_path = entry.path
entry_is_file = entry.is_file()
entry_is_dir = entry.is_dir()
entry_stat = entry.stat()
except:
# error while trying to fetch file metadata, that might be thanks to file already having
# been moved or deleted - ignore it and continue
continue
try:
new_entry_name, new_entry_path = self._sanitize_entry(entry_name, path, entry_path)

View file

@ -125,7 +125,8 @@ def getSettings():
"pollWatched": s.getBoolean(["feature", "pollWatched"]),
"ignoreIdenticalResends": s.getBoolean(["feature", "ignoreIdenticalResends"]),
"modelSizeDetection": s.getBoolean(["feature", "modelSizeDetection"]),
"firmwareDetection": s.getBoolean(["feature", "firmwareDetection"])
"firmwareDetection": s.getBoolean(["feature", "firmwareDetection"]),
"printCancelConfirmation": s.getBoolean(["feature", "printCancelConfirmation"])
},
"serial": {
"port": connectionOptions["portPreference"],
@ -309,6 +310,7 @@ def _saveSettings(data):
if "ignoreIdenticalResends" in data["feature"]: s.setBoolean(["feature", "ignoreIdenticalResends"], data["feature"]["ignoreIdenticalResends"])
if "modelSizeDetection" in data["feature"]: s.setBoolean(["feature", "modelSizeDetection"], data["feature"]["modelSizeDetection"])
if "firmwareDetection" in data["feature"]: s.setBoolean(["feature", "firmwareDetection"], data["feature"]["firmwareDetection"])
if "printCancelConfirmation" in data["feature"]: s.setBoolean(["feature", "printCancelConfirmation"], data["feature"]["printCancelConfirmation"])
if "serial" in data.keys():
if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"])

View file

@ -1297,6 +1297,7 @@ def collect_core_assets(enable_gcodeviewer=True, preferred_stylesheet="css"):
'js/app/bindings/slimscrolledforeach.js',
'js/app/bindings/toggle.js',
'js/app/bindings/togglecontent.js',
'js/app/bindings/valuewithinit.js',
'js/app/viewmodels/appearance.js',
'js/app/viewmodels/connection.js',
'js/app/viewmodels/control.js',

View file

@ -200,7 +200,8 @@ default_settings = {
"identicalResendsCountdown": 7,
"supportFAsCommand": False,
"modelSizeDetection": True,
"firmwareDetection": True
"firmwareDetection": True,
"printCancelConfirmation": True
},
"folder": {
"uploads": None,

View file

@ -0,0 +1,11 @@
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var observable = valueAccessor();
var value = element.value;
observable(value);
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor, context);
},
update: ko.bindingHandlers.value.update
};

View file

@ -478,6 +478,17 @@ $(function() {
// reload overlay
$("#reloadui_overlay_reload").click(function() { location.reload(); });
var changeTab = function()
{
var hashtag = window.location.hash;
var tab = $('#tabs a[href="' + hashtag + '"]');
if (tab.length)
{
tab.tab("show");
onTabChange(hashtag);
}
}
//~~ view model binding
var bindViewModels = function() {
@ -566,6 +577,15 @@ $(function() {
callViewModels(allViewModels, "onBrowserTabVisibilityChange", [status]);
});
$(window).on("hashchange", function() {
changeTab();
});
if (window.location.hash != "")
{
changeTab();
}
log.info("Application startup complete");
};

View file

@ -708,11 +708,12 @@ $(function() {
return false;
}
if (entry["type"] == "folder" && entry["children"]) {
var success = entry["name"].toLocaleLowerCase().indexOf(query) > -1;
if (!success && entry["type"] == "folder" && entry["children"]) {
return _.any(entry["children"], recursiveSearch);
} else {
return entry["name"].toLocaleLowerCase().indexOf(query) > -1;
}
return success;
};
self.listHelper.changeSearchFunction(recursiveSearch);

View file

@ -76,6 +76,10 @@ $(function() {
self.loginUser("");
self.loginPass("");
self.loginRemember(false);
if (history && history.replaceState) {
history.replaceState({success: true}, document.title, window.location.pathname);
}
})
.fail(function() {
new PNotify({title: gettext("Login failed"), text: gettext("User unknown or wrong password"), type: "error"});
@ -95,17 +99,18 @@ $(function() {
});
};
self.onLoginUserKeyup = function(data, event) {
if (event.keyCode == 13) {
self.elementPasswordInput.focus();
self.prepareLogin = function(data, event) {
if(event && event.preventDefault) {
event.preventDefault();
}
self.login();
};
self.onLoginPassKeyup = function(data, event) {
if (event.keyCode == 13) {
self.login();
self.onKeyUp = function(data, event) {
if (event && event.keyCode == 13) {
$('#loginForm').submit();
}
};
}
self.onAllBound = function(allViewModels) {
self.allViewModels = allViewModels;

View file

@ -3,6 +3,7 @@ $(function() {
var self = this;
self.loginState = parameters[0];
self.settings = parameters[1];
self.stateString = ko.observable(undefined);
self.isErrorOrClosed = ko.observable(undefined);
@ -288,18 +289,22 @@ $(function() {
};
self.cancel = function() {
showConfirmationDialog({
message: gettext("This will cancel your print."),
onproceed: function() {
OctoPrint.job.cancel();
}
});
if (!self.settings.feature_printCancelConfirmation()) {
OctoPrint.job.cancel();
} else {
showConfirmationDialog({
message: gettext("This will cancel your print."),
onproceed: function() {
OctoPrint.job.cancel();
}
});
};
};
}
OCTOPRINT_VIEWMODELS.push([
PrinterStateViewModel,
["loginStateViewModel"],
["loginStateViewModel", "settingsViewModel"],
["#state_wrapper", "#drop_overlay"]
]);
});

View file

@ -138,6 +138,7 @@ $(function() {
self.feature_ignoreIdenticalResends = ko.observable(undefined);
self.feature_modelSizeDetection = ko.observable(undefined);
self.feature_firmwareDetection = ko.observable(undefined);
self.feature_printCancelConfirmation = ko.observable(undefined);
self.serial_port = ko.observable();
self.serial_baudrate = ko.observable();

View file

@ -13,6 +13,13 @@
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_printCancelConfirmation" id="settings-printCancelConfirmation"> {{ _('Confirm before cancelling a print') }}
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
@ -34,7 +41,6 @@
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">

View file

@ -3,14 +3,16 @@
<b class="caret"></b>
</a>
<div id="login_dropdown_loggedout" style="padding: 15px" class="dropdown-menu" data-bind="css: {hide: loginState.loggedIn(), 'dropdown-menu': !loginState.loggedIn()}">
<label for="login_user">{{ _('Username') }}</label>
<input type="text" id="login_user" data-bind="value: loginState.loginUser, event: {'keyup': loginState.onLoginUserKeyup}" placeholder="{{ _('Username') }}" autocapitalize="none">
<label for="login_pass">{{ _('Password') }}</label>
<input type="password" id="login_pass" data-bind="value: loginState.loginPass, event: {'keyup': loginState.onLoginPassKeyup}" placeholder="{{ _('Password') }}">
<label class="checkbox">
<input type="checkbox" id="login_remember" data-bind="checked: loginState.loginRemember"> {{ _('Remember me') }}
</label>
<button class="btn btn-block btn-primary" id="login_button" data-bind="click: function() { loginState.login(); }">{{ _('Login') }}</button>
<form id="loginForm" data-bind="event: {'submit': loginState.prepareLogin }" autocomplete="on">
<label for="login_user">{{ _('Username') }}</label>
<input type="text" id="login_user" name="username" data-bind="valueWithInit: loginState.loginUser, event: {'keyup': loginState.onKeyUp }" placeholder="{{ _('Username') }}" autocapitalize="none" autocomplete="on">
<label for="login_pass">{{ _('Password') }}</label>
<input type="password" id="login_pass" name="password" data-bind="valueWithInit: loginState.loginPass, event: {'keyup': loginState.onKeyUp }" placeholder="{{ _('Password') }}" autocomplete="on">
<label class="checkbox">
<input type="checkbox" id="login_remember" data-bind="checked: loginState.loginRemember"> {{ _('Remember me') }}
</label>
<button type="submit" class="btn btn-block btn-primary" id="login_button">{{ _('Login') }}</button>
</form>
</div>
<ul id="login_dropdown_loggedin" class="hide" data-bind="css: {hide: !loginState.loggedIn(), 'dropdown-menu': loginState.loggedIn()}">
<li><a href="#" id="usersettings_button" data-bind="click: function() { usersettings.show(); }">{{ _('User Settings') }}</a></li>

View file

@ -2714,11 +2714,11 @@ msgstr "Nutze diese Einstellung um zusätzliche Baudraten zur Verbindung zu konf
#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:98
msgid "Not only cancel ongoing prints but also disconnect on unhandled errors from the firmware."
msgstr "Bei unbehalten Firmwarefehlern nicht nur den Druckauftrag abbrechen, sondern auch die Verbindung zum Drucker trennen."
msgstr "Bei unbehandelten Firmwarefehlern nicht nur den Druckauftrag abbrechen, sondern auch die Verbindung zum Drucker trennen."
#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:105
msgid "Ignore any unhandled errors from the firmware. Only use this if your firmware sends stuff prefixed with \"Error\" that is not an actual error. Might mask printer issues, be careful!"
msgstr "Alle unbehalten Firmwarefehler ignorieren. Nur nutzen wenn Deine Firmware Dinge mit \"Error\" sendet die nicht wirklich Fehler sind. Könnte Druckerprobleme maskieren, vorsicht!"
msgstr "Alle unbehandelten Firmwarefehler ignorieren. Nur nutzen wenn Deine Firmware Dinge mit \"Error\" sendet die nicht wirklich Fehler sind. Könnte Druckerprobleme maskieren, vorsicht!"
#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:113
msgid "Command to send to the firmware on first handshake attempt."

View file

@ -2714,11 +2714,11 @@ msgstr "Nutze diese Einstellung um zusätzliche Baudraten zur Verbindung zu konf
#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:98
msgid "Not only cancel ongoing prints but also disconnect on unhandled errors from the firmware."
msgstr "Bei unbehalten Firmwarefehlern nicht nur den Druckauftrag abbrechen, sondern auch die Verbindung zum Drucker trennen."
msgstr "Bei unbehandelten Firmwarefehlern nicht nur den Druckauftrag abbrechen, sondern auch die Verbindung zum Drucker trennen."
#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:105
msgid "Ignore any unhandled errors from the firmware. Only use this if your firmware sends stuff prefixed with \"Error\" that is not an actual error. Might mask printer issues, be careful!"
msgstr "Alle unbehalten Firmwarefehler ignorieren. Nur nutzen wenn Deine Firmware Dinge mit \"Error\" sendet die nicht wirklich Fehler sind. Könnte Druckerprobleme maskieren, vorsicht!"
msgstr "Alle unbehandelten Firmwarefehler ignorieren. Nur nutzen wenn Deine Firmware Dinge mit \"Error\" sendet die nicht wirklich Fehler sind. Könnte Druckerprobleme maskieren, vorsicht!"
#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:113
msgid "Command to send to the firmware on first handshake attempt."