Normalizza percorsi e volumi USN
This commit is contained in:
20
README.md
20
README.md
@@ -53,6 +53,26 @@ python -m bakrest.watchdog_runner
|
||||
|
||||
Il watchdog usa il journal USN NTFS per distinguere modifiche reali da eventi rumorosi generati da navigazione, anteprime o metadati. La lettura del journal puo' richiedere privilegi elevati: il servizio eseguito come `LocalSystem` e' il percorso consigliato. Se USN non e' disponibile o non e' leggibile, il servizio registra un warning e usa un fallback conservativo.
|
||||
|
||||
Se nel log compare:
|
||||
|
||||
```text
|
||||
USN journal unavailable for \\.\D:: [WinError 1179] Il journal delle modifiche al volume non e' attivo.
|
||||
```
|
||||
|
||||
abilita il journal USN sul volume da PowerShell/CMD amministratore:
|
||||
|
||||
```powershell
|
||||
fsutil usn createjournal m=134217728 a=33554432 D:
|
||||
```
|
||||
|
||||
Poi riavvia il servizio. Per verificare:
|
||||
|
||||
```powershell
|
||||
fsutil usn queryjournal D:
|
||||
```
|
||||
|
||||
Nella configurazione, per cartelle utente puoi usare token portabili come `%DOCUMENTS%`, `%DOWNLOADS%`, `%PICTURES%`, `%MUSIC%`, `%VIDEOS%`, `%FAVORITES%`. Con `target_user = "utente"` vengono espansi sotto `C:\Users\utente`.
|
||||
|
||||
## Avvio tray app per test
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -326,6 +326,8 @@ Quando si monitora una directory molto ampia, ad esempio un'intera unita' `D:\`,
|
||||
- eventi di soli metadati, chiusura handle, indicizzazione, security o basic info devono essere ignorati;
|
||||
- se USN non e' disponibile su un volume, il servizio deve fare fallback conservativo e registrare un warning nei log.
|
||||
|
||||
I nomi volume devono essere normalizzati, ad esempio `c:` e `C:` devono riferirsi allo stesso reader USN `\\.\C:`. I percorsi drive-only come `d:` devono essere normalizzati a `D:\`.
|
||||
|
||||
## Configurazione
|
||||
|
||||
Il file di configurazione dovra' definire almeno:
|
||||
|
||||
@@ -24,7 +24,8 @@ def default_config_path() -> Path:
|
||||
|
||||
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()
|
||||
expanded_path = Path(os.path.expandvars(expanded)).expanduser()
|
||||
return normalize_windows_path(expanded_path)
|
||||
|
||||
|
||||
def expand_user_vars(value: str, target_user: str | None = None) -> str:
|
||||
@@ -38,6 +39,13 @@ def expand_user_vars(value: str, target_user: str | None = None) -> str:
|
||||
profile = Path("C:/Users") / user
|
||||
replacements = {
|
||||
"%USERPROFILE%": str(profile),
|
||||
"%DESKTOP%": str(profile / "Desktop"),
|
||||
"%DOCUMENTS%": str(profile / "Documents"),
|
||||
"%DOWNLOADS%": str(profile / "Downloads"),
|
||||
"%PICTURES%": str(profile / "Pictures"),
|
||||
"%MUSIC%": str(profile / "Music"),
|
||||
"%VIDEOS%": str(profile / "Videos"),
|
||||
"%FAVORITES%": str(profile / "Favorites"),
|
||||
"%APPDATA%": str(profile / "AppData" / "Roaming"),
|
||||
"%LOCALAPPDATA%": str(profile / "AppData" / "Local"),
|
||||
"%TEMP%": str(profile / "AppData" / "Local" / "Temp"),
|
||||
@@ -47,7 +55,43 @@ def expand_user_vars(value: str, target_user: str | None = None) -> str:
|
||||
expanded = value
|
||||
for token, replacement in replacements.items():
|
||||
expanded = _replace_case_insensitive(expanded, token, replacement)
|
||||
return expanded
|
||||
return _normalize_user_known_folder_aliases(expanded, profile)
|
||||
|
||||
|
||||
def normalize_windows_path(path: Path) -> Path:
|
||||
path_text = str(path)
|
||||
if len(path_text) == 2 and path_text[1] == ":":
|
||||
return Path(f"{path_text.upper()}\\")
|
||||
if len(path_text) >= 2 and path_text[1] == ":":
|
||||
return Path(path_text[0].upper() + path_text[1:])
|
||||
return path
|
||||
|
||||
|
||||
def _normalize_user_known_folder_aliases(value: str, profile: Path) -> str:
|
||||
aliases = {
|
||||
"download": "Downloads",
|
||||
"downloads": "Downloads",
|
||||
"documenti": "Documents",
|
||||
"immagini": "Pictures",
|
||||
"musica": "Music",
|
||||
"video": "Videos",
|
||||
"preferiti": "Favorites",
|
||||
}
|
||||
path = Path(value)
|
||||
try:
|
||||
relative = path.relative_to(profile)
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
if not relative.parts:
|
||||
return value
|
||||
|
||||
first = relative.parts[0]
|
||||
replacement = aliases.get(first.lower())
|
||||
if not replacement:
|
||||
return value
|
||||
|
||||
return str(profile.joinpath(replacement, *relative.parts[1:]))
|
||||
|
||||
|
||||
def _replace_case_insensitive(value: str, old: str, new: str) -> str:
|
||||
|
||||
@@ -213,7 +213,7 @@ def volume_name_for_path(path: Path) -> str | None:
|
||||
drive = path.drive
|
||||
if not drive:
|
||||
return None
|
||||
return f"\\\\.\\{drive.rstrip(':')}:"
|
||||
return f"\\\\.\\{drive.rstrip(':').upper()}:"
|
||||
|
||||
|
||||
def file_reference_number(path: Path) -> int:
|
||||
|
||||
@@ -21,3 +21,17 @@ def test_expand_path_uses_target_user_for_temp() -> None:
|
||||
assert expand_path("%TEMP%", "pettirosso") == Path(
|
||||
"C:/Users/pettirosso/AppData/Local/Temp"
|
||||
)
|
||||
|
||||
|
||||
def test_expand_path_normalizes_drive_root() -> None:
|
||||
assert expand_path("d:") == Path("D:/")
|
||||
|
||||
|
||||
def test_expand_path_maps_downloads_token_for_target_user() -> None:
|
||||
assert expand_path("%DOWNLOADS%", "utente") == Path("C:/Users/utente/Downloads")
|
||||
|
||||
|
||||
def test_expand_path_maps_italian_known_folder_aliases() -> None:
|
||||
assert expand_path("%USERPROFILE%\\Immagini", "utente") == Path("C:/Users/utente/Pictures")
|
||||
assert expand_path("%USERPROFILE%\\Musica", "utente") == Path("C:/Users/utente/Music")
|
||||
assert expand_path("%USERPROFILE%\\video", "utente") == Path("C:/Users/utente/Videos")
|
||||
|
||||
@@ -11,7 +11,9 @@ from bakrest.usn_journal import (
|
||||
USN_REASON_RENAME_NEW_NAME,
|
||||
is_meaningful_usn_reason,
|
||||
parse_usn_records,
|
||||
volume_name_for_path,
|
||||
)
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_usn_reason_filter_accepts_real_data_changes() -> None:
|
||||
@@ -50,3 +52,8 @@ def test_parse_usn_record_v3_reason_and_file_reference() -> None:
|
||||
assert len(records) == 1
|
||||
assert records[0].file_reference_number == 12345
|
||||
assert records[0].reason == USN_REASON_FILE_CREATE
|
||||
|
||||
|
||||
def test_volume_name_is_normalized_to_uppercase_drive() -> None:
|
||||
assert volume_name_for_path(Path("c:/tmp")) == "\\\\.\\C:"
|
||||
assert volume_name_for_path(Path("C:/tmp")) == "\\\\.\\C:"
|
||||
|
||||
Reference in New Issue
Block a user