Files
cmautobuy/client/src/settings_ui_event.py
T

351 lines
12 KiB
Python
Raw Normal View History

2026-08-06 17:57:49 +08:00
"""设置页事件、当前 Client 本地保存和后台登记。
2026-08-06 17:57:49 +08:00
本文件不直接写 SQL。SQLite 保存和 Admin HTTP 请求由 Worker 在线程中执行,
后台结果只通过信号返回主线程更新页面。
"""
2026-08-06 17:57:49 +08:00
from typing import Optional
2026-08-06 17:57:49 +08:00
from PyQt5.QtCore import QCoreApplication, QObject, QThread, pyqtSignal, pyqtSlot
2026-08-06 17:57:49 +08:00
from .admin_gateway import (
AdminGatewayError,
AndroidDeviceInfo,
ClaimCapabilities,
ClientInfo,
ClientRegistrationGateway,
)
from .current_client_service import (
CurrentClientService,
generate_client_device_id,
)
from .http_admin_gateway import DEFAULT_ADMIN_BASE_URL, HttpAdminGateway
from .settings_repository import SettingsRepository
DEVICE_ID_PLACEHOLDER = "待生成"
2026-08-06 17:57:49 +08:00
class CurrentClientSaveWorker(QObject):
"""在线程中先保存本地身份,再登记到 Admin。"""
2026-08-06 17:57:49 +08:00
localSaved = pyqtSignal(str, str)
localSaveFailed = pyqtSignal(str)
registrationSucceeded = pyqtSignal(str)
registrationFailed = pyqtSignal(str)
completed = pyqtSignal()
def __init__(
self,
service: CurrentClientService,
gateway: Optional[ClientRegistrationGateway],
client_name: str,
capabilities: ClaimCapabilities,
gateway_error: str = "",
):
super().__init__()
self._service = service
self._gateway = gateway
self._client_name = client_name
self._capabilities = capabilities
self._gateway_error = gateway_error
self._cancelled = False
def cancel(self) -> None:
"""阻止尚未开始的远端登记;已经发出的 HTTP 等超时返回。"""
self._cancelled = True
@pyqtSlot()
def run(self) -> None:
try:
try:
saved = self._service.save(self._client_name)
except Exception as exc:
self.localSaveFailed.emit(str(exc) or "无法写入本地数据库")
return
self.localSaved.emit(saved.client_id, saved.client_name)
if self._cancelled:
return
if self._gateway is None:
self.registrationFailed.emit(
self._gateway_error or "Admin Gateway 尚未配置"
)
return
try:
receipt = self._gateway.register_client(
ClientInfo(saved.client_id, saved.client_name),
self._capabilities,
)
except AdminGatewayError as exc:
self.registrationFailed.emit(str(exc))
except Exception as exc:
self.registrationFailed.emit(str(exc) or "Admin 登记失败")
else:
self.registrationSucceeded.emit(receipt.registered_at)
finally:
self.completed.emit()
class SettingsPageEventBinder(QObject):
"""绑定设备管理控件,并向应用层发出稳定事件。"""
searchRequested = pyqtSignal()
connectRequested = pyqtSignal(str)
currentDeviceSaveRequested = pyqtSignal(str, str)
saveRequested = pyqtSignal(str, str)
deleteRequested = pyqtSignal(str)
2026-08-06 17:57:49 +08:00
def __init__(
self,
page,
settings_repository: Optional[SettingsRepository] = None,
admin_gateway: Optional[ClientRegistrationGateway] = None,
):
super().__init__(page)
self._page = page
self._busy = False
2026-08-06 17:57:49 +08:00
self._current_device_busy = False
self._closing = False
self._thread: Optional[QThread] = None
self._worker: Optional[CurrentClientSaveWorker] = None
repository = settings_repository or SettingsRepository()
self._client_service = CurrentClientService(repository)
self._admin_gateway = admin_gateway
self._gateway_error = ""
if self._admin_gateway is None:
base_url = repository.get("admin.base_url", DEFAULT_ADMIN_BASE_URL)
timeout_value = repository.get("admin.request_timeout_seconds", 3.0)
try:
timeout_seconds = float(timeout_value)
self._admin_gateway = HttpAdminGateway(
base_url if isinstance(base_url, str) else "",
timeout_seconds=timeout_seconds,
)
except (TypeError, ValueError) as exc:
self._gateway_error = str(exc)
page.currentDeviceSaveButton.clicked.connect(
self._request_save_current_device
)
2026-08-06 17:57:49 +08:00
self.currentDeviceSaveRequested.connect(
self._start_save_current_device
)
page.searchButton.clicked.connect(self._request_search)
page.connectButton.clicked.connect(self._request_connect)
page.saveButton.clicked.connect(self._request_save)
page.deleteButton.clicked.connect(self._request_delete)
page.addressInput.textChanged.connect(self._sync_button_state)
page.deviceTableModel.checkedDeviceChanged.connect(self._sync_button_state)
2026-08-06 17:57:49 +08:00
page.destroyed.connect(self.shutdown)
application = QCoreApplication.instance()
if application is not None:
application.aboutToQuit.connect(self.shutdown)
self._load_current_client()
self._sync_button_state()
@pyqtSlot()
def _request_save_current_device(self) -> None:
2026-08-06 17:57:49 +08:00
if self._busy or self._current_device_busy:
return
device_id = self._page.deviceIdInput.text().strip()
device_name = self._page.deviceNameInput.text().strip()
self._page.deviceNameInput.setText(device_name)
self.currentDeviceSaveRequested.emit(device_id, device_name)
2026-08-06 17:57:49 +08:00
@pyqtSlot(str, str)
def _start_save_current_device(
self, _displayed_device_id: str, device_name: str
) -> None:
if self._closing or self._current_device_busy:
return
try:
device = self._selected_android_device()
capabilities = ClaimCapabilities(device=device)
except ValueError as exc:
self._page.set_current_device_status(f"保存失败:{exc}")
return
self._current_device_busy = True
self._sync_button_state()
self._page.set_current_device_status("正在保存本地设备信息…")
thread = QThread(self)
worker = CurrentClientSaveWorker(
self._client_service,
self._admin_gateway,
device_name,
capabilities,
self._gateway_error,
)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.localSaved.connect(self._on_local_saved)
worker.localSaveFailed.connect(self._on_local_save_failed)
worker.registrationSucceeded.connect(self._on_registration_succeeded)
worker.registrationFailed.connect(self._on_registration_failed)
worker.completed.connect(thread.quit)
worker.completed.connect(worker.deleteLater)
thread.finished.connect(self._on_save_thread_finished)
thread.finished.connect(thread.deleteLater)
self._thread = thread
self._worker = worker
thread.start()
@pyqtSlot()
def _request_search(self) -> None:
if self._busy:
return
self.searchRequested.emit()
@pyqtSlot()
def _request_connect(self) -> None:
if self._busy:
return
address = self._page.addressInput.text().strip()
if not address:
self._page.deviceStatusLabel.setText(
"连接失败:请先填写无线调试地址,例如 192.168.0.173:5555"
)
self._page.addressInput.setFocus()
return
self.connectRequested.emit(address)
@pyqtSlot()
def _request_save(self) -> None:
if self._busy:
return
serial = self._page.deviceTableModel.checked_serial
if not serial:
self._page.deviceStatusLabel.setText("保存失败:请先勾选一台已连接设备")
self._page.deviceTable.setFocus()
return
device_name = self._page.deviceNameInput.text().strip()
self.saveRequested.emit(device_name, serial)
@pyqtSlot()
def _request_delete(self) -> None:
if self._busy:
return
serial = self._page.deviceTableModel.checked_serial
if not serial:
self._page.deviceStatusLabel.setText("删除失败:请先勾选一台设备")
self._page.deviceTable.setFocus()
return
self.deleteRequested.emit(serial)
@pyqtSlot()
def _sync_button_state(self, *_args) -> None:
serial = self._page.deviceTableModel.checked_serial
has_address = bool(self._page.addressInput.text().strip())
2026-08-06 17:57:49 +08:00
self._page.currentDeviceSaveButton.setEnabled(
not self._busy and not self._current_device_busy
)
self._page.searchButton.setEnabled(not self._busy)
self._page.connectButton.setEnabled(not self._busy and has_address)
self._page.saveButton.setEnabled(not self._busy and bool(serial))
self._page.deleteButton.setEnabled(not self._busy and bool(serial))
def set_busy(self, busy: bool, message: str = "") -> None:
"""切换界面忙碌状态,防止用户重复提交设备命令。"""
self._busy = busy
self._sync_button_state()
if message:
self._page.deviceStatusLabel.setText(message)
2026-08-06 17:57:49 +08:00
def _load_current_client(self) -> None:
"""页面创建时恢复本地 Client 信息。"""
try:
saved = self._client_service.load()
except Exception as exc:
self._page.set_current_device_status(
f"读取本地设备信息失败:{str(exc) or '数据库不可用'}"
)
return
self._page.set_client_info(saved.client_id, saved.client_name)
if saved.client_id:
self._page.set_current_device_status("本地设备信息已加载")
def _selected_android_device(self) -> Optional[AndroidDeviceInfo]:
serial = self._page.deviceTableModel.checked_serial.strip()
return AndroidDeviceInfo(serial) if serial else None
@pyqtSlot(str, str)
def _on_local_saved(self, client_id: str, client_name: str) -> None:
if self._closing:
return
self._page.set_client_info(client_id, client_name)
self._page.set_current_device_status(
"本地已保存,正在登记到 Admin…"
)
@pyqtSlot(str)
def _on_local_save_failed(self, message: str) -> None:
if self._closing:
return
self._page.set_current_device_status(f"本地保存失败:{message}")
@pyqtSlot(str)
def _on_registration_succeeded(self, _registered_at: str) -> None:
if self._closing:
return
self._page.set_current_device_status("本地已保存,已登记到 Admin")
@pyqtSlot(str)
def _on_registration_failed(self, message: str) -> None:
if self._closing:
return
self._page.set_current_device_status(
f"本地已保存,Admin 登记失败:{message};可再次点击保存重试"
)
@pyqtSlot()
def _on_save_thread_finished(self) -> None:
self._worker = None
self._thread = None
self._current_device_busy = False
if not self._closing:
self._sync_button_state()
@pyqtSlot()
def shutdown(self) -> None:
"""窗口关闭时忽略迟到结果,并等待短请求安全结束。"""
if self._closing:
return
self._closing = True
worker = self._worker
thread = self._thread
if worker is not None:
worker.cancel()
for signal, slot in (
(worker.localSaved, self._on_local_saved),
(worker.localSaveFailed, self._on_local_save_failed),
(worker.registrationSucceeded, self._on_registration_succeeded),
(worker.registrationFailed, self._on_registration_failed),
):
try:
signal.disconnect(slot)
except TypeError:
pass
if thread is not None and thread.isRunning():
thread.quit()
thread.wait(4000)