mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-17 23:13:03 +08:00
5e898180c7
- GUI 模块统一 QtCore → QtGui → QtWidgets 导入排列,各类独占一行按字母排序 - 统一类间两空行、类内方法间一空行、函数间一空行的间距规范 - 统一方法排列顺序:__init__ → setupUi → connectSignals → public → Slot → private - 统一 _widgets 中 ConditionRowFrame/ActionStepFrame 方法命名(populate* / toScript / updateValueWidget) - LibTimeSelector 迁入 operators/abs 抽象层 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
574 lines
19 KiB
Python
574 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Copyright (c) 2026 KenanZhu.
|
|
All rights reserved.
|
|
|
|
This software is provided "as is", without any warranty of any kind.
|
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
|
See the LICENSE file for details.
|
|
"""
|
|
import threading
|
|
from typing import Optional
|
|
|
|
from PySide6.QtCore import (
|
|
Qt,
|
|
Slot,
|
|
QThread,
|
|
Signal
|
|
)
|
|
from PySide6.QtWidgets import (
|
|
QDialog,
|
|
QLabel,
|
|
QComboBox,
|
|
QProgressBar,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QMessageBox,
|
|
QFrame,
|
|
QLineEdit
|
|
)
|
|
from PySide6.QtGui import QCloseEvent
|
|
|
|
from managers.driver.WebDriverManager import (
|
|
instance as webdriverManagerInstance,
|
|
WebDriverManager,
|
|
WebDriverInfo,
|
|
WebDriverType,
|
|
WebDriverStatus
|
|
)
|
|
from gui.ALStatusLabel import ALStatusLabel
|
|
|
|
|
|
class DownloadWorker(QThread):
|
|
"""
|
|
Worker thread for downloading web drivers.
|
|
"""
|
|
|
|
progress = Signal(float, int, float, str)
|
|
downloadFinished = Signal(object, str)
|
|
downloadError = Signal(str)
|
|
downloadCancelled = Signal()
|
|
|
|
def __init__(
|
|
self,
|
|
driver_manager: WebDriverManager,
|
|
driver_info: WebDriverInfo
|
|
):
|
|
super().__init__()
|
|
self.__driver_manager = driver_manager
|
|
self.__driver_info = driver_info
|
|
self.__driver_path = None
|
|
self.__cancelled = False
|
|
self.__cancel_event = threading.Event()
|
|
|
|
def cancel(
|
|
self
|
|
):
|
|
"""
|
|
Cancel the download operation.
|
|
"""
|
|
|
|
self.__cancelled = True
|
|
self.__cancel_event.set()
|
|
|
|
def run(
|
|
self
|
|
):
|
|
try:
|
|
if self.__cancelled:
|
|
self.downloadCancelled.emit()
|
|
return
|
|
self.__driver_path = self.__driver_manager.installDriver(
|
|
self.__driver_info,
|
|
progress_callback=self.onProgress,
|
|
cancel_event=self.__cancel_event
|
|
)
|
|
if self.__cancelled:
|
|
self.downloadCancelled.emit()
|
|
return
|
|
if self.__driver_path:
|
|
self.downloadFinished.emit(self.__driver_path, "")
|
|
else:
|
|
self.downloadError.emit("下载失败: 未返回有效路径")
|
|
except Exception as e:
|
|
if not self.__cancelled:
|
|
self.downloadError.emit(f"下载失败: {str(e)}")
|
|
|
|
def onProgress(
|
|
self,
|
|
downloaded: float,
|
|
total: int,
|
|
speed: float,
|
|
message: str
|
|
):
|
|
|
|
if self.__cancel_event.is_set():
|
|
self.__cancelled = True
|
|
if not self.__cancelled:
|
|
self.progress.emit(downloaded, total, speed, message)
|
|
|
|
def stop(
|
|
self
|
|
):
|
|
"""
|
|
Cancel and wait for the thread to finish.
|
|
Must only be called from the main thread.
|
|
"""
|
|
|
|
self.cancel()
|
|
if not self.isFinished():
|
|
if not self.wait(5000):
|
|
self.terminate()
|
|
self.wait()
|
|
|
|
|
|
class ALWebDriverDownloadDialog(QDialog):
|
|
|
|
def __init__(
|
|
self,
|
|
parent: Optional[QDialog] = None,
|
|
driver_dir: str = ""
|
|
):
|
|
"""
|
|
Web driver download dialog.
|
|
|
|
Args:
|
|
parent: Parent widget.
|
|
driver_dir: Driver directory path.
|
|
"""
|
|
|
|
super().__init__(parent)
|
|
|
|
self.__driver_dir = driver_dir
|
|
self.__driver_manager: Optional[WebDriverManager] = None
|
|
self.__confirmed = False
|
|
self.__selected_driver_info: Optional[WebDriverInfo] = None
|
|
self.__driver_infos: list[WebDriverInfo] = []
|
|
self.__download_thread: Optional[DownloadWorker] = None
|
|
|
|
self.setupUi()
|
|
self.connectSignals()
|
|
self.initializeDriverManager()
|
|
self.refreshDriverList()
|
|
|
|
def showEvent(
|
|
self,
|
|
event
|
|
):
|
|
|
|
result = super().showEvent(event)
|
|
if self.parent():
|
|
screen_rect = self.screen().geometry()
|
|
target_pos = self.parent().geometry().center()
|
|
target_pos.setX(target_pos.x() - self.width()//2)
|
|
target_pos.setY(target_pos.y() - self.height()//2)
|
|
if target_pos.x() < 0:
|
|
target_pos.setX(0)
|
|
if target_pos.x() + self.width() > screen_rect.width():
|
|
target_pos.setX(screen_rect.width() - self.width())
|
|
if target_pos.y() < 0:
|
|
target_pos.setY(0)
|
|
if target_pos.y() + self.height() > screen_rect.height():
|
|
target_pos.setY(screen_rect.height() - self.height())
|
|
self.move(target_pos)
|
|
return result
|
|
|
|
def setupUi(
|
|
self
|
|
):
|
|
|
|
self.setModal(True)
|
|
self.setMaximumHeight(240)
|
|
self.setMinimumHeight(240)
|
|
self.setWindowTitle("浏览器驱动下载 - AutoLibrary")
|
|
|
|
self.MainLayout = QVBoxLayout(self)
|
|
self.MainLayout.setContentsMargins(5, 5, 5, 5)
|
|
self.MainLayout.setSpacing(5)
|
|
|
|
self.BrowserCountLabel = QLabel("检测到 0 个可用浏览器:")
|
|
self.MainLayout.addWidget(self.BrowserCountLabel)
|
|
|
|
self.DriverInfoLayout = QHBoxLayout()
|
|
self.DriverInfoLayout.setSpacing(5)
|
|
self.DriverComboBox = QComboBox()
|
|
self.DriverInfoLayout.addWidget(self.DriverComboBox)
|
|
self.StatusLabel = ALStatusLabel()
|
|
self.StatusLabel.setFixedSize(32, 32)
|
|
self.DriverInfoLayout.addWidget(self.StatusLabel)
|
|
self.MainLayout.addLayout(self.DriverInfoLayout)
|
|
|
|
self.DetailLayout = QVBoxLayout()
|
|
self.DetailLayout.setSpacing(5)
|
|
self.DetailLayout.setContentsMargins(5, 5, 5, 5)
|
|
self.BrowserTypeLabel = QLabel("类型:")
|
|
self.DetailLayout.addWidget(self.BrowserTypeLabel)
|
|
self.VersionLabel = QLabel("版本:")
|
|
self.DetailLayout.addWidget(self.VersionLabel)
|
|
self.PathLabel = QLineEdit()
|
|
self.PathLabel.setReadOnly(True)
|
|
self.PathLabel.setText("路径:未安装")
|
|
self.DetailLayout.addWidget(self.PathLabel)
|
|
self.MainLayout.addLayout(self.DetailLayout)
|
|
|
|
self.Line = QFrame()
|
|
self.Line.setFrameShape(QFrame.Shape.HLine)
|
|
self.Line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
self.MainLayout.addWidget(self.Line)
|
|
self.ProgressBar = QProgressBar()
|
|
self.ProgressBar.setValue(0)
|
|
self.ProgressBar.setTextVisible(False)
|
|
self.MainLayout.addWidget(self.ProgressBar)
|
|
self.ProgressText = QLabel("")
|
|
self.ProgressText.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
self.MainLayout.addWidget(self.ProgressText)
|
|
self.ControlLayout = QHBoxLayout()
|
|
self.ControlLayout.setSpacing(8)
|
|
self.ControlLayout.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
self.RefreshButton = QPushButton("刷新")
|
|
self.RefreshButton.setFixedSize(80, 25)
|
|
self.DownloadButton = QPushButton("下载驱动")
|
|
self.DownloadButton.setFixedSize(80, 25)
|
|
self.DeleteButton = QPushButton("删除驱动")
|
|
self.DeleteButton.setFixedSize(80, 25)
|
|
self.CancelButton = QPushButton("取消")
|
|
self.CancelButton.setFixedSize(80, 25)
|
|
self.ConfirmButton = QPushButton("确认")
|
|
self.ConfirmButton.setFixedSize(80, 25)
|
|
self.ConfirmButton.setEnabled(False)
|
|
|
|
self.ControlLayout.addWidget(self.RefreshButton)
|
|
self.ControlLayout.addWidget(self.DownloadButton)
|
|
self.ControlLayout.addWidget(self.DeleteButton)
|
|
self.ControlLayout.addWidget(self.CancelButton)
|
|
self.ControlLayout.addWidget(self.ConfirmButton)
|
|
self.MainLayout.addLayout(self.ControlLayout)
|
|
|
|
def connectSignals(
|
|
self
|
|
):
|
|
|
|
self.RefreshButton.clicked.connect(self.onRefreshButtonClicked)
|
|
self.DownloadButton.clicked.connect(self.onDownloadButtonClicked)
|
|
self.DeleteButton.clicked.connect(self.onDeleteButtonClicked)
|
|
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
|
|
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
|
|
self.DriverComboBox.currentIndexChanged.connect(self.onDriverComboBoxChanged)
|
|
|
|
def initializeDriverManager(
|
|
self
|
|
):
|
|
|
|
try:
|
|
self.__driver_manager = webdriverManagerInstance(self.__driver_dir)
|
|
except ValueError as e:
|
|
QMessageBox.warning(self, "初始化失败", f"WebDriverManager 初始化失败:\n{str(e)}")
|
|
self.reject()
|
|
|
|
def refreshDriverList(
|
|
self
|
|
):
|
|
|
|
if not self.__driver_manager:
|
|
return
|
|
self.__driver_manager.refresh()
|
|
self.__driver_infos = self.__driver_manager.getDriverInfos()
|
|
self.DriverComboBox.clear()
|
|
installed = 0
|
|
installed_idx = 0
|
|
for i, driver_info in enumerate(self.__driver_infos):
|
|
display_text = f"{driver_info.driver_type.value} - {driver_info.browser_version}"
|
|
if driver_info.driver_status == WebDriverStatus.INSTALLED:
|
|
installed += 1
|
|
installed_idx = i # get the installed driver index
|
|
display_text += " : 已安装"
|
|
self.DriverComboBox.addItem(display_text)
|
|
count = len(self.__driver_infos)
|
|
self.BrowserCountLabel.setText(f"检测到 {count} 个可用浏览器,{installed} 个已安装驱动:")
|
|
if self.__driver_infos:
|
|
self.DriverComboBox.setCurrentIndex(installed_idx)
|
|
|
|
def onDriverComboBoxChanged(
|
|
self,
|
|
index: int
|
|
):
|
|
|
|
if not self.__driver_infos or index < 0 or index >= len(self.__driver_infos):
|
|
return
|
|
driver_info = self.__driver_infos[index]
|
|
self.updateDriverInfoDisplay(driver_info)
|
|
self.updateProgressBarStates(driver_info)
|
|
self.updateButtonStates(driver_info)
|
|
|
|
def closeEvent(
|
|
self,
|
|
event: QCloseEvent
|
|
):
|
|
|
|
if self.__download_thread and self.__download_thread.isRunning():
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"确认关闭 - AutoLibrary",
|
|
"驱动正在下载中, 确定要取消并关闭对话框吗 ?",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
if reply == QMessageBox.StandardButton.No:
|
|
event.ignore()
|
|
return
|
|
self.__download_thread.stop()
|
|
if not self.__confirmed:
|
|
self.__selected_driver_info = None
|
|
event.accept()
|
|
super().closeEvent(event)
|
|
|
|
def onThreadFinished(
|
|
self
|
|
):
|
|
|
|
if self.__download_thread:
|
|
self.__download_thread.deleteLater()
|
|
self.__download_thread = None
|
|
|
|
def getSelectedDriverInfo(
|
|
self
|
|
) -> Optional[WebDriverInfo]:
|
|
|
|
return self.__selected_driver_info
|
|
|
|
def updateDriverInfoDisplay(
|
|
self,
|
|
driver_info: WebDriverInfo
|
|
):
|
|
|
|
if driver_info.driver_type == WebDriverType.CHROME:
|
|
driver_type = "Google Chrome"
|
|
elif driver_info.driver_type == WebDriverType.FIREFOX:
|
|
driver_type = "Mozilla Firefox"
|
|
elif driver_info.driver_type == WebDriverType.EDGE:
|
|
driver_type = "Microsoft Edge"
|
|
else:
|
|
driver_type = "未知"
|
|
self.BrowserTypeLabel.setText(f"类型:{driver_type}")
|
|
self.VersionLabel.setText(f"版本:{driver_info.driver_version}")
|
|
if driver_info.driver_path:
|
|
self.PathLabel.setText(str(driver_info.driver_path))
|
|
else:
|
|
self.PathLabel.setText("未安装")
|
|
match driver_info.driver_status:
|
|
case WebDriverStatus.NOT_INSTALLED:
|
|
self.StatusLabel.status = ALStatusLabel.Status.WAITING
|
|
case WebDriverStatus.INSTALLED:
|
|
self.StatusLabel.status = ALStatusLabel.Status.SUCCESS
|
|
case WebDriverStatus.DOWNLOADING:
|
|
self.StatusLabel.status = ALStatusLabel.Status.RUNNING
|
|
case WebDriverStatus.ERROR:
|
|
self.StatusLabel.status = ALStatusLabel.Status.FAILURE
|
|
|
|
def updateProgressBarStates(
|
|
self,
|
|
driver_info: WebDriverInfo
|
|
):
|
|
|
|
if driver_info.driver_status == WebDriverStatus.NOT_INSTALLED:
|
|
self.ProgressBar.setValue(0)
|
|
self.ProgressText.setText("未安装")
|
|
elif driver_info.driver_status == WebDriverStatus.INSTALLED:
|
|
self.ProgressBar.setValue(100)
|
|
self.ProgressText.setText("已安装")
|
|
elif driver_info.driver_status == WebDriverStatus.DOWNLOADING:
|
|
pass # update by worker thread
|
|
elif driver_info.driver_status == WebDriverStatus.ERROR:
|
|
self.ProgressBar.setValue(0)
|
|
self.ProgressText.setText("下载失败")
|
|
|
|
def updateButtonStates(
|
|
self,
|
|
driver_info: WebDriverInfo
|
|
):
|
|
|
|
if driver_info.driver_status == WebDriverStatus.NOT_INSTALLED:
|
|
self.RefreshButton.setEnabled(True)
|
|
self.DeleteButton.setEnabled(False)
|
|
self.DownloadButton.setEnabled(True)
|
|
self.CancelButton.setEnabled(True)
|
|
self.ConfirmButton.setEnabled(False)
|
|
elif driver_info.driver_status == WebDriverStatus.INSTALLED:
|
|
self.RefreshButton.setEnabled(True)
|
|
self.DownloadButton.setEnabled(False)
|
|
self.DeleteButton.setEnabled(True)
|
|
self.CancelButton.setEnabled(True)
|
|
self.ConfirmButton.setEnabled(True)
|
|
elif driver_info.driver_status == WebDriverStatus.DOWNLOADING:
|
|
self.RefreshButton.setEnabled(False)
|
|
self.DownloadButton.setEnabled(False)
|
|
self.DeleteButton.setEnabled(False)
|
|
self.CancelButton.setEnabled(True)
|
|
self.ConfirmButton.setEnabled(False)
|
|
elif driver_info.driver_status == WebDriverStatus.ERROR:
|
|
self.RefreshButton.setEnabled(True)
|
|
self.DownloadButton.setEnabled(True)
|
|
self.DeleteButton.setEnabled(False)
|
|
self.CancelButton.setEnabled(True)
|
|
self.ConfirmButton.setEnabled(False)
|
|
|
|
@Slot()
|
|
def onRefreshButtonClicked(
|
|
self
|
|
):
|
|
|
|
self.refreshDriverList()
|
|
|
|
@Slot()
|
|
def onDeleteButtonClicked(
|
|
self
|
|
):
|
|
|
|
index = self.DriverComboBox.currentIndex()
|
|
if index < 0 or index >= len(self.__driver_infos):
|
|
return
|
|
driver_info = self.__driver_infos[index]
|
|
if driver_info.driver_status.name != "INSTALLED":
|
|
QMessageBox.information(self, "提示 - AutoLibrary", "该驱动未安装, 无需删除")
|
|
return
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"确认删除 - AutoLibrary",
|
|
f"确定要删除 {driver_info.driver_type.value} 驱动吗 ?",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
if reply == QMessageBox.StandardButton.No:
|
|
return
|
|
try:
|
|
self.__driver_manager.uninstallDriver(driver_info)
|
|
self.refreshDriverList()
|
|
QMessageBox.information(self, "删除成功 - AutoLibrary", "驱动已成功删除")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "删除失败 - AutoLibrary", f"删除驱动时出错:\n{str(e)}")
|
|
|
|
@Slot()
|
|
def onDownloadButtonClicked(
|
|
self
|
|
):
|
|
|
|
self.DriverComboBox.setEnabled(False)
|
|
index = self.DriverComboBox.currentIndex()
|
|
if index < 0 or index >= len(self.__driver_infos):
|
|
return
|
|
driver_info = self.__driver_infos[index]
|
|
if driver_info.driver_status == WebDriverStatus.INSTALLED:
|
|
return
|
|
driver_info.driver_status = WebDriverStatus.DOWNLOADING # we set this only to update
|
|
# the display, and we will set to not installed in the download thread
|
|
self.updateDriverInfoDisplay(driver_info)
|
|
self.updateProgressBarStates(driver_info)
|
|
self.ProgressText.setText("准备开始下载...")
|
|
self.updateButtonStates(driver_info)
|
|
# set to not installed
|
|
driver_info.driver_status = WebDriverStatus.NOT_INSTALLED
|
|
self.__download_thread = DownloadWorker(self.__driver_manager, driver_info)
|
|
self.__download_thread.progress.connect(self.onDownloadProgress)
|
|
self.__download_thread.downloadFinished.connect(self.onDownloadFinished)
|
|
self.__download_thread.downloadError.connect(self.onDownloadError)
|
|
self.__download_thread.downloadCancelled.connect(self.onDownloadCancelled)
|
|
self.__download_thread.finished.connect(self.onThreadFinished)
|
|
self.__download_thread.start()
|
|
|
|
@Slot()
|
|
def onDownloadProgress(
|
|
self,
|
|
downloaded: float,
|
|
total: int,
|
|
speed: float,
|
|
message: str
|
|
):
|
|
|
|
progress = downloaded
|
|
self.ProgressBar.setValue(progress)
|
|
if speed >= 1024:
|
|
speed_text = f"{speed/1024:.1f} MB/s"
|
|
else:
|
|
speed_text = f"{speed:.1f} KB/s"
|
|
progress_text = f"{message}... {downloaded:.1f}% - {speed_text}"
|
|
self.ProgressText.setText(progress_text)
|
|
|
|
@Slot()
|
|
def onDownloadFinished(
|
|
self
|
|
):
|
|
|
|
self.DriverComboBox.setEnabled(True)
|
|
index = self.DriverComboBox.currentIndex()
|
|
if 0 <= index < len(self.__driver_infos):
|
|
driver_info = self.__driver_infos[index]
|
|
driver_info.driver_status = WebDriverStatus.INSTALLED
|
|
self.updateDriverInfoDisplay(driver_info)
|
|
self.updateProgressBarStates(driver_info)
|
|
self.updateButtonStates(driver_info)
|
|
|
|
@Slot()
|
|
def onDownloadError(
|
|
self,
|
|
error_message: str
|
|
):
|
|
|
|
self.DriverComboBox.setEnabled(True)
|
|
index = self.DriverComboBox.currentIndex()
|
|
if 0 <= index < len(self.__driver_infos):
|
|
driver_info = self.__driver_infos[index]
|
|
driver_info.driver_status = WebDriverStatus.ERROR
|
|
self.updateDriverInfoDisplay(driver_info)
|
|
self.updateProgressBarStates(driver_info)
|
|
self.updateButtonStates(driver_info)
|
|
QMessageBox.critical(self, "下载失败 - AutoLibrary", error_message)
|
|
|
|
@Slot()
|
|
def onDownloadCancelled(
|
|
self
|
|
):
|
|
|
|
self.DriverComboBox.setEnabled(True)
|
|
index = self.DriverComboBox.currentIndex()
|
|
if 0 <= index < len(self.__driver_infos):
|
|
driver_info = self.__driver_infos[index]
|
|
self.__driver_manager.cancelDriverDownload(driver_info)
|
|
driver_info.driver_status = WebDriverStatus.NOT_INSTALLED
|
|
self.updateDriverInfoDisplay(driver_info)
|
|
self.updateProgressBarStates(driver_info)
|
|
self.updateButtonStates(driver_info)
|
|
self.ProgressText.setText("下载已取消")
|
|
|
|
@Slot()
|
|
def onConfirmButtonClicked(
|
|
self
|
|
):
|
|
|
|
index = self.DriverComboBox.currentIndex()
|
|
if index < 0 or index >= len(self.__driver_infos):
|
|
return
|
|
driver_info = self.__driver_infos[index]
|
|
if driver_info.driver_status != WebDriverStatus.INSTALLED:
|
|
return
|
|
self.__selected_driver_info = driver_info
|
|
self.__confirmed = True
|
|
self.accept()
|
|
|
|
@Slot()
|
|
def onCancelButtonClicked(
|
|
self
|
|
):
|
|
|
|
if self.__download_thread:
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"确认取消 - AutoLibrary",
|
|
"正在下载中, 确定要取消下载吗 ?",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
self.__download_thread.cancel()
|
|
else:
|
|
self.__confirmed = False
|
|
self.__selected_driver_info = None
|
|
self.reject()
|