982 lines
33 KiB
Python
982 lines
33 KiB
Python
"""设置页事件、ADB 搜索、当前 Client 本地保存和后台登记。
|
|
|
|
本文件不直接写 SQL。ADB 搜索、SQLite 写入和 Admin HTTP 请求由 Worker 在线程中
|
|
执行;页面启动时只同步读取少量索引设置,后台结果通过信号返回主线程更新页面。
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
from PyQt5.QtCore import (
|
|
QCoreApplication,
|
|
QObject,
|
|
QThread,
|
|
QTimer,
|
|
pyqtSignal,
|
|
pyqtSlot,
|
|
)
|
|
|
|
from .android_device_service import (
|
|
AndroidDeviceConversionCancelled,
|
|
AndroidDeviceSearchError,
|
|
AndroidDeviceService,
|
|
)
|
|
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 .selected_android_device_service import SelectedAndroidDeviceService
|
|
from .settings_repository import SettingsRepository
|
|
from .settings_ui import AndroidDeviceRow
|
|
|
|
DEVICE_ID_PLACEHOLDER = "待生成"
|
|
|
|
|
|
class CurrentClientSaveWorker(QObject):
|
|
"""在线程中先保存本地身份,再登记到 Admin。"""
|
|
|
|
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 AndroidDeviceSearchWorker(QObject):
|
|
"""在后台搜索设备,也可先恢复一台已保存设备。"""
|
|
|
|
succeeded = pyqtSignal(object)
|
|
failed = pyqtSignal(str)
|
|
completed = pyqtSignal()
|
|
|
|
def __init__(
|
|
self,
|
|
service: AndroidDeviceService,
|
|
restore_serial: str = "",
|
|
):
|
|
super().__init__()
|
|
self._service = service
|
|
self._restore_serial = restore_serial
|
|
self._cancelled = False
|
|
|
|
def cancel(self) -> None:
|
|
"""停止后续属性查询;正在执行的命令等待自身超时结束。"""
|
|
|
|
self._cancelled = True
|
|
|
|
@pyqtSlot()
|
|
def run(self) -> None:
|
|
try:
|
|
try:
|
|
if self._restore_serial:
|
|
devices = self._service.restore_saved_device(
|
|
self._restore_serial,
|
|
lambda: self._cancelled,
|
|
)
|
|
else:
|
|
devices = self._service.search(lambda: self._cancelled)
|
|
except AndroidDeviceSearchError as exc:
|
|
if not self._cancelled:
|
|
self.failed.emit(str(exc))
|
|
except Exception as exc:
|
|
if not self._cancelled:
|
|
self.failed.emit(str(exc) or "无法搜索 Android 设备")
|
|
else:
|
|
if not self._cancelled:
|
|
self.succeeded.emit(devices)
|
|
finally:
|
|
self.completed.emit()
|
|
|
|
|
|
class AndroidWifiConversionWorker(QObject):
|
|
"""在后台引导一台 USB 设备切换到 Wi-Fi ADB。"""
|
|
|
|
progressChanged = pyqtSignal(str)
|
|
succeeded = pyqtSignal(object)
|
|
failed = pyqtSignal(str)
|
|
completed = pyqtSignal()
|
|
|
|
def __init__(self, service: AndroidDeviceService, usb_serial: str):
|
|
super().__init__()
|
|
self._service = service
|
|
self._usb_serial = usb_serial
|
|
self._cancelled = False
|
|
|
|
def cancel(self) -> None:
|
|
"""停止轮询和后续命令;正在执行的 ADB 命令等待自身超时。"""
|
|
|
|
self._cancelled = True
|
|
|
|
@pyqtSlot()
|
|
def run(self) -> None:
|
|
try:
|
|
try:
|
|
result = self._service.convert_usb_to_wifi(
|
|
self._usb_serial,
|
|
lambda: self._cancelled,
|
|
self.progressChanged.emit,
|
|
)
|
|
except AndroidDeviceConversionCancelled:
|
|
return
|
|
except AndroidDeviceSearchError as exc:
|
|
if not self._cancelled:
|
|
self.failed.emit(str(exc))
|
|
except Exception as exc:
|
|
if not self._cancelled:
|
|
self.failed.emit(str(exc) or "无法将 USB 设备转为 Wi-Fi")
|
|
else:
|
|
if not self._cancelled:
|
|
self.succeeded.emit(result)
|
|
finally:
|
|
self.completed.emit()
|
|
|
|
|
|
class AndroidDeviceSettingWorker(QObject):
|
|
"""在后台保存或删除当前使用的 Android 设备号。"""
|
|
|
|
succeeded = pyqtSignal(str, str)
|
|
failed = pyqtSignal(str, str)
|
|
completed = pyqtSignal()
|
|
|
|
def __init__(
|
|
self,
|
|
service: SelectedAndroidDeviceService,
|
|
action: str,
|
|
serial: str = "",
|
|
):
|
|
super().__init__()
|
|
self._service = service
|
|
self._action = action
|
|
self._serial = serial
|
|
self._cancelled = False
|
|
|
|
def cancel(self) -> None:
|
|
"""写入无法中断;完成后不再向已关闭页面返回结果。"""
|
|
|
|
self._cancelled = True
|
|
|
|
@pyqtSlot()
|
|
def run(self) -> None:
|
|
try:
|
|
try:
|
|
if self._action == "save":
|
|
serial = self._service.save(self._serial)
|
|
elif self._action == "delete":
|
|
self._service.delete()
|
|
serial = ""
|
|
else:
|
|
raise ValueError("未知的 Android 设备设置操作")
|
|
except Exception as exc:
|
|
if not self._cancelled:
|
|
self.failed.emit(
|
|
self._action,
|
|
str(exc) or "无法写入本地数据库",
|
|
)
|
|
else:
|
|
if not self._cancelled:
|
|
self.succeeded.emit(self._action, serial)
|
|
finally:
|
|
self.completed.emit()
|
|
|
|
|
|
class SettingsPageEventBinder(QObject):
|
|
"""绑定设备管理控件,并向应用层发出稳定事件。"""
|
|
|
|
searchRequested = pyqtSignal()
|
|
connectRequested = pyqtSignal(str)
|
|
convertWifiRequested = pyqtSignal(str)
|
|
currentDeviceSaveRequested = pyqtSignal(str, str)
|
|
saveRequested = pyqtSignal(str, str)
|
|
deleteRequested = pyqtSignal(str)
|
|
|
|
def __init__(
|
|
self,
|
|
page,
|
|
settings_repository: Optional[SettingsRepository] = None,
|
|
admin_gateway: Optional[ClientRegistrationGateway] = None,
|
|
android_device_service: Optional[AndroidDeviceService] = None,
|
|
):
|
|
super().__init__(page)
|
|
self._page = page
|
|
self._busy = False
|
|
self._current_device_busy = False
|
|
self._closing = False
|
|
self._thread: Optional[QThread] = None
|
|
self._worker: Optional[CurrentClientSaveWorker] = None
|
|
self._search_busy = False
|
|
self._search_restore_serial = ""
|
|
self._search_thread: Optional[QThread] = None
|
|
self._search_worker: Optional[AndroidDeviceSearchWorker] = None
|
|
self._wifi_conversion_busy = False
|
|
self._wifi_conversion_thread: Optional[QThread] = None
|
|
self._wifi_conversion_worker: Optional[
|
|
AndroidWifiConversionWorker
|
|
] = None
|
|
self._android_setting_busy = False
|
|
self._android_setting_thread: Optional[QThread] = None
|
|
self._android_setting_worker: Optional[AndroidDeviceSettingWorker] = None
|
|
self._saved_android_serial = ""
|
|
self._android_device_service = (
|
|
android_device_service or AndroidDeviceService()
|
|
)
|
|
|
|
repository = settings_repository or SettingsRepository()
|
|
self._client_service = CurrentClientService(repository)
|
|
self._selected_android_device_service = SelectedAndroidDeviceService(
|
|
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
|
|
)
|
|
self.currentDeviceSaveRequested.connect(
|
|
self._start_save_current_device
|
|
)
|
|
page.searchButton.clicked.connect(self._request_search)
|
|
self.searchRequested.connect(self._start_search)
|
|
page.connectButton.clicked.connect(self._request_connect)
|
|
page.convertWifiButton.clicked.connect(self._request_convert_wifi)
|
|
self.convertWifiRequested.connect(self._start_convert_wifi)
|
|
page.saveButton.clicked.connect(self._request_save)
|
|
self.saveRequested.connect(self._start_save_android_device)
|
|
page.deleteButton.clicked.connect(self._request_delete)
|
|
self.deleteRequested.connect(self._start_delete_android_device)
|
|
page.addressInput.textChanged.connect(self._sync_button_state)
|
|
page.deviceTableModel.checkedDeviceChanged.connect(self._sync_button_state)
|
|
page.destroyed.connect(self.shutdown)
|
|
application = QCoreApplication.instance()
|
|
if application is not None:
|
|
application.aboutToQuit.connect(self.shutdown)
|
|
|
|
self._load_current_client()
|
|
self._load_selected_android_device()
|
|
self._sync_button_state()
|
|
if self._saved_android_serial:
|
|
QTimer.singleShot(0, self._request_restore_saved_android_device)
|
|
|
|
@pyqtSlot()
|
|
def _request_save_current_device(self) -> None:
|
|
if (
|
|
self._busy
|
|
or self._current_device_busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_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)
|
|
|
|
@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
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_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
|
|
or self._current_device_busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
self.searchRequested.emit()
|
|
|
|
@pyqtSlot()
|
|
def _start_search(self) -> None:
|
|
self._start_search_worker()
|
|
|
|
@pyqtSlot()
|
|
def _request_restore_saved_android_device(self) -> None:
|
|
if not self._saved_android_serial:
|
|
return
|
|
self._start_search_worker(self._saved_android_serial)
|
|
|
|
def _start_search_worker(self, restore_serial: str = "") -> None:
|
|
if (
|
|
self._closing
|
|
or self._busy
|
|
or self._current_device_busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
|
|
self._search_busy = True
|
|
self._search_restore_serial = restore_serial
|
|
self._sync_button_state()
|
|
status = (
|
|
f"正在恢复已保存设备:{restore_serial}…"
|
|
if restore_serial
|
|
else "正在搜索 Android 设备…"
|
|
)
|
|
self._page.deviceStatusLabel.setText(status)
|
|
|
|
thread = QThread(self)
|
|
worker = AndroidDeviceSearchWorker(
|
|
self._android_device_service,
|
|
restore_serial,
|
|
)
|
|
worker.moveToThread(thread)
|
|
|
|
thread.started.connect(worker.run)
|
|
worker.succeeded.connect(self._on_search_succeeded)
|
|
worker.failed.connect(self._on_search_failed)
|
|
worker.completed.connect(thread.quit)
|
|
worker.completed.connect(worker.deleteLater)
|
|
thread.finished.connect(self._on_search_thread_finished)
|
|
thread.finished.connect(thread.deleteLater)
|
|
|
|
self._search_thread = thread
|
|
self._search_worker = worker
|
|
thread.start()
|
|
|
|
@pyqtSlot()
|
|
def _request_connect(self) -> None:
|
|
if (
|
|
self._busy
|
|
or self._current_device_busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_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_convert_wifi(self) -> None:
|
|
if (
|
|
self._busy
|
|
or self._current_device_busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
|
|
serial = self._page.deviceTableModel.checked_serial
|
|
device = self._page.deviceTableModel.device_for_serial(serial)
|
|
if device is None or device.status != "device":
|
|
self._page.deviceStatusLabel.setText(
|
|
"转换失败:请先勾选一台已连接的 USB 设备"
|
|
)
|
|
self._page.deviceTable.setFocus()
|
|
return
|
|
if device.connection_type != "USB":
|
|
self._page.deviceStatusLabel.setText(
|
|
"转换失败:当前设备已经是 Wi-Fi 连接"
|
|
)
|
|
return
|
|
self.convertWifiRequested.emit(serial)
|
|
|
|
@pyqtSlot(str)
|
|
def _start_convert_wifi(self, usb_serial: str) -> None:
|
|
if (
|
|
self._closing
|
|
or self._busy
|
|
or self._current_device_busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
|
|
self._wifi_conversion_busy = True
|
|
self._sync_button_state()
|
|
self._page.deviceStatusLabel.setText("正在准备 USB 转 Wi-Fi…")
|
|
|
|
thread = QThread(self)
|
|
worker = AndroidWifiConversionWorker(
|
|
self._android_device_service,
|
|
usb_serial,
|
|
)
|
|
worker.moveToThread(thread)
|
|
|
|
thread.started.connect(worker.run)
|
|
worker.progressChanged.connect(self._on_wifi_conversion_progress)
|
|
worker.succeeded.connect(self._on_wifi_conversion_succeeded)
|
|
worker.failed.connect(self._on_wifi_conversion_failed)
|
|
worker.completed.connect(thread.quit)
|
|
worker.completed.connect(worker.deleteLater)
|
|
thread.finished.connect(self._on_wifi_conversion_finished)
|
|
thread.finished.connect(thread.deleteLater)
|
|
|
|
self._wifi_conversion_thread = thread
|
|
self._wifi_conversion_worker = worker
|
|
thread.start()
|
|
|
|
@pyqtSlot()
|
|
def _request_save(self) -> None:
|
|
if (
|
|
self._busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
|
|
serial = self._page.deviceTableModel.checked_serial
|
|
if not serial:
|
|
self._page.deviceStatusLabel.setText("保存失败:请先勾选一台已连接设备")
|
|
self._page.deviceTable.setFocus()
|
|
return
|
|
|
|
self.saveRequested.emit("", serial)
|
|
|
|
@pyqtSlot(str, str)
|
|
def _start_save_android_device(
|
|
self, _unused_device_name: str, serial: str
|
|
) -> None:
|
|
self._start_android_device_setting("save", serial)
|
|
|
|
@pyqtSlot()
|
|
def _request_delete(self) -> None:
|
|
if (
|
|
self._busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
|
|
serial = self._saved_android_serial
|
|
if not serial:
|
|
self._page.deviceStatusLabel.setText("删除失败:当前没有已保存设备")
|
|
return
|
|
self.deleteRequested.emit(serial)
|
|
|
|
@pyqtSlot(str)
|
|
def _start_delete_android_device(self, _serial: str) -> None:
|
|
self._start_android_device_setting("delete")
|
|
|
|
def _start_android_device_setting(
|
|
self, action: str, serial: str = ""
|
|
) -> None:
|
|
if (
|
|
self._closing
|
|
or self._busy
|
|
or self._search_busy
|
|
or self._wifi_conversion_busy
|
|
or self._android_setting_busy
|
|
):
|
|
return
|
|
|
|
self._android_setting_busy = True
|
|
self._sync_button_state()
|
|
message = (
|
|
"正在保存当前使用设备…"
|
|
if action == "save"
|
|
else "正在删除已保存设备配置…"
|
|
)
|
|
self._page.deviceStatusLabel.setText(message)
|
|
|
|
thread = QThread(self)
|
|
worker = AndroidDeviceSettingWorker(
|
|
self._selected_android_device_service,
|
|
action,
|
|
serial,
|
|
)
|
|
worker.moveToThread(thread)
|
|
|
|
thread.started.connect(worker.run)
|
|
worker.succeeded.connect(self._on_android_device_setting_succeeded)
|
|
worker.failed.connect(self._on_android_device_setting_failed)
|
|
worker.completed.connect(thread.quit)
|
|
worker.completed.connect(worker.deleteLater)
|
|
thread.finished.connect(self._on_android_device_setting_finished)
|
|
thread.finished.connect(thread.deleteLater)
|
|
|
|
self._android_setting_thread = thread
|
|
self._android_setting_worker = worker
|
|
thread.start()
|
|
|
|
@pyqtSlot()
|
|
def _sync_button_state(self, *_args) -> None:
|
|
serial = self._page.deviceTableModel.checked_serial
|
|
selected_device = self._page.deviceTableModel.device_for_serial(serial)
|
|
has_address = bool(self._page.addressInput.text().strip())
|
|
self._page.currentDeviceSaveButton.setEnabled(
|
|
not self._busy
|
|
and not self._current_device_busy
|
|
and not self._search_busy
|
|
and not self._wifi_conversion_busy
|
|
and not self._android_setting_busy
|
|
)
|
|
device_commands_enabled = (
|
|
not self._busy
|
|
and not self._search_busy
|
|
and not self._wifi_conversion_busy
|
|
and not self._android_setting_busy
|
|
)
|
|
self._page.searchButton.setEnabled(
|
|
device_commands_enabled and not self._current_device_busy
|
|
)
|
|
self._page.connectButton.setEnabled(
|
|
device_commands_enabled and has_address
|
|
)
|
|
self._page.saveButton.setEnabled(
|
|
device_commands_enabled and bool(serial)
|
|
)
|
|
self._page.convertWifiButton.setEnabled(
|
|
device_commands_enabled
|
|
and not self._current_device_busy
|
|
and selected_device is not None
|
|
and selected_device.status == "device"
|
|
and selected_device.connection_type == "USB"
|
|
)
|
|
self._page.deleteButton.setEnabled(
|
|
device_commands_enabled and bool(self._saved_android_serial)
|
|
)
|
|
|
|
def set_busy(self, busy: bool, message: str = "") -> None:
|
|
"""切换界面忙碌状态,防止用户重复提交设备命令。"""
|
|
|
|
self._busy = busy
|
|
self._sync_button_state()
|
|
if message:
|
|
self._page.deviceStatusLabel.setText(message)
|
|
|
|
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 _load_selected_android_device(self) -> None:
|
|
"""页面创建时恢复自动化应使用的 Android 设备号。"""
|
|
|
|
try:
|
|
serial = self._selected_android_device_service.load()
|
|
except Exception as exc:
|
|
self._page.deviceStatusLabel.setText(
|
|
f"读取已保存 Android 设备失败:{str(exc) or '数据库不可用'}"
|
|
)
|
|
return
|
|
|
|
self._saved_android_serial = serial
|
|
if serial:
|
|
self._page.deviceStatusLabel.setText(
|
|
f"当前使用设备:{serial}(等待搜索确认连接)"
|
|
)
|
|
|
|
def _selected_android_device(self) -> Optional[AndroidDeviceInfo]:
|
|
serial = self._page.deviceTableModel.checked_serial.strip()
|
|
return AndroidDeviceInfo(serial) if serial else None
|
|
|
|
@pyqtSlot(object)
|
|
def _on_search_succeeded(self, devices) -> None:
|
|
if self._closing:
|
|
return
|
|
|
|
rows = [
|
|
AndroidDeviceRow(
|
|
serial=device.serial,
|
|
connection_type=device.connection_type,
|
|
model=device.model,
|
|
android_version=device.android_version,
|
|
status=device.status,
|
|
)
|
|
for device in devices
|
|
]
|
|
self._page.set_android_devices(rows)
|
|
saved_device = self._page.deviceTableModel.device_for_serial(
|
|
self._saved_android_serial
|
|
)
|
|
if saved_device is not None and saved_device.status == "device":
|
|
self._page.deviceTableModel.set_checked_serial(
|
|
self._saved_android_serial
|
|
)
|
|
action = "恢复完成" if self._search_restore_serial else "搜索完成"
|
|
self._page.deviceStatusLabel.setText(
|
|
f"{action}:找到 {len(rows)} 台 Android 设备;"
|
|
"已自动选择当前使用设备"
|
|
)
|
|
elif self._saved_android_serial:
|
|
self._page.deviceStatusLabel.setText(
|
|
"已保存设备当前未连接:"
|
|
f"{self._saved_android_serial};配置已保留"
|
|
)
|
|
elif rows:
|
|
self._page.deviceStatusLabel.setText(
|
|
f"搜索完成:找到 {len(rows)} 台 Android 设备"
|
|
)
|
|
else:
|
|
self._page.deviceStatusLabel.setText(
|
|
"未找到 Android 设备,请检查 USB 调试或无线连接后重试"
|
|
)
|
|
|
|
@pyqtSlot(str)
|
|
def _on_search_failed(self, message: str) -> None:
|
|
if self._closing:
|
|
return
|
|
if self._search_restore_serial:
|
|
self._page.deviceStatusLabel.setText(
|
|
f"自动恢复失败:{message};已保存配置未更改,"
|
|
"可点击搜索重试"
|
|
)
|
|
else:
|
|
self._page.deviceStatusLabel.setText(
|
|
f"搜索失败:{message};设备列表和已保存配置未更改"
|
|
)
|
|
|
|
@pyqtSlot()
|
|
def _on_search_thread_finished(self) -> None:
|
|
self._search_worker = None
|
|
self._search_thread = None
|
|
self._search_busy = False
|
|
self._search_restore_serial = ""
|
|
if not self._closing:
|
|
self._sync_button_state()
|
|
|
|
@pyqtSlot(str)
|
|
def _on_wifi_conversion_progress(self, message: str) -> None:
|
|
if not self._closing:
|
|
self._page.deviceStatusLabel.setText(message)
|
|
|
|
@pyqtSlot(object)
|
|
def _on_wifi_conversion_succeeded(self, result) -> None:
|
|
if self._closing:
|
|
return
|
|
|
|
rows = [
|
|
AndroidDeviceRow(
|
|
serial=device.serial,
|
|
connection_type=device.connection_type,
|
|
model=device.model,
|
|
android_version=device.android_version,
|
|
status=device.status,
|
|
)
|
|
for device in result.devices
|
|
]
|
|
self._page.set_android_devices(rows)
|
|
self._page.deviceTableModel.set_checked_serial(result.wifi_serial)
|
|
self._page.deviceStatusLabel.setText(
|
|
f"Wi-Fi 设备 {result.wifi_serial} 已连接并勾选;"
|
|
"请确认后点击保存,仅在可信网络使用"
|
|
)
|
|
|
|
@pyqtSlot(str)
|
|
def _on_wifi_conversion_failed(self, message: str) -> None:
|
|
if self._closing:
|
|
return
|
|
self._page.deviceStatusLabel.setText(
|
|
f"转换失败:{message};设备列表和已保存配置未更改,可重试"
|
|
)
|
|
|
|
@pyqtSlot()
|
|
def _on_wifi_conversion_finished(self) -> None:
|
|
self._wifi_conversion_worker = None
|
|
self._wifi_conversion_thread = None
|
|
self._wifi_conversion_busy = False
|
|
if not self._closing:
|
|
self._sync_button_state()
|
|
|
|
@pyqtSlot(str, str)
|
|
def _on_android_device_setting_succeeded(
|
|
self, action: str, serial: str
|
|
) -> None:
|
|
if self._closing:
|
|
return
|
|
|
|
if action == "save":
|
|
self._saved_android_serial = serial
|
|
self._page.deviceStatusLabel.setText(
|
|
f"当前使用设备:{serial}(已保存)"
|
|
)
|
|
else:
|
|
self._saved_android_serial = ""
|
|
self._page.deviceTableModel.set_checked_serial("")
|
|
self._page.deviceStatusLabel.setText(
|
|
"当前使用设备:未选择(本地配置已删除)"
|
|
)
|
|
|
|
@pyqtSlot(str, str)
|
|
def _on_android_device_setting_failed(
|
|
self, action: str, message: str
|
|
) -> None:
|
|
if self._closing:
|
|
return
|
|
action_text = "保存" if action == "save" else "删除"
|
|
self._page.deviceStatusLabel.setText(
|
|
f"{action_text}失败:{message};原设备配置未更改,可重试"
|
|
)
|
|
|
|
@pyqtSlot()
|
|
def _on_android_device_setting_finished(self) -> None:
|
|
self._android_setting_worker = None
|
|
self._android_setting_thread = None
|
|
self._android_setting_busy = False
|
|
if not self._closing:
|
|
self._sync_button_state()
|
|
|
|
@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)
|
|
|
|
search_worker = self._search_worker
|
|
search_thread = self._search_thread
|
|
if search_worker is not None:
|
|
search_worker.cancel()
|
|
for signal, slot in (
|
|
(search_worker.succeeded, self._on_search_succeeded),
|
|
(search_worker.failed, self._on_search_failed),
|
|
):
|
|
try:
|
|
signal.disconnect(slot)
|
|
except TypeError:
|
|
pass
|
|
|
|
if search_thread is not None and search_thread.isRunning():
|
|
search_thread.quit()
|
|
search_thread.wait(6000)
|
|
|
|
conversion_worker = self._wifi_conversion_worker
|
|
conversion_thread = self._wifi_conversion_thread
|
|
if conversion_worker is not None:
|
|
conversion_worker.cancel()
|
|
for signal, slot in (
|
|
(
|
|
conversion_worker.progressChanged,
|
|
self._on_wifi_conversion_progress,
|
|
),
|
|
(
|
|
conversion_worker.succeeded,
|
|
self._on_wifi_conversion_succeeded,
|
|
),
|
|
(
|
|
conversion_worker.failed,
|
|
self._on_wifi_conversion_failed,
|
|
),
|
|
):
|
|
try:
|
|
signal.disconnect(slot)
|
|
except TypeError:
|
|
pass
|
|
|
|
if conversion_thread is not None and conversion_thread.isRunning():
|
|
conversion_thread.quit()
|
|
conversion_thread.wait(6000)
|
|
|
|
setting_worker = self._android_setting_worker
|
|
setting_thread = self._android_setting_thread
|
|
if setting_worker is not None:
|
|
setting_worker.cancel()
|
|
for signal, slot in (
|
|
(
|
|
setting_worker.succeeded,
|
|
self._on_android_device_setting_succeeded,
|
|
),
|
|
(
|
|
setting_worker.failed,
|
|
self._on_android_device_setting_failed,
|
|
),
|
|
):
|
|
try:
|
|
signal.disconnect(slot)
|
|
except TypeError:
|
|
pass
|
|
|
|
if setting_thread is not None and setting_thread.isRunning():
|
|
setting_thread.quit()
|
|
setting_thread.wait(4000)
|