fix: config save broken (relative_to→relpath), startup validation, auto-create, untrack config.yaml

- _relative_path(): replace Path.relative_to() with os.path.relpath()
  (ValueError crossing filesystems silently returned absolute paths)
- _sync_config_from_ui(): always derive relative from get_path() absolute,
  never trust stale FileSelector._relative_path after Browse
- validate_paths(): check all file paths exist on startup, clear missing
- Clear UI selectors after validation to prevent re-save of stale paths
- _load_config(): auto-create blank config.yaml when missing on disk
- .gitignore: add config.yaml (user-specific, must not be tracked)
This commit is contained in:
2026-06-12 15:02:09 +08:00
parent 9238cbc63e
commit 9502bba636
4 changed files with 84 additions and 47 deletions
+34 -2
View File
@@ -190,8 +190,11 @@ class Config:
def _relative_path(self, path: str) -> str:
"""Convert an absolute path to a relative path from the config file.
Uses :func:`os.path.relpath` which works across filesystem
boundaries (unlike :meth:`Path.relative_to`).
Args:
path: Absolute path string.
path: Absolute or relative path string.
Returns:
Relative path string, or the original if conversion fails.
@@ -201,7 +204,8 @@ class Config:
try:
target = Path(path)
if target.is_absolute():
return str(target.relative_to(self._config_path.parent))
base = str(self._config_path.parent)
return os.path.relpath(str(target), base)
except (ValueError, OSError):
pass
return path
@@ -219,6 +223,34 @@ class Config:
return Path()
return (self._config_path.parent / relative_path).resolve()
def validate_paths(self) -> list[str]:
"""Validate file paths in config and clear those that don't exist.
Checks each file path field, resolves it against the config
directory, and clears it if the target file does not exist
on the current filesystem.
Returns:
List of field names that were cleared.
"""
cleared: list[str] = []
path_fields = [
"bootloader_bit_path",
"bootloader_elf_path",
"bootloader_bin_path",
"fsbl_elf_path",
"firmware_bin_path",
]
for attr in path_fields:
val = getattr(self, attr, "")
if not val:
continue
resolved = self.resolve_path(val)
if not resolved.is_file():
setattr(self, attr, "")
cleared.append(attr)
return cleared
@property
def config_path(self) -> Path | None:
"""Return the path to the config file on disk."""