From abfcc6e5aa0c252741b261163826e6209aa286bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 10 Oct 2017 12:41:47 +0200 Subject: [PATCH] Fix deletion of unrendered timelapses If the filename contained a [] pair, the file would not match the glob pattern used for selecting for deletion. Backporting the glob.escape function from Python 3.4 and using it here should fix that. We only use our own ported version if glob.escape doesn't exist - that should reduce redundant code once we become Python 3 compatible. --- src/octoprint/timelapse.py | 4 +++- src/octoprint/util/__init__.py | 23 ++++++++++++++++++ tests/util/test_file_helpers.py | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/octoprint/timelapse.py b/src/octoprint/timelapse.py index 58de381f..a3cff03e 100644 --- a/src/octoprint/timelapse.py +++ b/src/octoprint/timelapse.py @@ -139,12 +139,14 @@ def get_unrendered_timelapses(): def delete_unrendered_timelapse(name): global _cleanup_lock + + pattern = "{}*.jpg".format(util.glob_escape(name)) basedir = settings().getBaseFolder("timelapse_tmp") with _cleanup_lock: for entry in scandir(basedir): try: - if fnmatch.fnmatch(entry.name, "{}*.jpg".format(name)): + if fnmatch.fnmatch(entry.name, pattern): os.remove(entry.path) except: if logging.getLogger(__name__).isEnabledFor(logging.DEBUG): diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 862347c7..cc8a4a91 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -866,6 +866,29 @@ def is_hidden_path(path): return False +try: + from glob import escape + glob_escape = escape +except ImportError: + # no glob.escape - we need to implement our own + _glob_escape_check = re.compile("([*?[])") + _glob_escape_check_bytes = re.compile(b"([*?[])") + + def glob_escape(pathname): + """ + Ported from Python 3.4 + + See https://github.com/python/cpython/commit/fd32fffa5ada8b8be8a65bd51b001d989f99a3d3 + """ + + drive, pathname = os.path.splitdrive(pathname) + if isinstance(pathname, bytes): + pathname = _glob_escape_check_bytes.sub(br"[\1]", pathname) + else: + pathname = _glob_escape_check.sub(r"[\1]", pathname) + return drive + pathname + + class RepeatedTimer(threading.Thread): """ This class represents an action that should be run repeatedly in an interval. It is similar to python's diff --git a/tests/util/test_file_helpers.py b/tests/util/test_file_helpers.py index 2f0add32..36bf525d 100644 --- a/tests/util/test_file_helpers.py +++ b/tests/util/test_file_helpers.py @@ -358,3 +358,45 @@ class IsHiddenPathTest(unittest.TestCase): def test_is_hidden_path(self, path_id, expected): path = getattr(self, path_id) if path_id is not None else None self.assertEqual(octoprint.util.is_hidden_path(path), expected) + + +try: + from glob import escape + +except ImportError: + # no glob.escape - tests for our ported implementation + + @ddt.ddt + class GlobEscapeTest(unittest.TestCase): + """ + Ported from Python 3.4 + + See https://github.com/python/cpython/commit/fd32fffa5ada8b8be8a65bd51b001d989f99a3d3 + """ + + @ddt.data( + ("abc", "abc"), + ("[", "[[]"), + ("?", "[?]"), + ("*", "[*]"), + ("[[_/*?*/_]]", "[[][[]_/[*][?][*]/_]]"), + ("/[[_/*?*/_]]/", "/[[][[]_/[*][?][*]/_]]/") + ) + @ddt.unpack + def test_glob_escape(self, text, expected): + actual = octoprint.util.glob_escape(text) + self.assertEqual(actual, expected) + + @ddt.data( + ("?:?", "?:[?]"), + ("*:*", "*:[*]"), + (r"\\?\c:\?", r"\\?\c:\[?]"), + (r"\\*\*\*", r"\\*\*\[*]"), + ("//?/c:/?", "//?/c:/[?]"), + ("//*/*/*", "//*/*/[*]") + ) + @ddt.unpack + @unittest.skipUnless(sys.platform == "win32", "Win32 specific test") + def test_glob_escape_windows(self, text, expected): + actual = octoprint.util.glob_escape(text) + self.assertEqual(actual, expected)