feat: complete dual-seat whole-vehicle orchestration
This commit is contained in:
@@ -303,6 +303,7 @@ class ModeProfile:
|
||||
common: bool = True
|
||||
vehicle_ids: tuple[str, ...] = ()
|
||||
timeline: ModeTimeline | None = None
|
||||
user_defined: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, path: Path, profile_root: Path | None = None) -> "ModeProfile":
|
||||
@@ -336,6 +337,7 @@ class ModeProfile:
|
||||
if isinstance(raw_timeline, dict)
|
||||
else None
|
||||
),
|
||||
user_defined=_is_user_profile_path(path, profile_root),
|
||||
)
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
@@ -352,6 +354,8 @@ class ModeProfile:
|
||||
"vehicle_ids": list(self.vehicle_ids),
|
||||
},
|
||||
"behavior": "cyclic" if self.timeline is not None else "static",
|
||||
"origin": "user" if self.user_defined else "preset",
|
||||
"deletable": self.user_defined,
|
||||
}
|
||||
if self.timeline is not None:
|
||||
result["timeline"] = self.timeline.public_dict()
|
||||
@@ -380,9 +384,11 @@ class ModeProfileRegistry:
|
||||
return list(self._profiles)
|
||||
|
||||
def public_modes(self, vehicle_id: str | None = None) -> list[dict[str, Any]]:
|
||||
profiles = self._profiles_for_vehicle(vehicle_id)
|
||||
profiles.sort(key=lambda profile: 0 if profile.user_defined else 1)
|
||||
return [
|
||||
profile.public_dict()
|
||||
for profile in self._profiles_for_vehicle(vehicle_id)
|
||||
for profile in profiles
|
||||
]
|
||||
|
||||
def match_text(
|
||||
@@ -467,6 +473,45 @@ class ModeProfileRegistry:
|
||||
self.reload()
|
||||
return ModeProfile.from_yaml(path, self.profile_root)
|
||||
|
||||
def delete_user_mode(
|
||||
self,
|
||||
mode_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> ModeProfile:
|
||||
clean_id = str(mode_id).strip()
|
||||
if not clean_id:
|
||||
raise ValueError("mode id must not be empty")
|
||||
profile = next(
|
||||
(
|
||||
item
|
||||
for item in self._profiles_for_vehicle(vehicle_id)
|
||||
if item.id == clean_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if profile is None:
|
||||
raise KeyError(clean_id)
|
||||
if not profile.user_defined:
|
||||
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
|
||||
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():
|
||||
raise KeyError(clean_id)
|
||||
|
||||
path.unlink()
|
||||
backup_path = _initial_backup_path(path)
|
||||
if backup_path.is_file():
|
||||
backup_path.unlink()
|
||||
self.reload()
|
||||
return profile
|
||||
|
||||
def update_mode(
|
||||
self,
|
||||
profile: ModeProfile,
|
||||
@@ -623,6 +668,16 @@ def _mode_scope(
|
||||
return True, ()
|
||||
|
||||
|
||||
def _is_user_profile_path(path: Path, profile_root: Path | None) -> bool:
|
||||
if profile_root is None:
|
||||
return path.parent.name == "user"
|
||||
try:
|
||||
relative_parts = path.resolve().relative_to(profile_root.resolve()).parts
|
||||
except ValueError:
|
||||
return False
|
||||
return "user" in relative_parts
|
||||
|
||||
|
||||
def _is_visible_for_vehicle(
|
||||
profile: ModeProfile,
|
||||
vehicle_id: str | None,
|
||||
|
||||
Reference in New Issue
Block a user