Folder names without a trailing dot (NTFS refuses them)

Found moving the runs to an NTFS disk: the tour "Rundgang 12.10." (folder Rundgang-12.10.) was not copied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
thunderheronightandClaude Opus 5.5 committed 2026-10-15 12:02:35 +02:00
1 parent 3cf0e81aef
commit fccaf4afc6
4 files changed
+30 -7

No files matched your search

+6
View File
@@ -2,6 +2,12 @@
All notable changes to Splat Workshop. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versions follow [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Fixed
- **Folder names without a dot at the end**: a name like "Tour 12.10." gave the folder `Tour-12.10.`, which Windows file systems (NTFS, e.g. a second disk the runs are moved to) refuse – the copy silently lacked that tour. Now it is `Tour-12.10`. Existing folders keep their names.
## [0.3.0] – 2026-10-15
### Added
+3 -2
View File
@@ -76,7 +76,8 @@ class Config:
def slugify(name: str) -> str:
"""Folder/file name from a display name: ä→ae etc., accents dropped, only [A-Za-z0-9._-]."""
"""Folder/file name from a display name: ä→ae etc., accents dropped, only [A-Za-z0-9._-]; no dot or
dash at the ends ("Tour 12.10." → Tour-12.10: Windows file systems such as NTFS refuse a trailing dot)."""
for a, b in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("Ä", "Ae"), ("Ö", "Oe"), ("Ü", "Ue"), ("ß", "ss")):
name = name.replace(a, b)
name = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode()
@@ -88,7 +89,7 @@ def slugify(name: str) -> str:
elif not dash:
out.append("-")
dash = True
return "".join(out).strip("-")
return "".join(out).strip("-.")
def _merge(base: dict, over: dict) -> dict:
+16
View File
@@ -1,5 +1,6 @@
"""Groups of scenes: one group per scene, suggestions, renaming, cleaning up together."""
import json
import pytest
from conftest import RUNS, server
import synth
@@ -78,3 +79,18 @@ def test_dissolving_a_group_keeps_its_scenes(api):
assert api.post("/api/delete", {"slug": "Home"})[0] == 200
assert api.get("/api/groups")[1]["groups"] == []
assert {"Kitchen", "Hallway", "Statue"} <= {s["slug"] for s in server.scenes()}
@pytest.mark.parametrize("name,slug", [("Rundgang 12.10.", "Rundgang-12.10"), ("Küche", "Kueche"),
("...", ""), (".hidden", "hidden"), ("Flur 2", "Flur-2")])
def test_folder_names_suit_every_file_system(name, slug):
"""No dot at the end: NTFS (e.g. runs on a Windows disk) refuses it, and the copy loses the tour."""
from splat_workshop import config
assert config.slugify(name) == slug
def test_a_tour_named_with_a_date_gets_a_folder_ntfs_accepts(api):
synth.make_room(RUNS, "Kitchen", "A", name="Kitchen")
synth.make_room(RUNS, "Hall", "B", name="Hall")
status, r = api.post("/api/group", {"name": "Tour 12.10.", "members": "Kitchen,Hall"})
assert status == 200 and r["slug"] == "Tour-12.10"
+5 -5
View File
@@ -23,13 +23,13 @@ def test_renaming_moves_folder_files_and_log(api):
(run / "aufnahme-12.10.-16-40.gallery.png").write_bytes(b"x")
status, r = api.post("/api/rename", {"slug": "Aufnahme-12.10.-16-40", "new": "Büro 12.10."})
assert status == 200, r
new = RUNS / "Buero-12.10."
new = RUNS / "Buero-12.10"
assert new.is_dir() and not run.exists()
assert (new / "buero-12.10..sog").exists() and (new / "buero-12.10..gallery.png").exists()
assert (RUNS / "Buero-12.10..log").exists() and not (RUNS / "Aufnahme-12.10.-16-40.log").exists()
meta = json.loads((RUNS / "Buero-12.10..ui.json").read_text())
assert (new / "buero-12.10.sog").exists() and (new / "buero-12.10.gallery.png").exists()
assert (RUNS / "Buero-12.10.log").exists() and not (RUNS / "Aufnahme-12.10.-16-40.log").exists()
meta = json.loads((RUNS / "Buero-12.10.ui.json").read_text())
assert meta["name"] == "Büro 12.10." and meta["folder_renamed"] is True
assert server.no_resume("Buero-12.10.") == "renamed" # the pipeline remembers the old path
assert server.no_resume("Buero-12.10") == "renamed" # the pipeline remembers the old path
def test_renaming_to_a_taken_name_is_refused(api):