storage/detect+test file systems with collision: fix to catch HFS+ (MacOS)

This commit is contained in:
Peter Bieringer
2026-04-14 20:43:30 +02:00
parent c7625650c1
commit a6d7fcf1b9
3 changed files with 34 additions and 31 deletions

View File

@@ -406,19 +406,16 @@ def path_supports_symlink(path):
return result
def path_is_collision_free(path):
"""Check whether path supports case colliding-free entries."""
def path_is_collision_free_case_sensitive(path):
# Test: case sensitive
if not os.path.isdir(path):
raise ValueError("%r is not a path" % (path))
result = True
# Test 1: case sensitive
base_dir = tempfile.mkdtemp(dir=path)
test_dir = "TESTDIR"
test_dir_uc = os.path.join(base_dir, test_dir.upper())
test_dir_lc = os.path.join(base_dir, test_dir.lower())
os.mkdir(test_dir_uc)
result = True
try:
os.mkdir(test_dir_lc)
except FileExistsError:
@@ -426,20 +423,23 @@ def path_is_collision_free(path):
else:
# cleanup
os.rmdir(test_dir_lc)
finally:
# cleanup
os.rmdir(test_dir_uc)
if not result:
# early exit
os.rmdir(base_dir)
logger.trace("path_is_collision_free: path=%r result=%s", path, result)
return result
# cleanup
os.rmdir(test_dir_uc)
os.rmdir(base_dir)
logger.debug("path_is_collision_free (case-sensitive): path=%r result=%s", path, result)
return result
# Test 2: short filename
def path_is_collision_free_no_short_filename(path):
"""Check whether path supports short-filename collision-free entries."""
if not os.path.isdir(path):
raise ValueError("%r is not a path" % (path))
base_dir = tempfile.mkdtemp(dir=path)
test_dir = "TESTDIRLONG"
test_dir_long = os.path.join(base_dir, test_dir)
test_dir_short = os.path.join(base_dir, test_dir[:6] + "~1")
os.mkdir(test_dir_long)
result = True
try:
os.mkdir(test_dir_short)
except FileExistsError:
@@ -447,10 +447,8 @@ def path_is_collision_free(path):
else:
# cleanup
os.rmdir(test_dir_short)
finally:
# cleanup
os.rmdir(test_dir_long)
# final exit
# cleanup
os.rmdir(test_dir_long)
os.rmdir(base_dir)
logger.trace("path_is_collision_free: path=%r result=%s", path, result)
logger.debug("path_is_collision_free (no short-filename): path=%r result=%s", path, result)
return result

View File

@@ -173,8 +173,13 @@ class Storage(
self._makedirs_synced(self._get_collection_root_folder())
logger.info("Storage location subfolder permissions: %s", pathutils.path_permissions_as_string(self._get_collection_root_folder()))
logger.info("Storage location subfolder softlink support: %s", pathutils.path_supports_symlink(self._get_collection_root_folder()))
self._filesystem_root_folder_is_collision_free = pathutils.path_is_collision_free(self._get_collection_root_folder())
logger.info("Storage location subfolder is collision free: %s", self._filesystem_root_folder_is_collision_free)
filesystem_root_folder_is_collision_free_case_sensitive = pathutils.path_is_collision_free_case_sensitive(self._get_collection_root_folder())
filesystem_root_folder_is_collision_free_no_short_filename = pathutils.path_is_collision_free_no_short_filename(self._get_collection_root_folder())
self._filesystem_root_folder_is_collision_free = filesystem_root_folder_is_collision_free_case_sensitive and filesystem_root_folder_is_collision_free_no_short_filename
logger.info("Storage location subfolder is collision free: %s (case-sensitive=%s no-short-filename=%s)",
self._filesystem_root_folder_is_collision_free,
filesystem_root_folder_is_collision_free_case_sensitive,
filesystem_root_folder_is_collision_free_no_short_filename)
logger.info("Storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item)
logger.info("Storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history)
logger.info("Storage cache subfolder usage for 'sync-token': %s", self._use_cache_subfolder_for_synctoken)

View File

@@ -222,7 +222,7 @@ class TestMultiFileSystem(BaseTest):
"""Test for colliding files on file systems."""
caplog.set_level(logging.WARNING)
self.configure({"logging": {"request_content_on_debug": "False"}})
fs_colliding_free = pathutils.path_is_collision_free(tempfile.mkdtemp())
fs_collision_free = pathutils.path_is_collision_free_case_sensitive(tempfile.mkdtemp())
file_item = "EvEnT1.iCs"
path_coll = "/calendar.ics/"
self.mkcalendar(path_coll)
@@ -232,11 +232,11 @@ class TestMultiFileSystem(BaseTest):
path_lc = os.path.join(path_coll, file_item.lower())
self.put(path_item, event)
self.put(path_uc, event, check=409)
if not fs_colliding_free:
if not fs_collision_free:
logs = caplog.messages
assert len([log for log in logs if "File name collision" in log]) == 1
self.put(path_lc, event, check=409)
if not fs_colliding_free:
if not fs_collision_free:
logs = caplog.messages
assert len([log for log in logs if "File name collision" in log]) == 2
@@ -244,16 +244,16 @@ class TestMultiFileSystem(BaseTest):
"""Test for colliding dirs on file systems."""
caplog.set_level(logging.WARNING)
self.configure({"logging": {"request_content_on_debug": "False"}})
fs_colliding_free = pathutils.path_is_collision_free(tempfile.mkdtemp())
fs_collision_free = pathutils.path_is_collision_free_case_sensitive(tempfile.mkdtemp())
path_coll = "/CaLeNdAr.ics/"
self.mkcalendar(path_coll)
if fs_colliding_free:
if fs_collision_free:
self.mkcalendar(path_coll.lower(), check=201)
else:
self.mkcalendar(path_coll.lower(), check=409)
logs = caplog.messages
assert len([log for log in logs if "File name collision" in log]) == 1
if fs_colliding_free:
if fs_collision_free:
self.mkcalendar(path_coll.upper(), check=201)
else:
self.mkcalendar(path_coll.upper(), check=409)
@@ -264,17 +264,17 @@ class TestMultiFileSystem(BaseTest):
"""Test for colliding dirs (shortname) on file systems."""
caplog.set_level(logging.WARNING)
self.configure({"logging": {"request_content_on_debug": "False"}})
fs_colliding_free = pathutils.path_is_collision_free(tempfile.mkdtemp())
fs_collision_free = pathutils.path_is_collision_free_no_short_filename(tempfile.mkdtemp())
path_coll = "/calendarlongname.ics/"
path_coll_short = "/calend~1.ics/"
self.mkcalendar(path_coll)
if fs_colliding_free:
if fs_collision_free:
self.mkcalendar(path_coll_short.lower(), check=201)
else:
self.mkcalendar(path_coll_short.lower(), check=409)
logs = caplog.messages
assert len([log for log in logs if "File name collision" in log]) == 1
if fs_colliding_free:
if fs_collision_free:
self.mkcalendar(path_coll_short.upper(), check=201)
else:
self.mkcalendar(path_coll_short.upper(), check=409)