87 lines
2.7 KiB
Python
87 lines
2.7 KiB
Python
"""供 T-304 使用的稳定本地配置与恢复快照。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
import re
|
|
|
|
from cmbuyer_client.core.models import ClaimRequest, ClaimedTask, DeviceCredentials, RenewRequest
|
|
from cmbuyer_client.core.validation import require_string, require_uuid4
|
|
|
|
|
|
LOOPBACK_SERVICE_URL = "http://127.0.0.1:8080"
|
|
PROFILE_ID_RE = re.compile(r"[a-z0-9][a-z0-9_-]{0,63}")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProfileSettings:
|
|
profile_id: str
|
|
service_url: str
|
|
device_id: str
|
|
adb_path: str
|
|
adb_serial: str
|
|
transport: str
|
|
poll_interval_seconds: int = 15
|
|
failure_threshold: int = 3
|
|
http_timeout_seconds: int = 10
|
|
step_timeout_seconds: int = 45
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.profile_id, str) or PROFILE_ID_RE.fullmatch(self.profile_id) is None:
|
|
raise ValueError("invalid_profile_id")
|
|
if self.service_url != LOOPBACK_SERVICE_URL:
|
|
raise ValueError("service_url_not_allowed")
|
|
require_uuid4(self.device_id, "invalid_device_id")
|
|
require_string(self.adb_path, "invalid_adb_path", maximum=1024)
|
|
require_string(self.adb_serial, "invalid_adb_serial", maximum=200)
|
|
if self.transport not in ("usb", "wifi"):
|
|
raise ValueError("invalid_transport")
|
|
_range(self.poll_interval_seconds, 5, 300, "invalid_poll_interval")
|
|
_range(self.failure_threshold, 1, 10, "invalid_failure_threshold")
|
|
_range(self.http_timeout_seconds, 1, 120, "invalid_http_timeout")
|
|
_range(self.step_timeout_seconds, 5, 300, "invalid_step_timeout")
|
|
|
|
|
|
@dataclass(frozen=True, repr=False)
|
|
class LoadedProfile:
|
|
settings: ProfileSettings
|
|
credentials: DeviceCredentials = field(repr=False)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"LoadedProfile(settings={self.settings!r}, credentials=[已隐藏])"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PollingSession:
|
|
profile_id: str
|
|
session_id: str
|
|
accept_new: bool
|
|
|
|
def __post_init__(self) -> None:
|
|
require_uuid4(self.session_id, "invalid_session_id")
|
|
if type(self.accept_new) is not bool:
|
|
raise ValueError("invalid_accept_new")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PendingEvidence:
|
|
task_id: str
|
|
attempt_id: str
|
|
kind: str
|
|
upload_key: str
|
|
status: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RecoverySnapshot:
|
|
session: PollingSession | None
|
|
pending_claim: ClaimRequest | None
|
|
active_claim: ClaimedTask | None = field(repr=False)
|
|
pending_renew: RenewRequest | None = field(repr=False)
|
|
pending_evidence: tuple[PendingEvidence, ...]
|
|
|
|
|
|
def _range(value: object, minimum: int, maximum: int, reason: str) -> None:
|
|
if type(value) is not int or not minimum <= value <= maximum:
|
|
raise ValueError(reason)
|