Init
This commit is contained in:
100
ptdiag/registry.py
Normal file
100
ptdiag/registry.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""Read-only Wine registry snapshots for reversible desktop configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from .filesystem import sha256_file
|
||||
|
||||
|
||||
_VALUE_RE = re.compile(r'^(?P<name>"(?:\\.|[^"\\])*")=(?P<value>.*)$')
|
||||
_SECTION_RE = re.compile(r"^\[(?P<name>.*)\](?:\s+[0-9]+)?$")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RegistryValue:
|
||||
exists: bool
|
||||
value: str | None
|
||||
|
||||
def to_dict(self) -> dict[str, str | bool | None]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DesktopRegistrySnapshot:
|
||||
user_reg: Path
|
||||
sha256: str
|
||||
desktop_name: str
|
||||
selected_desktop: RegistryValue
|
||||
desktop_size: RegistryValue
|
||||
|
||||
def to_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"user_reg": str(self.user_reg),
|
||||
"sha256": self.sha256,
|
||||
"desktop_name": self.desktop_name,
|
||||
"selected_desktop": self.selected_desktop.to_dict(),
|
||||
"desktop_size": self.desktop_size.to_dict(),
|
||||
}
|
||||
|
||||
|
||||
def snapshot_desktop_registry(prefix: Path, desktop_name: str) -> DesktopRegistrySnapshot:
|
||||
user_reg = prefix / "user.reg"
|
||||
sections = read_registry_string_values(user_reg)
|
||||
selected = sections.get(r"Software\Wine\Explorer", {}).get("Desktop")
|
||||
size = sections.get(r"Software\Wine\Explorer\Desktops", {}).get(desktop_name)
|
||||
return DesktopRegistrySnapshot(
|
||||
user_reg=user_reg,
|
||||
sha256=sha256_file(user_reg),
|
||||
desktop_name=desktop_name,
|
||||
selected_desktop=RegistryValue(selected is not None, selected),
|
||||
desktop_size=RegistryValue(size is not None, size),
|
||||
)
|
||||
|
||||
|
||||
def desktop_registry_restored(
|
||||
before: DesktopRegistrySnapshot,
|
||||
after: DesktopRegistrySnapshot,
|
||||
) -> bool:
|
||||
return (
|
||||
before.desktop_name == after.desktop_name
|
||||
and before.selected_desktop == after.selected_desktop
|
||||
and before.desktop_size == after.desktop_size
|
||||
)
|
||||
|
||||
|
||||
def read_registry_string_values(path: Path) -> dict[str, dict[str, str]]:
|
||||
sections: dict[str, dict[str, str]] = {}
|
||||
current: dict[str, str] | None = None
|
||||
with path.open("r", encoding="utf-8", errors="replace") as handle:
|
||||
for raw in handle:
|
||||
line = raw.rstrip("\r\n")
|
||||
section = _SECTION_RE.match(line)
|
||||
if section is not None:
|
||||
name = section.group("name").replace(r"\\", "\\")
|
||||
current = sections.setdefault(name, {})
|
||||
continue
|
||||
if current is None:
|
||||
continue
|
||||
match = _VALUE_RE.match(line)
|
||||
if match is None:
|
||||
continue
|
||||
name = _decode_string(match.group("name"))
|
||||
value_text = match.group("value")
|
||||
if name is None or not value_text.startswith('"'):
|
||||
continue
|
||||
value = _decode_string(value_text)
|
||||
if value is not None:
|
||||
current[name] = value
|
||||
return sections
|
||||
|
||||
|
||||
def _decode_string(value: str) -> str | None:
|
||||
try:
|
||||
result = ast.literal_eval(value)
|
||||
except (SyntaxError, ValueError):
|
||||
return None
|
||||
return result if isinstance(result, str) else None
|
||||
Reference in New Issue
Block a user