Merge branch 'maintenance' into devel

Conflicts:
	CHANGELOG.md
This commit is contained in:
Gina Häußge 2016-02-10 16:46:56 +01:00
commit e28ca60f21
13 changed files with 1244 additions and 567 deletions

View file

@ -14,11 +14,11 @@ master
HEAD
\(detached.*
# maintenance is currently the branch for preparation of maintenance release 1.2.9
# maintenance is currently the branch for preparation of maintenance release 1.2.10
# so are any fix/... and improve/... branches
maintenance 1.2.9 dedadbc9ac0305799e94ae279d3bca131629c4c5 pep440-dev
fix/.* 1.2.9 dedadbc9ac0305799e94ae279d3bca131629c4c5 pep440-dev
improve/.* 1.2.9 dedadbc9ac0305799e94ae279d3bca131629c4c5 pep440-dev
maintenance 1.2.10 abe68adac8f465a31bf4f3f3933190c1fc242cda pep440-dev
fix/.* 1.2.10 abe68adac8f465a31bf4f3f3933190c1fc242cda pep440-dev
improve/.* 1.2.10 abe68adac8f465a31bf4f3f3933190c1fc242cda pep440-dev
# every other branch is a development branch and thus gets resolved to 1.3.0-dev for now
.* 1.3.0 198d3450d94be1a2 pep440-dev

View file

@ -69,6 +69,45 @@
* [#1047](https://github.com/foosel/OctoPrint/issues/1047) - Fixed 90 degree
webcam rotation for iOS Safari.
## 1.2.9 (2016-02-10)
### Improvements
* [#318](https://github.com/foosel/OctoPrint/issues/318) - Snapshots for timelapses are now named in a non-colliding, job-based way, allowing a new timelapse to start while the other is still being rendered (although printing with an active timelapse rendering job is not recommended and will be solved with a proper render job queue in a later version). Timelapses that were not successfully rendered are kept for 7 days (configurable, although not via the UI so far) and can be manually rendered or deleted through a new UI component within the timelapse tab that shows up if unrendered timelapses are detected.
* [#485](https://github.com/foosel/OctoPrint/issues/485) - "Timelapse rendering" notification is now persistent, even across reloads/client switches. That should make it easier to see that a rendering job is currently in progress.
* [#939](https://github.com/foosel/OctoPrint/issues/939) - Updated to Knockout 3.4.0
* [#1204](https://github.com/foosel/OctoPrint/issues/1204) - Display total print time as estimated by GCODE viewer on GCODE viewer tab. That will allow access to an estimate even if the server hadn't yet calculated that when a print started. Note that due to slightly different implementation server and client side the resulting estimate might differ.
* OctoPrint now serves an intermediary page upon start that informs the user about the server still starting up. Once the server is detected as running, the page automatically switches to the standard interface.
* OctoPrint now displays a link to the release notes of an updated component in the update notification, the update confirmation and the version overview in the settings dialog. Please always make sure to at least skim over the release notes for new OctoPrint releases, they might contain important information that you need to know before updating.
* Improved initial page loading speeds by introducing a preemptive cache. OctoPrint will now record how you access it and on server start pre-render the page so it's ideally available in the server-side cache when you try to access it.
* Initialize login user name and password with an empty string and clear both on successful login (see [#1175](https://github.com/foosel/OctoPrint/pull/1175)).
* Added a "Refresh" button to the file list for people who modify the stored files externally (doing this is not encouraged however due to reasons of book keeping, e.g. metadata tracking etc).
* "Save" button on settings dialog is now disabled while background tasks (getting or receiving config data from the backend) are in progress.
* Improved performance of terminal tab on lower powered clients. Adaptive rate limiting now ensures the server backs off with log updates if the client can't process them fast enough. If the client is really slow, log updates get disabled automatically during printing. This behaviour can be disabled with override buttons in the terminal tab's advanced options if necessary.
* Added option to ignore any unhandled errors reported by the firmware and another option to only cancel ongoing prints on unhandled errors from the firmware (instead of instant disconnect that so far was the default).
* Made version compatibility check PEP440 compliant (important for plugin authors).
* Do not hiccup on manually sent `M28` commands.
* Persist print recovery data on print failures (origin and name of printed file, position in file when print was aborted, time and date of print failure). Currently this data isn't used anywhere, but it [can be accessed from plugins in order to add recovery functionality](https://github.com/foosel/OctoPrint-PrintRecoveryPoc) to OctoPrint.
* Small performance improvements in update checks.
* The file upload dialog will now only display files having an extension that's supported for upload (if the browser supports it, also see [#1196](https://github.com/foosel/OctoPrint/issues/1196)).
### Bug Fixes
* [#1007](https://github.com/foosel/OctoPrint/issues/1007) - Don't enable the "Print" button if no print job is selected.
* [#1181](https://github.com/foosel/OctoPrint/issues/1181) - Properly slugify UTF-8 only file names.
* [#1196](https://github.com/foosel/OctoPrint/issues/1196) - Do not show drag-n-drop overlay if server is offline.
* [#1208](https://github.com/foosel/OctoPrint/issues/1208) - Fixed `retraction_combing` profile setting being incorrectly used by bundled Cura plugin (see [#1209](https://github.com/foosel/OctoPrint/pull/1209))
* Fixed OctoPrint compatibility check in the plugin manager, could report `False` for development versions against certain versions of Python's `setuptools` (thanks to @ignaworm who stumbled over this).
* Fixed a missing parameter in `PluginSettings.remove` call (see [#1177](https://github.com/foosel/OctoPrint/pull/1177)).
* Docs: Fixed the example for a custom `M114` control to also match negative coordinates.
* Reset scroll position in settings dialog properly when re-opening it or switching tabs.
* Fixed an issue that prevented system menu entries that were added to a so far empty system menu make the menu show up.
* Fixed an issue that made requests to restricted resources fail even though the first run wizard had been completed successfully.
* Fixed an issue where an unknown command or the suppression of a command could cause the communication to stall until a communication timeout was triggered.
* Strip [unwanted ANSI characters](https://github.com/pypa/pip/issues/3418) from output produced by pip versions 8.0.0, 8.0.1 and 8.0.3 that prevents our plugin installation detection from working correctly.
([Commits](https://github.com/foosel/OctoPrint/compare/1.2.8...1.2.9))
## 1.2.8 (2015-12-07)
### Notes for Upgraders

View file

@ -13,6 +13,8 @@ or **[creating pull requests](#pull-requests)**.
* [Where can I find those log files you keep talking about?](#where-can-i-find-those-log-files-you-keep-talking-about)
* [Where can I find my browser's error console?](#where-can-i-find-my-browsers-error-console)
* [Pull requests](#pull-requests)
* [What do the branches mean?](#what-do-the-branches-mean)
* [How OctoPrint is versioned](#how-octoprint-is-versioned)
* [History](#history)
* [Footnotes](#footnotes)
@ -225,12 +227,14 @@ See [How to open the Javascript Console in different browsers](https://webmaster
make sure to backport it to the `maintenance` branch to also include it in
the next release.
4. Make sure you **follow the current coding style**. This means:
* Tabs instead of spaces in the Python files[2]
* Spaces instead of tabs in the Javascript sources
* English language (code, variables, comments, ...)
* Comments where necessary: Tell why the code does something like it does
* Comments where necessary: Tell *why* the code does something like it does
it, structure your code
* Following the general architecture
If your PR needs to make changes to the Stylesheets, change the ``.less`` files
from which the CSS is compiled.
5. **Test your changes thoroughly**. That also means testing with usage
@ -240,13 +244,75 @@ See [How to open the Javascript Console in different browsers](https://webmaster
your changes. Ideally **add unit tests** - OctoPrint severely lacks in that
department, but we are trying to change that, so any new code already covered
with a test suite helps a lot!
6. In your pull request's description, **state what your pull request is doing**,
6. In your pull request's description, **state what your pull request does**,
as in, what feature does it implement, what bug does it fix. The more
thoroughly you explain your intent behind the PR here, the higher the
chances it will get merged fast.
7. Important: Don't forget to **add yourself to the [AUTHORS](./AUTHORS.md)
file** :)
## What do the branches mean?
There are three main branches in OctoPrint:
* `master`: The master branch always contains the current stable release. It
is *only* updated on new releases. Will have a version number following
the scheme `x.y.z` (e.g. `1.2.9`) or - if it's absolutely necessary to
add a commit after release to this branch - `x.y.z.post<commits since x.y.z>`
(e.g. `1.2.9.post1`).
* `maintenance`: Improvements and fixes of the current release that make up
the next release go here. More or less continously updated. You can consider
this a preview of the next release version. It should be very stable at all
times. Anything you spot in here helps tremendously with getting a rock solid
next stable release, so if you want to help out development, running the
`maintenance` branch and reporting back anything you find is a very good way
to do that. Will usually have a version number following the scheme
`x.y.z+1.dev.<commits since increase of z>` for an OctoPrint version of `x.y.z`
(e.g. `1.2.10.dev12`).
* `devel`: Ongoing development of new features that will go into the next bigger
release (MINOR version number increases) will happen on this branch. Usually
kept stable, sometimes stuff can break though or lose backwards compatibility
temporarily. Can be considered the "bleeding edge". All PRs should target
*this* branch. Important improvements and fixes from PRs here are backported to
`maintenance` as needed. Will usually have a version number following the
scheme `x.y+1.0.dev<commits since increase of y>` for an OctoPrint version
of `x.y.z` (e.g. `1.3.0.dev123`).
Additionally, from time to time you might see other branches pop up in the repository.
Those usually have one of the following prefixes:
* `fix/...`: Fixes under development that are to be merged into the `maintenance`
and `devel` branches.
* `improve/...`: Improvements under development that are to be merged into the
`maintenance` and `devel` branches.
* `dev/...` or `feature/...`: New functionality under development that is to be merged
into the `devel` branch.
There is also the `gh-pages` branch, which holds OctoPrint's web page, and a couple of
older development branches that are slowly being migrated or deleted.
## How OctoPrint is versioned
OctoPrint follows the [semantic versioning scheme](http://semver.org/) of **MAJOR.MINOR.PATCH**.
The **PATCH** version number is the one increasing most often due to OctoPrint's maintenance releases.
Releases that only change the patch number indicate that they contain bug fixes and small improvements
of existing functionality. Example: 1.2.8 to 1.2.9.
The **MINOR** version number increases with releases that add a lot of new functionality and
large features. Example: 1.2.x to 1.3.0.
Finally, the **MAJOR** version number increases if there are breaking API changes that concern any of the
documented interfaces (REST API, plugin interfaces, ...). So far this hasn't happened. Example: 1.x.y to 2.0.0.
OctoPrint's version numbers are automatically generated using [versioneer](https://github.com/warner/python-versioneer)
and depend on the selected git branch, nearest git tag and commits. The generated version number
should always be [PEP440](https://www.python.org/dev/peps/pep-0440/) compatible. Unless a git tag
is used for version number determination, the version number will also contain the git hash within
the local version identifier to allow for an exact determination of the active code base
(e.g. `1.2.9.dev68+g46c7a9c`). Additionally, instances with active uncommitted changes will contain
`.dirty` in the local version identifier.
## History
* 2015-01-23: More guidelines for creating pull requests, support/questions
@ -255,6 +321,7 @@ See [How to open the Javascript Console in different browsers](https://webmaster
* 2015-07-07: Added step to add yourself to AUTHORS when creating a PR :)
* 2015-12-01: Heavily reworked to include examples, better structure and
all information in one document.
* 2016-02-10: Added information about branch structure and versioning.
## Footnotes
* [1] - If you are wondering why, the problem is that anything that you add

View file

@ -424,6 +424,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") + ": " + formatDuration(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"));

View file

@ -57,6 +57,10 @@
</p>
</div>
</div>
<div class="muted">
<small>{{ _('Note that the time estimates in this tab are calculated by the GCODE viewer in your browser and might differ from the values calculated by the server that are displayed in the "State" and "Files" panels in the sidebar due to slightly different implementations.') }}</small>
</div>
</div>
<div data-bind="visible: waitForApproval">
<h1>{{ _('Warning') }}</h1>

View file

@ -22,6 +22,8 @@ from octoprint.events import eventManager, Events
import sarge
import collections
import re
# currently configured timelapse
current = None
@ -32,6 +34,10 @@ current_render_job = None
_capture_format = "{prefix}-%d.jpg"
_output_format = "{prefix}.mpg"
# old capture format, needed to delete old left-overs from
# versions <1.2.9
_old_capture_format_re = re.compile("^tmp_\d{5}.jpg$")
# valid timelapses
_valid_timelapse_types = ["off", "timed", "zchange"]
@ -93,7 +99,7 @@ def get_unrendered_timelapses():
del job["timestamp"]
return job
return [util.dict_merge(dict(name=key), finalize_fields(value)) for key, value in jobs.items()]
return sorted([util.dict_merge(dict(name=key), finalize_fields(value)) for key, value in jobs.items()], key=lambda x: x["name"])
def delete_unrendered_timelapse(name):
@ -129,14 +135,30 @@ def delete_old_unrendered_timelapses():
clean_after_days = settings().getInt(["webcam", "cleanTmpAfterDays"])
cutoff = time.time() - clean_after_days * 24 * 60 * 60
prefixes_to_clean = []
for filename in os.listdir(basedir):
try:
path = os.path.join(basedir, filename)
prefix = _extract_prefix(filename)
if prefix is None:
# might be an old tmp_00000.jpg kinda frame. we can't
# render those easily anymore, so delete that stuff
if _old_capture_format_re.match(filename):
os.remove(path)
continue
if prefix in prefixes_to_clean:
continue
if os.path.getmtime(path) < cutoff:
os.remove(path)
prefixes_to_clean.append(prefix)
except:
logging.getLogger(__name__).exception("Error while processing file {} during cleanup".format(filename))
for prefix in prefixes_to_clean:
delete_unrendered_timelapse(prefix)
def _create_render_start_handler(name, gcode=None):
def f(movie):

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
# coding=utf-8
"""
Unit tests for ``octoprint.timelapse``.
"""
from __future__ import absolute_import
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2016 The OctoPrint Project - Released under terms of the AGPLv3 License"

View file

@ -0,0 +1,172 @@
# coding=utf-8
from __future__ import absolute_import
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2016 The OctoPrint Project - Released under terms of the AGPLv3 License"
import unittest
import mock
import os
import time
from collections import namedtuple
_stat = namedtuple("StatResult", "st_size, st_ctime, st_mtime")
import octoprint.settings
import octoprint.timelapse
class TimelapseTest(unittest.TestCase):
def setUp(self):
# mock settings
self.settings_patcher = mock.patch("octoprint.timelapse.settings")
self.settings_getter = self.settings_patcher.start()
self.settings = mock.create_autospec(octoprint.settings.Settings)
self.settings_getter.return_value = self.settings
self.now = time.time()
def cleanUp(self):
self.settings_patcher.stop()
@mock.patch("os.remove")
@mock.patch("os.listdir")
def test_delete_unrendered_timelapse(self, mock_listdir, mock_remove):
## prepare
mocked_path = "/path/to/timelapse/tmp"
mocked_files = ["a-0.jpg",
"a-1.jpg",
"a-2.jpg",
"b-0.jpg",
"b-1.jpg",
"tmp_00000.jpg",
"tmp_00001.jpg"]
self.settings.getBaseFolder.return_value = mocked_path
mock_listdir.return_value = mocked_files
## test
octoprint.timelapse.delete_unrendered_timelapse("b")
## verify
expected_deletions = map(lambda x: os.path.join(mocked_path, x), ["b-0.jpg",
"b-1.jpg"])
expected_deletion_calls = map(mock.call, expected_deletions)
self.assertListEqual(mock_remove.mock_calls, expected_deletion_calls)
@mock.patch("time.time")
@mock.patch("os.remove")
@mock.patch("os.path.getmtime")
@mock.patch("os.listdir")
def test_delete_old_unrendered_timelapses(self, mock_listdir, mock_mtime, mock_remove, mock_time):
## prepare
mocked_path = "/path/to/timelapse/tmp"
mocked_files = ["old-0.jpg",
"old-1.jpg",
"old-2.jpg",
"prefix-0.jpg",
"prefix-1.jpg",
"tmp_00000.jpg",
"tmp_00001.jpg"]
now = self.now
days = 1
def mtime(p):
if p.startswith(os.path.join(mocked_path, "old-0")):
# old-0 is definitely older than cutoff
return 0
else:
# all other files were just created
return now
self.settings.getBaseFolder.return_value = mocked_path
self.settings.getInt.return_value = days
mock_time.return_value = now
mock_listdir.return_value = mocked_files
mock_mtime.side_effect = mtime
## test
octoprint.timelapse.delete_old_unrendered_timelapses()
## verify
expected_deletions = map(lambda x: os.path.join(mocked_path, x), ["tmp_00000.jpg",
"tmp_00001.jpg",
"old-0.jpg",
"old-1.jpg",
"old-2.jpg"])
expected_deletion_calls = map(mock.call, expected_deletions)
self.assertListEqual(mock_remove.mock_calls, expected_deletion_calls)
@mock.patch("os.stat")
@mock.patch("os.listdir")
def test_get_finished_timelapses(self, mock_listdir, mock_stat):
## prepare
files = dict()
files["one.mpg"] = _stat(st_size=1024, st_ctime=self.now, st_mtime=self.now)
files["nope.jpg"] = _stat(st_size=100, st_ctime=self.now, st_mtime=self.now)
files["two.mpg"] = _stat(st_size=2048, st_ctime=self.now, st_mtime=self.now)
mocked_path = "/path/to/timelapse"
self.settings.getBaseFolder.return_value = mocked_path
mock_listdir.return_value = sorted(files.keys())
def stat(p):
name = p[len(mocked_path) + 1:]
return files[name]
mock_stat.side_effect = stat
## test
result = octoprint.timelapse.get_finished_timelapses()
## verify
self.assertEqual(len(result), 2)
self.assertEqual(result[0]["name"], "one.mpg")
self.assertEqual(result[0]["bytes"], 1024)
self.assertEqual(result[1]["name"], "two.mpg")
self.assertEqual(result[1]["bytes"], 2048)
@mock.patch("os.stat")
@mock.patch("os.listdir")
def test_unrendered_timelapses(self, mock_listdir, mock_stat):
## prepare
files = dict()
files["one-0.jpg"] = _stat(st_size=1, st_ctime=self.now - 1, st_mtime=self.now - 1)
files["one-1.jpg"] = _stat(st_size=2, st_ctime=self.now, st_mtime=self.now)
files["one-2.jpg"] = _stat(st_size=3, st_ctime=self.now, st_mtime=self.now)
files["nope.mpg"] = _stat(st_size=2048, st_ctime=self.now, st_mtime=self.now)
files["two-0.jpg"] = _stat(st_size=4, st_ctime=self.now, st_mtime=self.now)
files["two-1.jpg"] = _stat(st_size=5, st_ctime=self.now, st_mtime=self.now)
mocked_path = "/path/to/timelapse/tmp"
self.settings.getBaseFolder.return_value = mocked_path
mock_listdir.return_value = sorted(files.keys())
def stat(p):
name = p[len(mocked_path) + 1:]
return files[name]
mock_stat.side_effect = stat
## test
result = octoprint.timelapse.get_unrendered_timelapses()
## verify
self.assertEqual(len(result), 2)
self.assertEqual(result[0]["name"], "one")
self.assertEqual(result[0]["count"], 3)
self.assertEqual(result[0]["bytes"], 6)
self.assertEqual(result[1]["name"], "two")
self.assertEqual(result[1]["count"], 2)
self.assertEqual(result[1]["bytes"], 9)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff