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)