From c55fea666db6bd2b95429b82fcde9f1298f47a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 19 Oct 2015 09:33:48 +0200 Subject: [PATCH 1/7] Updated required versions of psutil and netifaces Also see #1090 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0fed3ef0..80ed83c4 100644 --- a/setup.py +++ b/setup.py @@ -26,13 +26,13 @@ INSTALL_REQUIRES = [ "netaddr==0.7.17", "watchdog==0.8.3", "sarge==0.1.4", - "netifaces==0.8", + "netifaces==0.10", "pylru==1.0.9", "rsa==3.2", "pkginfo==1.2.1", "requests==2.7.0", "semantic_version==2.4.2", - "psutil==3.1.1" + "psutil==3.2.1" ] # Additional requirements for optional install options From 758062133cafa9593d0eabd7094a35c6583ab83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 20 Oct 2015 11:55:34 +0200 Subject: [PATCH 2/7] Updated changelog for 1.2.7 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 541f2551..d9597bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # OctoPrint Changelog +## 1.2.7 (2015-10-20) + +### Improvements + + * [#1062](https://github.com/foosel/OctoPrint/issues/1062) - Plugin Manager now has a configuration dialog that among other things allows defining the used `pip` command if auto detection proves to be insufficient here. + * Allow defining additional `pip` parameters in Plugin Manager. That might make `sudo`-less installation of plugins possible in situations where it's tricky otherwise (e.g. by using `--user`) + * Improved timelapse processing (backported from `devel` branch): + * Individually captured frames cannot "overtake" each other anymore through usage of a capture queue. + * Notifications will now be shown when the capturing of the timelapse's post roll happens, including an approximation of how long that will take. + * Usage of `requests` instead of `urllib` for fetching the snapshot, appears to also have [positive effects on webcam compatibility](https://github.com/foosel/OctoPrint/issues/1078). + * Some more defensive escaping for various settings in the UI (e.g. webcam URL) + * Switch to more error resilient saving of configuration files and other files modified during runtime (save to temporary file & move). Should reduce risk of file corruption. + * Downloading GCODE and STL files should now set more fitting `Content-Type` headers (`text/plain` and `application/sla`) for better client side support for "Open with" like usage scenarios. + * Selecting z-triggered timelapse mode will now inform about that not working when printing from SD card. + * Software Update Plugin: Removed "The web interface will now be reloaded" notification after successful update since that became obsolete with introduction of the "Reload Now" overlay. + * Updated required version of `psutil` and `netifaces` dependencies. + +### Bug Fixes + + * [#1057](https://github.com/foosel/OctoPrint/issues/1057) - Better error resilience of the Software Update plugin against broken/incomplete update configurations. + * [#1075](https://github.com/foosel/OctoPrint/issues/1075) - Fixed support of `sudo` for installing plugins, but added big visible warning about it as it's **not** recommended. + * [#1077](https://github.com/foosel/OctoPrint/issues/1077) - Do not hiccup on [UTF-8 BOMs](https://en.wikipedia.org/wiki/Byte_order_mark) (or other BOMs for that matter) at the beginning of GCODE files. + * Fixed an issue that caused user sessions to not be properly associated, leading to Sessions getting duplicated, wrongly saved etc. + * Fixed internal server error (HTTP 500) response on REST API calls with unset `Content-Type` header. + * Fixed an issue leading to drag-and-drop file uploads to trigger frontend processing in various other file upload widgets. + * Fixed a documentation error. + * Fixed caching behaviour on GCODE/STL downloads, was setting the `ETag` header improperly. + * Fixed GCODE viewer not properly detecting change of currently visualized file on Windows systems. + +([Commits](https://github.com/foosel/OctoPrint/compare/1.2.6...1.2.7)) + ## 1.2.6 (2015-09-02) ### Improvements From 693633bd1d51baf2a57bb628757c326ed540e359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 20 Jul 2015 16:29:29 +0200 Subject: [PATCH 3/7] RepeatedTimer now supports callback when timer stops (cherry picked from commit 3c5a976) --- src/octoprint/util/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 58c750a3..b09dd252 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -585,10 +585,13 @@ class RepeatedTimer(threading.Thread): run_first (boolean): If set to True, the function will be run for the first time *before* the first wait period. If set to False (the default), the function will be run for the first time *after* the first wait period. condition (callable): Condition that needs to be True for loop to continue. Defaults to ``lambda: True``. + on_finish (callable): Callback to call when the timer finishes, either due to being cancelled or since + the condition became false. daemon (bool): daemon flag to set on underlying thread. """ - def __init__(self, interval, function, args=None, kwargs=None, run_first=False, condition=None, daemon=True): + def __init__(self, interval, function, args=None, kwargs=None, + run_first=False, condition=None, on_finish=None, daemon=True): threading.Thread.__init__(self) if args is None: @@ -609,10 +612,11 @@ class RepeatedTimer(threading.Thread): self.kwargs = kwargs self.run_first = run_first self.condition = condition + self.on_finish = on_finish self.daemon = daemon def cancel(self): - self.finished.set() + self.finish() def run(self): while self.condition(): @@ -633,8 +637,13 @@ class RepeatedTimer(threading.Thread): # if we are to run the function AFTER waiting for the first time self.function(*self.args, **self.kwargs) + self.finish() + + def finish(self): # make sure we set our finished event so we can detect that the loop was finished self.finished.set() + if callable(self.on_finish): + self.on_finish() class CountedEvent(object): From aa5099340ea9aaab6550c7a39f7928b8262880a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 28 Aug 2015 16:53:22 +0200 Subject: [PATCH 4/7] Two new callbacks for RepeatedTimer when cancelled or condition untrue (cherry picked from commit 9b9ecfe) --- src/octoprint/util/__init__.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index b09dd252..bc4ddfd7 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -585,13 +585,18 @@ class RepeatedTimer(threading.Thread): run_first (boolean): If set to True, the function will be run for the first time *before* the first wait period. If set to False (the default), the function will be run for the first time *after* the first wait period. condition (callable): Condition that needs to be True for loop to continue. Defaults to ``lambda: True``. + on_condition_false (callable): Callback to call when the timer finishes due to condition becoming false. Will + be called before the ``on_finish`` callback. + on_cancelled (callable): Callback to call when the timer finishes due to being cancelled. Will be called + before the ``on_finish`` callback. on_finish (callable): Callback to call when the timer finishes, either due to being cancelled or since the condition became false. daemon (bool): daemon flag to set on underlying thread. """ def __init__(self, interval, function, args=None, kwargs=None, - run_first=False, condition=None, on_finish=None, daemon=True): + run_first=False, condition=None, on_condition_false=None, + on_cancelled=None, on_finish=None, daemon=True): threading.Thread.__init__(self) if args is None: @@ -612,11 +617,15 @@ class RepeatedTimer(threading.Thread): self.kwargs = kwargs self.run_first = run_first self.condition = condition + + self.on_condition_false = on_condition_false + self.on_cancelled = on_cancelled self.on_finish = on_finish + self.daemon = daemon def cancel(self): - self.finish() + self._finish(self.on_cancelled) def run(self): while self.condition(): @@ -631,17 +640,23 @@ class RepeatedTimer(threading.Thread): # wait, but break if we are cancelled self.finished.wait(self.interval()) if self.finished.is_set(): - break + return if not self.run_first: # if we are to run the function AFTER waiting for the first time self.function(*self.args, **self.kwargs) - self.finish() + # we'll only get here if the condition was false + self._finish(self.on_condition_false) - def finish(self): - # make sure we set our finished event so we can detect that the loop was finished + def _finish(self, *callbacks): self.finished.set() + + for callback in callbacks: + if not callable(callback): + continue + callback() + if callable(self.on_finish): self.on_finish() From d34790e74e1811f926002e96c181070f1b3cdd6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 28 Aug 2015 16:56:14 +0200 Subject: [PATCH 5/7] Unit tests for octoprint.util.RepeatedTimer (cherry picked from commit 7362067) --- tests/util/test_repeated_timer.py | 180 ++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tests/util/test_repeated_timer.py diff --git a/tests/util/test_repeated_timer.py b/tests/util/test_repeated_timer.py new file mode 100644 index 00000000..6eed773b --- /dev/null +++ b/tests/util/test_repeated_timer.py @@ -0,0 +1,180 @@ +# coding=utf-8 +from __future__ import absolute_import + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License" + +import unittest +import mock +import time + +from octoprint.util import RepeatedTimer + + +class Countdown(object): + def __init__(self, start): + self._counter = start + + def step(self): + self._counter -= 1 + + @property + def counter(self): + return self._counter + + +class IncreasingInterval(Countdown): + def __init__(self, start, factor): + Countdown.__init__(self, start) + self._start = start + self._factor = factor + + def interval(self): + result = (self._start - self._counter + 1) * self._factor + return result + + +class RepeatedTimerTest(unittest.TestCase): + + def setUp(self): + pass + + def test_condition(self): + countdown = Countdown(5) + timer_task = mock.MagicMock() + timer_task.side_effect = countdown.step + + timer = RepeatedTimer(0.1, timer_task, condition=lambda: countdown.counter > 0) + timer.start() + + # wait for it + timer.join() + + self.assertEquals(5, timer_task.call_count) + + def test_finished_callback(self): + countdown = Countdown(5) + timer_task = mock.MagicMock() + timer_task.side_effect = countdown.step + + on_finished = mock.MagicMock() + + timer = RepeatedTimer(0.1, timer_task, condition=lambda: countdown.counter > 0, on_finish=on_finished) + timer.start() + + # wait for it + timer.join() + + self.assertEquals(1, on_finished.call_count) + + def test_condition_callback(self): + countdown = Countdown(5) + timer_task = mock.MagicMock() + timer_task.side_effect = countdown.step + + on_cancelled = mock.MagicMock() + on_condition_false = mock.MagicMock() + + timer = RepeatedTimer(0.1, timer_task, + condition=lambda: countdown.counter > 0, + on_condition_false=on_condition_false, + on_cancelled=on_cancelled) + timer.start() + + # wait for it + timer.join() + + self.assertEquals(1, on_condition_false.call_count) + self.assertEquals(0, on_cancelled.call_count) + + def test_cancelled_callback(self): + countdown = Countdown(5) + timer_task = mock.MagicMock() + timer_task.side_effect = countdown.step + + on_cancelled = mock.MagicMock() + on_condition_false = mock.MagicMock() + + timer = RepeatedTimer(10, timer_task, + condition=lambda: countdown.counter > 0, + on_condition_false=on_condition_false, + on_cancelled=on_cancelled) + timer.start() + + # give it some time to run + time.sleep(1) + + # then cancel it and wait for the thread to really finish + timer.cancel() + timer.join() + + self.assertEquals(0, on_condition_false.call_count) + self.assertEquals(1, on_cancelled.call_count) + + def test_run_first(self): + timer_task = mock.MagicMock() + + timer = RepeatedTimer(60, timer_task, run_first=True) + timer.start() + + # give it some time to run + time.sleep(1) + + # then cancel it and wait for the thread to really finish + timer.cancel() + timer.join() + + # should have run once + self.assertEquals(1, timer_task.call_count) + + def test_not_run_first(self): + timer_task = mock.MagicMock() + + timer = RepeatedTimer(60, timer_task) + timer.start() + + # give it some time to run - should hang in the sleep phase though + time.sleep(1) + + # then cancel it and wait for the thread to really finish + timer.cancel() + timer.join() + + self.assertEquals(0, timer_task.call_count) + + def test_adjusted_interval(self): + increasing_interval = IncreasingInterval(3, 1) + + timer_task = mock.MagicMock() + timer_task.side_effect = increasing_interval.step + + timer = RepeatedTimer(increasing_interval.interval, + timer_task, + condition=lambda: increasing_interval.counter > 0) + + # this should take 1 + 2 + 3 = 6s + start_time = time.time() + timer.start() + timer.join() + duration = time.time() - start_time + + self.assertEquals(3, timer_task.call_count) + self.assertGreaterEqual(duration, 6) + self.assertLess(duration, 7) + + def test_condition_change_during_task(self): + def sleep(): + time.sleep(2) + + timer_task = mock.MagicMock() + timer_task.side_effect = sleep + + timer = RepeatedTimer(0.1, timer_task, run_first=True) + timer.start() + + time.sleep(1) + timer.condition = lambda: False + timer.join() + + self.assertEquals(1, timer_task.call_count) From 6c622f7c4332b71c6ece59552ffc87c146155c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 20 Oct 2015 12:24:23 +0200 Subject: [PATCH 6/7] Fixed indentation of change log --- CHANGELOG.md | 59 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9597bc5..be40008f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,30 +4,55 @@ ### Improvements - * [#1062](https://github.com/foosel/OctoPrint/issues/1062) - Plugin Manager now has a configuration dialog that among other things allows defining the used `pip` command if auto detection proves to be insufficient here. - * Allow defining additional `pip` parameters in Plugin Manager. That might make `sudo`-less installation of plugins possible in situations where it's tricky otherwise (e.g. by using `--user`) + * [#1062](https://github.com/foosel/OctoPrint/issues/1062) - Plugin Manager + now has a configuration dialog that among other things allows defining the + used `pip` command if auto detection proves to be insufficient here. + * Allow defining additional `pip` parameters in Plugin Manager. That might + make `sudo`-less installation of plugins possible in situations where it's + tricky otherwise. * Improved timelapse processing (backported from `devel` branch): - * Individually captured frames cannot "overtake" each other anymore through usage of a capture queue. - * Notifications will now be shown when the capturing of the timelapse's post roll happens, including an approximation of how long that will take. - * Usage of `requests` instead of `urllib` for fetching the snapshot, appears to also have [positive effects on webcam compatibility](https://github.com/foosel/OctoPrint/issues/1078). + * Individually captured frames cannot "overtake" each other anymore through + usage of a capture queue. + * Notifications will now be shown when the capturing of the timelapse's + post roll happens, including an approximation of how long that will take. + * Usage of `requests` instead of `urllib` for fetching the snapshot, + appears to also have [positive effects on webcam compatibility](https://github.com/foosel/OctoPrint/issues/1078). * Some more defensive escaping for various settings in the UI (e.g. webcam URL) - * Switch to more error resilient saving of configuration files and other files modified during runtime (save to temporary file & move). Should reduce risk of file corruption. - * Downloading GCODE and STL files should now set more fitting `Content-Type` headers (`text/plain` and `application/sla`) for better client side support for "Open with" like usage scenarios. - * Selecting z-triggered timelapse mode will now inform about that not working when printing from SD card. - * Software Update Plugin: Removed "The web interface will now be reloaded" notification after successful update since that became obsolete with introduction of the "Reload Now" overlay. + * Switch to more error resilient saving of configuration files and other files + modified during runtime (save to temporary file & move). Should reduce risk + of file corruption. + * Downloading GCODE and STL files should now set more fitting `Content-Type` + headers (`text/plain` and `application/sla`) for better client side support + for "Open with" like usage scenarios. + * Selecting z-triggered timelapse mode will now inform about that not working + when printing from SD card. + * Software Update Plugin: Removed "The web interface will now be reloaded" + notification after successful update since that became obsolete with + introduction of the "Reload Now" overlay. * Updated required version of `psutil` and `netifaces` dependencies. ### Bug Fixes - * [#1057](https://github.com/foosel/OctoPrint/issues/1057) - Better error resilience of the Software Update plugin against broken/incomplete update configurations. - * [#1075](https://github.com/foosel/OctoPrint/issues/1075) - Fixed support of `sudo` for installing plugins, but added big visible warning about it as it's **not** recommended. - * [#1077](https://github.com/foosel/OctoPrint/issues/1077) - Do not hiccup on [UTF-8 BOMs](https://en.wikipedia.org/wiki/Byte_order_mark) (or other BOMs for that matter) at the beginning of GCODE files. - * Fixed an issue that caused user sessions to not be properly associated, leading to Sessions getting duplicated, wrongly saved etc. - * Fixed internal server error (HTTP 500) response on REST API calls with unset `Content-Type` header. - * Fixed an issue leading to drag-and-drop file uploads to trigger frontend processing in various other file upload widgets. + * [#1057](https://github.com/foosel/OctoPrint/issues/1057) - Better error + resilience of the Software Update plugin against broken/incomplete update + configurations. + * [#1075](https://github.com/foosel/OctoPrint/issues/1075) - Fixed support + of `sudo` for installing plugins, but added big visible warning about it + as it's **not** recommended. + * [#1077](https://github.com/foosel/OctoPrint/issues/1077) - Do not hiccup + on [UTF-8 BOMs](https://en.wikipedia.org/wiki/Byte_order_mark) (or other + BOMs for that matter) at the beginning of GCODE files. + * Fixed an issue that caused user sessions to not be properly associated, + leading to Sessions getting duplicated, wrongly saved etc. + * Fixed internal server error (HTTP 500) response on REST API calls with + unset `Content-Type` header. + * Fixed an issue leading to drag-and-drop file uploads to trigger frontend + processing in various other file upload widgets. * Fixed a documentation error. - * Fixed caching behaviour on GCODE/STL downloads, was setting the `ETag` header improperly. - * Fixed GCODE viewer not properly detecting change of currently visualized file on Windows systems. + * Fixed caching behaviour on GCODE/STL downloads, was setting the `ETag` + header improperly. + * Fixed GCODE viewer not properly detecting change of currently visualized + file on Windows systems. ([Commits](https://github.com/foosel/OctoPrint/compare/1.2.6...1.2.7)) From adf39cdf21f0bc929040f3732c4273a2bb920501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 20 Oct 2015 14:54:16 +0200 Subject: [PATCH 7/7] maintenance branch is now 1.2.8-dev --- .versioneer-lookup | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.versioneer-lookup b/.versioneer-lookup index 41fd3a30..a6d43079 100644 --- a/.versioneer-lookup +++ b/.versioneer-lookup @@ -10,10 +10,10 @@ # master shall not use the lookup table, only tags master -# maintenance is currently the branch for preparation of maintenance release 1.2.7 +# maintenance is currently the branch for preparation of maintenance release 1.2.8 # so are any fix/... branches -maintenance 1.2.7-dev 536bb31965db17b969e7c1c53e241ddac4ae1814 -fix/.* 1.2.7-dev 536bb31965db17b969e7c1c53e241ddac4ae1814 +maintenance 1.2.8-dev 6c622f7c4332b71c6ece59552ffc87c146155c84 +fix/.* 1.2.8-dev 6c622f7c4332b71c6ece59552ffc87c146155c84 # Special case disconnected checkouts, e.g. 'git checkout ' \(detached.*