From e5a76992d402da5fbbc98debeec0fcb5effd0192 Mon Sep 17 00:00:00 2001 From: allebonvi Date: Thu, 2 Jul 2026 09:22:12 +0200 Subject: [PATCH] Supporta utente target per percorsi watchdog --- README.md | 14 ++++++++++ SPECIFICHE.md | 3 +++ config/config.example.toml | 1 + src/bakrest/config.py | 20 +++++++++++---- src/bakrest/config_app.py | 16 +++++++++--- src/bakrest/default_config.py | 1 + src/bakrest/paths.py | 44 ++++++++++++++++++++++++++++++-- tests/test_config_and_filters.py | 19 ++++++++++++++ tests/test_paths.py | 23 +++++++++++++++++ 9 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 tests/test_paths.py diff --git a/README.md b/README.md index 954ada3..00ac895 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,22 @@ Per creare o trovare il file di configurazione: ```powershell python -m bakrest.config_cli init python -m bakrest.config_cli path +python -m bakrest.config_cli show ``` +Se il servizio gira come `LocalSystem`, imposta l'utente a cui devono riferirsi i percorsi nel tab `Monitoraggio`, campo `Utente percorsi`, oppure nel TOML: + +```toml +[watch] +target_user = "pettirosso" +include_dirs = [ + "%USERPROFILE%\\Documents", + "%USERPROFILE%\\Desktop", +] +``` + +Con `target_user = "pettirosso"`, `%USERPROFILE%` viene espanso come `C:\Users\pettirosso` anche se il servizio gira come `SYSTEM`. + In sviluppo si puo' usare un percorso diverso: ```powershell diff --git a/SPECIFICHE.md b/SPECIFICHE.md index 7205a0f..2d91ee6 100644 --- a/SPECIFICHE.md +++ b/SPECIFICHE.md @@ -198,12 +198,15 @@ La strategia consigliata e': - permettere all'utente di aggiungere cartelle di lavoro dalla GUI; - prevedere una modalita' di audit per capire quali cartelle producono file rilevanti. +Se il servizio gira come `LocalSystem`, la configurazione deve supportare un utente target per l'espansione dei percorsi utente. Esempio: con `target_user = "pettirosso"`, `%USERPROFILE%\\Documents` deve essere espanso in `C:\\Users\\pettirosso\\Documents`, non nel profilo di sistema. + ### Directory candidate da includere Esempi: ```yaml watch: + target_user: "pettirosso" include_dirs: - "%USERPROFILE%\\Desktop" - "%USERPROFILE%\\Documents" diff --git a/config/config.example.toml b/config/config.example.toml index 48de243..ffbfb27 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -5,6 +5,7 @@ autostart = true autostart = true [watch] +target_user = "" include_dirs = [ "%USERPROFILE%\\Desktop", "%USERPROFILE%\\Documents", diff --git a/src/bakrest/config.py b/src/bakrest/config.py index 9d8d85a..3757e28 100644 --- a/src/bakrest/config.py +++ b/src/bakrest/config.py @@ -35,6 +35,7 @@ class BackupConfig: @dataclass(frozen=True) class WatchConfig: + target_user: str | None include_dirs: tuple[Path, ...] include_extensions: frozenset[str] exclude_dirs: tuple[Path | str, ...] @@ -158,11 +159,12 @@ def parse_config(data: dict[str, Any], path: Path) -> AppConfig: storage = data.get("storage", {}) stability = data.get("stability", {}) - include_dirs = tuple(expand_path(item) for item in watch.get("include_dirs", [])) + target_user = _optional_string(watch.get("target_user")) + include_dirs = tuple(expand_path(item, target_user) for item in watch.get("include_dirs", [])) include_extensions = frozenset( _normalize_extension(item) for item in watch.get("include_extensions", []) ) - exclude_dirs = tuple(_normalize_exclude_dir(item) for item in watch.get("exclude_dirs", [])) + exclude_dirs = tuple(_normalize_exclude_dir(item, target_user) for item in watch.get("exclude_dirs", [])) storage_config = StorageConfig( change_registry=expand_path( @@ -180,6 +182,7 @@ def parse_config(data: dict[str, Any], path: Path) -> AppConfig: service_autostart=bool(service.get("autostart", True)), tray_autostart=bool(tray.get("autostart", True)), watch=WatchConfig( + target_user=target_user, include_dirs=include_dirs, include_extensions=include_extensions, exclude_dirs=exclude_dirs, @@ -219,13 +222,20 @@ def _normalize_extension(value: str) -> str: return value if value.startswith(".") else f".{value}" -def _normalize_exclude_dir(value: str) -> Path | str: - expanded = expand_path(value) - if "%" in value or not expanded.is_absolute(): +def _normalize_exclude_dir(value: str, target_user: str | None = None) -> Path | str: + expanded = expand_path(value, target_user) + if "%" in str(expanded) or not expanded.is_absolute(): return value.lower() return expanded +def _optional_string(value: object) -> str | None: + if value is None: + return None + text = str(value).strip() + return text or None + + def _remote_destinations(backup: dict[str, Any]) -> tuple[str, ...]: destinations = backup.get("remote_destinations") if isinstance(destinations, list): diff --git a/src/bakrest/config_app.py b/src/bakrest/config_app.py index 7daed51..1f82fcc 100644 --- a/src/bakrest/config_app.py +++ b/src/bakrest/config_app.py @@ -56,16 +56,22 @@ class BakRestConfigApp(ctk.CTk): def _build_monitor_tab(self) -> None: self.monitor_tab.grid_columnconfigure(0, weight=1) self.monitor_tab.grid_columnconfigure(1, weight=1) - self.monitor_tab.grid_rowconfigure(0, weight=1) + self.monitor_tab.grid_rowconfigure(1, weight=1) + + target_frame = ctk.CTkFrame(self.monitor_tab) + target_frame.grid(row=0, column=0, columnspan=2, sticky="ew", padx=8, pady=(8, 0)) + target_frame.grid_columnconfigure(1, weight=1) + self.target_user_var = tk.StringVar() + _label_entry(target_frame, "Utente percorsi", self.target_user_var, 0) left = ctk.CTkFrame(self.monitor_tab) - left.grid(row=0, column=0, sticky="nsew", padx=(0, 8), pady=8) + left.grid(row=1, column=0, sticky="nsew", padx=(0, 8), pady=8) left.grid_columnconfigure(0, weight=1) left.grid_rowconfigure(1, weight=1) left.grid_rowconfigure(3, weight=1) right = ctk.CTkFrame(self.monitor_tab) - right.grid(row=0, column=1, sticky="nsew", padx=(8, 0), pady=8) + right.grid(row=1, column=1, sticky="nsew", padx=(8, 0), pady=8) right.grid_columnconfigure(0, weight=1) right.grid_rowconfigure(1, weight=1) right.grid_rowconfigure(3, weight=1) @@ -83,7 +89,7 @@ class BakRestConfigApp(ctk.CTk): self.exclude_patterns.grid(row=2, column=0, rowspan=2, sticky="nsew", padx=12, pady=12) actions = ctk.CTkFrame(self.monitor_tab, fg_color="transparent") - actions.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(0, 8)) + actions.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(0, 8)) actions.grid_columnconfigure(0, weight=1) save_button = ctk.CTkButton(actions, text="Salva configurazione", command=self._save_config) @@ -169,6 +175,7 @@ class BakRestConfigApp(ctk.CTk): backup = self.data.setdefault("backup", {}) server_check = backup.setdefault("server_check", {}) + self.target_user_var.set(str(watch.get("target_user", ""))) self.include_dirs.set_items(watch.get("include_dirs", [])) self.extensions.set_items(watch.get("include_extensions", [])) self.exclude_dirs.set_items(watch.get("exclude_dirs", [])) @@ -187,6 +194,7 @@ class BakRestConfigApp(ctk.CTk): server_check = backup.setdefault("server_check", {}) watch["include_dirs"] = self.include_dirs.items() + watch["target_user"] = self.target_user_var.get().strip() watch["include_extensions"] = self.extensions.items() watch["exclude_dirs"] = self.exclude_dirs.items() watch["exclude_patterns"] = self.exclude_patterns.items() diff --git a/src/bakrest/default_config.py b/src/bakrest/default_config.py index da5cd71..b22efe8 100644 --- a/src/bakrest/default_config.py +++ b/src/bakrest/default_config.py @@ -5,6 +5,7 @@ autostart = true autostart = true [watch] +target_user = "" include_dirs = [ "%USERPROFILE%\\\\Desktop", "%USERPROFILE%\\\\Documents", diff --git a/src/bakrest/paths.py b/src/bakrest/paths.py index 147d7e8..b234111 100644 --- a/src/bakrest/paths.py +++ b/src/bakrest/paths.py @@ -22,5 +22,45 @@ def default_config_path() -> Path: return program_data_dir() / "config.toml" -def expand_path(value: str) -> Path: - return Path(os.path.expandvars(value)).expanduser() +def expand_path(value: str, target_user: str | None = None) -> Path: + expanded = expand_user_vars(value, target_user) + return Path(os.path.expandvars(expanded)).expanduser() + + +def expand_user_vars(value: str, target_user: str | None = None) -> str: + if not target_user: + return value + + user = target_user.strip().lstrip(".\\") + if not user: + return value + + profile = Path("C:/Users") / user + replacements = { + "%USERPROFILE%": str(profile), + "%APPDATA%": str(profile / "AppData" / "Roaming"), + "%LOCALAPPDATA%": str(profile / "AppData" / "Local"), + "%TEMP%": str(profile / "AppData" / "Local" / "Temp"), + "%TMP%": str(profile / "AppData" / "Local" / "Temp"), + } + + expanded = value + for token, replacement in replacements.items(): + expanded = _replace_case_insensitive(expanded, token, replacement) + return expanded + + +def _replace_case_insensitive(value: str, old: str, new: str) -> str: + lower_value = value.lower() + lower_old = old.lower() + start = 0 + parts: list[str] = [] + while True: + index = lower_value.find(lower_old, start) + if index == -1: + parts.append(value[start:]) + break + parts.append(value[start:index]) + parts.append(new) + start = index + len(old) + return "".join(parts) diff --git a/tests/test_config_and_filters.py b/tests/test_config_and_filters.py index 47cfbd7..7bc18dd 100644 --- a/tests/test_config_and_filters.py +++ b/tests/test_config_and_filters.py @@ -22,6 +22,25 @@ def test_parse_config_normalizes_extensions(tmp_path: Path) -> None: assert config.watch.include_extensions == frozenset({".jpg", ".png"}) +def test_parse_config_expands_watch_paths_for_target_user(tmp_path: Path) -> None: + config = parse_config( + { + "watch": { + "target_user": "pettirosso", + "include_dirs": ["%USERPROFILE%\\Documents"], + "include_extensions": [".docx"], + "exclude_dirs": ["%APPDATA%"], + "exclude_patterns": [], + } + }, + tmp_path / "config.toml", + ) + + assert config.watch.target_user == "pettirosso" + assert config.watch.include_dirs == (Path("C:/Users/pettirosso/Documents"),) + assert config.watch.exclude_dirs == (Path("C:/Users/pettirosso/AppData/Roaming"),) + + def test_parse_config_reads_multiple_remote_destinations(tmp_path: Path) -> None: config = parse_config( { diff --git a/tests/test_paths.py b/tests/test_paths.py new file mode 100644 index 0000000..279d34e --- /dev/null +++ b/tests/test_paths.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from pathlib import Path + +from bakrest.paths import expand_path + + +def test_expand_path_uses_target_user_for_userprofile() -> None: + assert expand_path("%USERPROFILE%\\Documents", "pettirosso") == Path( + "C:/Users/pettirosso/Documents" + ) + + +def test_expand_path_uses_target_user_for_appdata() -> None: + assert expand_path("%APPDATA%\\Microsoft", ".\\pettirosso") == Path( + "C:/Users/pettirosso/AppData/Roaming/Microsoft" + ) + + +def test_expand_path_uses_target_user_for_temp() -> None: + assert expand_path("%TEMP%", "pettirosso") == Path( + "C:/Users/pettirosso/AppData/Local/Temp" + )