zapret-kvn/xray_fluent/network/background_workers.py
loop-uh d5a4715771
Some checks failed
Windows project source guards / test (push) Has been cancelled
feat: use official Amnezia transport and organize runtime modules
2026-09-06 00:14:08 +03:00

128 lines
4.2 KiB
Python

from __future__ import annotations
from collections.abc import Callable
from typing import TYPE_CHECKING
from PyQt6.QtCore import QThread, pyqtSignal
from ..importer.subscription_http import fetch_subscription
from ..importer.subscription_parser import parse_subscription_payload
if TYPE_CHECKING:
from ..profiles.models import AppState
from ..profiles.storage import StateStorage
class ProxyProtectionResolver(QThread):
"""Resolve one proxy endpoint without blocking the Qt event loop."""
resolved = pyqtSignal(int, str, object, object)
def __init__(
self,
generation: int,
server: str,
resolver: Callable[[str], set[str]],
parent=None,
) -> None:
super().__init__(parent)
self._generation = generation
self._server = server
self._resolver = resolver
def run(self) -> None:
try:
addresses = self._resolver(self._server)
error: Exception | None = None
except Exception as exc: # DNS errors are reported back on the GUI thread
addresses = set()
error = exc
self.resolved.emit(self._generation, self._server, addresses, error)
class TargetProfileResolver(QThread):
"""Resolve every host in one immutable selected-server endpoint spec."""
resolved = pyqtSignal(int, object, object, object)
def __init__(self, generation: int, spec, resolver: Callable, parent=None) -> None:
super().__init__(parent)
self._generation = generation
self._spec = spec
self._resolver = resolver
def run(self) -> None:
try:
endpoint = self._resolver(self._spec)
error: Exception | None = None
except Exception as exc:
endpoint = None
error = exc
self.resolved.emit(self._generation, self._spec, endpoint, error)
class StateSaveWorker(QThread):
"""Serialize, encrypt and write a large state away from the GUI thread."""
failed = pyqtSignal(str)
def __init__(self, storage: StateStorage, state: AppState, parent=None) -> None:
super().__init__(parent)
self._storage = storage
self._state = state
def run(self) -> None:
try:
self._storage.save(self._state)
except Exception as exc:
self.failed.emit(str(exc))
class SubscriptionUpdateWorker(QThread):
"""Fetch and parse a subscription without mutating application state."""
completed = pyqtSignal(object, object, object) # Subscription, fetch result, parsed result | None
failed = pyqtSignal(object, str) # Subscription, sanitized message
progress = pyqtSignal(str, str) # subscription id, phase
def __init__(
self,
subscription,
*,
mode: str,
proxy_port: int | None,
force_refresh: bool = False,
parent=None,
) -> None:
super().__init__(parent)
self._subscription = subscription
self._mode = mode
self._proxy_port = proxy_port
self._force_refresh = bool(force_refresh)
def run(self) -> None:
try:
self.progress.emit(self._subscription.id, "download")
fetched = fetch_subscription(
self._subscription,
mode=self._mode,
proxy_port=self._proxy_port,
force_refresh=self._force_refresh,
)
if fetched.not_modified:
self.completed.emit(self._subscription, fetched, None)
return
self.progress.emit(self._subscription.id, "parse")
parsed = parse_subscription_payload(
fetched.data,
headers=fetched.headers,
source_url=self._subscription.url,
include_pattern=self._subscription.include_pattern,
exclude_pattern=self._subscription.exclude_pattern,
hidden_source_keys=set(self._subscription.hidden_source_keys),
)
self.completed.emit(self._subscription, fetched, parsed)
except Exception as exc:
from ..importer.subscription_http import sanitize_fetch_error
self.failed.emit(self._subscription, sanitize_fetch_error(exc))