Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/core/batch_rename_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,23 @@ def generate_preview(
norm_target = os.path.normcase(os.path.abspath(item.new_path))
norm_src = os.path.normcase(os.path.abspath(item.original_path))

# Case-Only Rename der eigenen Datei ist zulässig
# Case-Only Rename der eigenen Datei ist zulässig. os.path.normcase()
# allein reicht dafür nicht: normcase folgt der Pfad-STRING-Konvention
# des Betriebssystems (Windows -> lowercase, POSIX -> No-Op) und weiß
# nichts über das tatsächliche Dateisystem. Unter macOS ist die
# Pfadkonvention case-sensitiv (normcase verändert nichts), das dort
# standardmäßige APFS aber case-insensitiv -- "sample.txt" und
# "SAMPLE.txt" sind auf Dateisystemebene dieselbe Datei, obwohl ihre
# normcase-Strings sich unterscheiden. os.path.samefile() vergleicht
# dagegen tatsächliche Inode/Device-Identität und erkennt das korrekt.
if norm_target == norm_src:
continue
if os.path.exists(item.new_path) and os.path.exists(item.original_path):
try:
if os.path.samefile(item.new_path, item.original_path):
continue
except OSError:
pass

# Ziel wird von einer anderen, nicht wegziehenden Batch-Datei blockiert
if norm_target in blocked_original_paths:
Expand Down
12 changes: 11 additions & 1 deletion tests/test_preview_panel_hardening.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,22 @@ def test_metadata_handles_negative_or_invalid_timestamp_gracefully(self, tmp_pat

class TestExcelPreviewExternalLaunch:
def test_open_extern_handles_os_error_gracefully(self, tmp_path):
"""_open_extern() waehlt den Aufruf je nach sys.platform (os.startfile
unter Windows, subprocess.Popen("open"/"xdg-open") sonst) -- os.startfile
existiert unter Linux/macOS gar nicht, patch() darauf wuerde dort mit
AttributeError abbrechen. Der passende Aufrufpfad fuer die aktuelle
Plattform wird stattdessen gemockt."""
from gui.preview.preview_panel import ExcelPreview

widget = ExcelPreview()
widget._path = str(tmp_path / "fake.xlsx")

with patch("os.startfile", side_effect=OSError("No application associated")):
if sys.platform == "win32":
target, kwargs = "os.startfile", {"side_effect": OSError("No application associated")}
else:
target, kwargs = "subprocess.Popen", {"side_effect": OSError("No application associated")}

with patch(target, **kwargs):
widget._open_extern()

assert not widget.status_label.isHidden()
Expand Down
Loading