This commit is contained in:
vee
2026-08-14 15:01:29 +08:00
parent 6d768aaef4
commit f77dda70fe
20 changed files with 1125 additions and 122 deletions
+45 -20
View File
@@ -363,22 +363,33 @@ class ModeProfile:
class ModeProfileRegistry:
"""Load, match, and persist long-term cabin mode profiles."""
"""Load preset profiles and optionally keep user profiles in a session overlay."""
def __init__(self, profile_root: Path):
def __init__(self, profile_root: Path, user_root: Path | None = None):
self.profile_root = Path(profile_root)
self.user_root = Path(user_root) if user_root is not None else self.profile_root
self.session_user_profiles = user_root is not None
self._profiles: list[ModeProfile] = []
self.reload()
def reload(self) -> None:
if not self.profile_root.exists():
self._profiles = []
return
self._profiles = [
ModeProfile.from_yaml(path, self.profile_root)
for path in sorted(self.profile_root.rglob("*.yaml"))
if path.is_file()
]
profiles: list[ModeProfile] = []
if self.profile_root.exists():
for path in sorted(self.profile_root.rglob("*.yaml")):
if not path.is_file():
continue
if self.session_user_profiles and _is_user_profile_path(
path, self.profile_root
):
continue
profiles.append(ModeProfile.from_yaml(path, self.profile_root))
if self.session_user_profiles and self.user_root.exists():
profiles.extend(
ModeProfile.from_yaml(path, self.user_root)
for path in sorted(self.user_root.rglob("*.yaml"))
if path.is_file()
)
self._profiles = profiles
def profiles(self) -> list[ModeProfile]:
return list(self._profiles)
@@ -459,9 +470,9 @@ class ModeProfileRegistry:
],
}
path = (
self.profile_root / "vehicles" / vehicle_id / "user" / f"{profile_id}.yaml"
self.user_root / "vehicles" / vehicle_id / "user" / f"{profile_id}.yaml"
if vehicle_id
else self.profile_root / "common" / "user" / f"{profile_id}.yaml"
else self.user_root / "common" / "user" / f"{profile_id}.yaml"
)
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
@@ -471,7 +482,7 @@ class ModeProfileRegistry:
)
temporary.replace(path)
self.reload()
return ModeProfile.from_yaml(path, self.profile_root)
return ModeProfile.from_yaml(path, self.user_root)
def delete_user_mode(
self,
@@ -495,11 +506,15 @@ class ModeProfileRegistry:
raise PermissionError("preset mode profiles cannot be deleted")
path = Path(profile.source).resolve()
profile_root = self.profile_root.resolve()
try:
relative_parts = path.relative_to(profile_root).parts
except ValueError as exc:
raise PermissionError("mode profile is outside the configured registry") from exc
relative_parts = None
for root in {self.profile_root.resolve(), self.user_root.resolve()}:
try:
relative_parts = path.relative_to(root).parts
break
except ValueError:
continue
if relative_parts is None:
raise PermissionError("mode profile is outside the configured registry")
if "user" not in relative_parts or path.suffix.lower() != ".yaml":
raise PermissionError("only user-defined mode profiles can be deleted")
if not path.is_file():
@@ -532,7 +547,7 @@ class ModeProfileRegistry:
document = _mode_profile_document(profile, merged_operations)
_replace_yaml(path, document)
self.reload()
return ModeProfile.from_yaml(path, self.profile_root)
return ModeProfile.from_yaml(path, self._root_for_path(path))
def restore_initial_mode(self, profile: ModeProfile) -> ModeProfile:
path = Path(profile.source)
@@ -544,7 +559,17 @@ class ModeProfileRegistry:
temporary.write_text(backup_path.read_text(encoding="utf-8"), encoding="utf-8")
temporary.replace(path)
self.reload()
return ModeProfile.from_yaml(path, self.profile_root)
return ModeProfile.from_yaml(path, self._root_for_path(path))
def _root_for_path(self, path: Path) -> Path:
resolved = path.resolve()
for root in (self.user_root.resolve(), self.profile_root.resolve()):
try:
resolved.relative_to(root)
return root
except ValueError:
continue
return self.profile_root
def _profiles_for_vehicle(self, vehicle_id: str | None) -> list[ModeProfile]:
selected: dict[str, ModeProfile] = {}