Initial SyncVito release with installer updater

This commit is contained in:
Davito committed 2026-09-23 19:40:36 +02:00
commit b9e4c09f49
41 files changed
+4331

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
"""Ermittlung von Prozessen, die Dateien unter Windows sperren."""
from __future__ import annotations
import ctypes
import os
from ctypes import wintypes
from dataclasses import dataclass
from pathlib import Path
__all__ = ["LockingProcess", "find_locking_processes", "is_file_locked"]
@dataclass(frozen=True, slots=True)
class LockingProcess:
"""Ein vom Windows Restart Manager gemeldeter Prozess."""
pid: int
name: str
def is_file_locked(path: Path) -> bool:
"""Prüft eine vorhandene Datei durch exklusives Öffnen ohne Schreibzugriff."""
if not path.exists() or os.name != "nt":
return False
handle = ctypes.windll.kernel32.CreateFileW(
str(path),
0x80000000,
0,
None,
3,
0x80,
None,
)
invalid_handle = ctypes.c_void_p(-1).value
if handle == invalid_handle:
return ctypes.get_last_error() in {5, 32, 33}
ctypes.windll.kernel32.CloseHandle(handle)
return False
def find_locking_processes(path: Path) -> list[LockingProcess]:
"""Liefert sperrende Prozesse über die Windows-Restart-Manager-API."""
if os.name != "nt" or not path.exists():
return []
try:
return _restart_manager_processes(path)
except (OSError, AttributeError, ctypes.ArgumentError):
return []
def _restart_manager_processes(path: Path) -> list[LockingProcess]:
rstrtmgr = ctypes.WinDLL("Rstrtmgr")
session_handle = wintypes.DWORD()
session_key = ctypes.create_unicode_buffer(33)
if rstrtmgr.RmStartSession(ctypes.byref(session_handle), 0, session_key) != 0:
return []
try:
resources = (wintypes.LPCWSTR * 1)(str(path))
if rstrtmgr.RmRegisterResources(session_handle, 1, resources, 0, None, 0, None) != 0:
return []
needed = wintypes.UINT(0)
count = wintypes.UINT(0)
reboot_reasons = wintypes.DWORD(0)
result = rstrtmgr.RmGetList(
session_handle,
ctypes.byref(needed),
ctypes.byref(count),
None,
ctypes.byref(reboot_reasons),
)
if result not in {0, 234} or needed.value == 0:
return []
process_info_type = _rm_process_info_type()
process_array = (process_info_type * needed.value)()
count.value = needed.value
result = rstrtmgr.RmGetList(
session_handle,
ctypes.byref(needed),
ctypes.byref(count),
process_array,
ctypes.byref(reboot_reasons),
)
if result != 0:
return []
return [
LockingProcess(
pid=int(item.Process.dwProcessId), name=item.strAppName or f"Prozess {item.Process.dwProcessId}"
)
for item in process_array[: count.value]
]
finally:
rstrtmgr.RmEndSession(session_handle)
def _rm_process_info_type() -> type[ctypes.Structure]:
class FileTime(ctypes.Structure):
_fields_ = [("dwLowDateTime", wintypes.DWORD), ("dwHighDateTime", wintypes.DWORD)]
class UniqueProcess(ctypes.Structure):
_fields_ = [("dwProcessId", wintypes.DWORD), ("ProcessStartTime", FileTime)]
class ProcessInfo(ctypes.Structure):
_fields_ = [
("Process", UniqueProcess),
("strAppName", wintypes.WCHAR * 256),
("strServiceShortName", wintypes.WCHAR * 64),
("ApplicationType", wintypes.UINT),
("AppStatus", wintypes.ULONG),
("TSSessionId", wintypes.DWORD),
("bRestartable", wintypes.BOOL),
]
return ProcessInfo