diff --git a/src/octoprint/filemanager/storage.py b/src/octoprint/filemanager/storage.py index 4121af35..dbfa1ff8 100644 --- a/src/octoprint/filemanager/storage.py +++ b/src/octoprint/filemanager/storage.py @@ -17,6 +17,8 @@ from copy import deepcopy import octoprint.filemanager +from octoprint.util import is_hidden_path + class StorageInterface(object): """ Interface of storage adapters for OctoPrint. @@ -434,7 +436,7 @@ class LocalFileStorage(StorageInterface): if not metadata: metadata = dict() for entry in os.listdir(path): - if entry.startswith(".") or not octoprint.filemanager.valid_file_type(entry): + if is_hidden_path(entry) or not octoprint.filemanager.valid_file_type(entry): continue absolute_path = os.path.join(path, entry) @@ -1021,7 +1023,7 @@ class LocalFileStorage(StorageInterface): result = dict() for entry in os.listdir(path): - if entry.startswith("."): + if is_hidden_path(entry): # no hidden files and folders continue diff --git a/src/octoprint/printer/profile.py b/src/octoprint/printer/profile.py index 700aef07..f46d90bf 100644 --- a/src/octoprint/printer/profile.py +++ b/src/octoprint/printer/profile.py @@ -12,7 +12,7 @@ import re import logging from octoprint.settings import settings -from octoprint.util import dict_merge, dict_sanitize, dict_contains_keys +from octoprint.util import dict_merge, dict_sanitize, dict_contains_keys, is_hidden_path class SaveError(Exception): pass @@ -289,7 +289,7 @@ class PrinterProfileManager(object): def _load_all_identifiers(self): results = dict(_default=None) for entry in os.listdir(self._folder): - if entry.startswith(".") or not entry.endswith(".profile") or entry == "_default.profile": + if is_hidden_path(entry) or not entry.endswith(".profile") or entry == "_default.profile": continue path = os.path.join(self._folder, entry) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 67589e6a..9519e0a0 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -338,7 +338,7 @@ class Server(): ) additional_mime_types=dict(mime_type_guesser=mime_type_guesser) admin_validator = dict(access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator)) - no_hidden_files_validator = dict(path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404)) + no_hidden_files_validator = dict(path_validation=util.tornado.path_validation_factory(lambda path: not octoprint.util.is_hidden_path(path), status_code=404)) def joined_dict(*dicts): if not len(dicts): diff --git a/src/octoprint/slicing/__init__.py b/src/octoprint/slicing/__init__.py index 7812fec5..3248bd63 100644 --- a/src/octoprint/slicing/__init__.py +++ b/src/octoprint/slicing/__init__.py @@ -22,6 +22,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import os import octoprint.plugin import octoprint.events +import octoprint.util from octoprint.settings import settings import logging @@ -524,7 +525,7 @@ class SlicingManager(object): profiles = dict() slicer_profile_path = self.get_slicer_profile_path(slicer) for entry in os.listdir(slicer_profile_path): - if not entry.endswith(".profile") or entry.startswith("."): + if not entry.endswith(".profile") or octoprint.util.is_hidden_path(entry): # we are only interested in profiles and no hidden files continue diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index e0b5e308..7a79edcb 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -662,19 +662,27 @@ def bom_aware_open(filename, encoding="ascii", mode="r", **kwargs): def is_hidden_path(path): + if path is None: + # we define a None path as not hidden here + return False + filename = os.path.basename(path) if filename.startswith("."): + # filenames starting with a . are hidden return True if sys.platform == "win32": + # if we are running on windows we also try to read the hidden file + # attribute via the windows api try: import ctypes attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(path)) - assert attrs != -1 - return bool(attrs & 2) + assert attrs != -1 # INVALID_FILE_ATTRIBUTES == -1 + return bool(attrs & 2) # FILE_ATTRIBUTE_HIDDEN == 2 except (AttributeError, AssertionError): pass + # if we reach that point, the path is not hidden return False diff --git a/tests/util/test_file_helpers.py b/tests/util/test_file_helpers.py index 75e743b1..0ad27e83 100644 --- a/tests/util/test_file_helpers.py +++ b/tests/util/test_file_helpers.py @@ -7,6 +7,8 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms import unittest import mock import os +import ddt +import sys import octoprint.util @@ -222,6 +224,7 @@ class TempDirTest(unittest.TestCase): mock_rmtree.assert_called_once_with(path, ignore_errors=True, onerror=onerror) +@ddt.ddt class IsHiddenPathTest(unittest.TestCase): def setUp(self): @@ -249,12 +252,12 @@ class IsHiddenPathTest(unittest.TestCase): import shutil shutil.rmtree(self.basepath) - def test_is_hidden_path(self): - self.assertFalse(octoprint.util.is_hidden_path(self.path_always_visible)) - self.assertTrue(octoprint.util.is_hidden_path(self.path_always_hidden)) - - import sys - if sys.platform == "win32": - self.assertTrue(octoprint.util.is_hidden_path(self.path_hidden_on_windows)) - else: - self.assertFalse(octoprint.util.is_hidden_path(self.path_hidden_on_windows)) + @ddt.data( + (None, False), + ("path_always_visible", False), + ("path_always_hidden", True), + ("path_hidden_on_windows", sys.platform == "win32") + ) + @ddt.unpack + def test_is_hidden_path(self, path_id, expected): + self.assertEqual(octoprint.util.is_hidden_path(getattr(self, path_id)), expected)