mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 07:23:03 +08:00
feat(WebDriverManager): 支持下载取消操作并完善异常处理
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
|
import threading
|
||||||
import requests
|
import requests
|
||||||
import zipfile
|
import zipfile
|
||||||
import tarfile
|
import tarfile
|
||||||
@@ -243,29 +244,33 @@ class WebDriverDownloader:
|
|||||||
|
|
||||||
def download(
|
def download(
|
||||||
self,
|
self,
|
||||||
progress_callback: Optional[Callable[[int, int, float, str], None]] = None
|
progress_callback: Optional[Callable[[float, int, float, str], None]] = None,
|
||||||
|
cancel_event: Optional[threading.Event] = None
|
||||||
) -> Optional[Path]:
|
) -> Optional[Path]:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# downlaod file : 0% - 98%
|
# downlaod file : 0% - 98%
|
||||||
if not self._download(progress_callback):
|
if not self._download(progress_callback, cancel_event=cancel_event):
|
||||||
return None
|
return None
|
||||||
# verify file : 98% - 99%
|
# verify file : 98% - 99%
|
||||||
if not self._verify(progress_callback):
|
if not self._verify(progress_callback):
|
||||||
|
progress_callback(0, 100, 0.0, "验证失败")
|
||||||
return None
|
return None
|
||||||
# extract file : 99% - 100%
|
# extract file : 99% - 100%
|
||||||
driver_path = self._extract(progress_callback)
|
driver_path = self._extract(progress_callback)
|
||||||
if not driver_path:
|
if not driver_path:
|
||||||
|
progress_callback(0, 100, 0.0, "解压失败")
|
||||||
return None
|
return None
|
||||||
return driver_path
|
return driver_path
|
||||||
except Exception:
|
except Exception as e:
|
||||||
return None
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def _download(
|
def _download(
|
||||||
self,
|
self,
|
||||||
progress_callback: Optional[Callable[[int, int, float, str], None]] = None,
|
progress_callback: Optional[Callable[[float, int, float, str], None]] = None,
|
||||||
max_retries: int = 3
|
max_retries: int = 3,
|
||||||
|
cancel_event: Optional[threading.Event] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
CHUNK_SIZE = 8192*8 # 64KB chunk
|
CHUNK_SIZE = 8192*8 # 64KB chunk
|
||||||
@@ -276,6 +281,8 @@ class WebDriverDownloader:
|
|||||||
|
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
|
if cancel_event and cancel_event.is_set():
|
||||||
|
return False
|
||||||
# resume download if file exists
|
# resume download if file exists
|
||||||
if self.download_path.exists():
|
if self.download_path.exists():
|
||||||
downloaded_size = self.download_path.stat().st_size
|
downloaded_size = self.download_path.stat().st_size
|
||||||
@@ -287,7 +294,7 @@ class WebDriverDownloader:
|
|||||||
headers_ = headers
|
headers_ = headers
|
||||||
mode = 'wb'
|
mode = 'wb'
|
||||||
# get response
|
# get response
|
||||||
response = requests.get(str(self.download_url), headers=headers_, stream=True, timeout=120)
|
response = requests.get(str(self.download_url), headers=headers_, stream=True, timeout=10)
|
||||||
if response.status_code not in [200, 206]:
|
if response.status_code not in [200, 206]:
|
||||||
if self.download_path.exists():
|
if self.download_path.exists():
|
||||||
self.download_path.unlink()
|
self.download_path.unlink()
|
||||||
@@ -306,6 +313,9 @@ class WebDriverDownloader:
|
|||||||
last_progress = 0.0
|
last_progress = 0.0
|
||||||
with open(self.download_path, mode) as f:
|
with open(self.download_path, mode) as f:
|
||||||
for chunk in response.iter_content(CHUNK_SIZE):
|
for chunk in response.iter_content(CHUNK_SIZE):
|
||||||
|
if cancel_event and cancel_event.is_set():
|
||||||
|
response.close()
|
||||||
|
return False
|
||||||
if not chunk:
|
if not chunk:
|
||||||
continue
|
continue
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
@@ -328,8 +338,10 @@ class WebDriverDownloader:
|
|||||||
raise Exception(f"下载不完整 : {self.download_path.stat().st_size}/{total_size} 字节")
|
raise Exception(f"下载不完整 : {self.download_path.stat().st_size}/{total_size} 字节")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if cancel_event and cancel_event.is_set():
|
||||||
|
return False
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
progress_callback(0, 100, 0.0, "准备重试...")
|
progress_callback(0, 100, 0.0, f"第 {attempt+1} 次重试...")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
raise e
|
raise e
|
||||||
@@ -337,7 +349,7 @@ class WebDriverDownloader:
|
|||||||
|
|
||||||
def _verify(
|
def _verify(
|
||||||
self,
|
self,
|
||||||
progress_callback: Optional[Callable[[int, int, float, str], None]] = None
|
progress_callback: Optional[Callable[[float, int, float, str], None]] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
progress_callback(98, 100, 0.0, "验证完成")
|
progress_callback(98, 100, 0.0, "验证完成")
|
||||||
@@ -346,7 +358,7 @@ class WebDriverDownloader:
|
|||||||
|
|
||||||
def _extract(
|
def _extract(
|
||||||
self,
|
self,
|
||||||
progress_callback: Optional[Callable[[int, int, float, str], None]] = None
|
progress_callback: Optional[Callable[[float, int, float, str], None]] = None
|
||||||
) -> Optional[Path]:
|
) -> Optional[Path]:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -40,21 +40,22 @@ class WebDriverInfo:
|
|||||||
Web driver information.
|
Web driver information.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
browser_info (WebBrowserInfo): Web browser information
|
|
||||||
driver_type (WebDriverType): Web driver type
|
driver_type (WebDriverType): Web driver type
|
||||||
|
driver_arch (WebDriverArch): Web driver architecture
|
||||||
driver_version (str): Web driver version
|
driver_version (str): Web driver version
|
||||||
|
browser_version (str): Web browser version
|
||||||
driver_path (Optional[Path]): Web driver executable file path
|
driver_path (Optional[Path]): Web driver executable file path
|
||||||
driver_status (DriverStatus): Web driver status
|
driver_status (DriverStatus): Web driver status
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self
|
||||||
browser_info: WebBrowserInfo
|
|
||||||
):
|
):
|
||||||
|
|
||||||
self.browser_info = browser_info
|
self.driver_type = None
|
||||||
self.driver_type = WebDriverType(browser_info.browser_type.value)
|
self.driver_arch = None
|
||||||
self.driver_version = ""
|
self.driver_version = ""
|
||||||
|
self.browser_version = ""
|
||||||
self.driver_path: Optional[Path] = None
|
self.driver_path: Optional[Path] = None
|
||||||
self.driver_status = DriverStatus.NOT_INSTALLED
|
self.driver_status = DriverStatus.NOT_INSTALLED
|
||||||
|
|
||||||
@@ -99,7 +100,10 @@ class WebDriverManager:
|
|||||||
|
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
browser_infos = self.__browser_detector.detect()
|
browser_infos = self.__browser_detector.detect()
|
||||||
self.__driver_infos = [WebDriverInfo(info) for info in browser_infos]
|
self.__driver_infos = [
|
||||||
|
self._getDriverInfo(info)
|
||||||
|
for info in browser_infos
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _checkDriverStatus(
|
def _checkDriverStatus(
|
||||||
@@ -108,27 +112,28 @@ class WebDriverManager:
|
|||||||
|
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
for driver_info in self.__driver_infos:
|
for driver_info in self.__driver_infos:
|
||||||
driver_arch = self._mapWebBrowserArch(
|
driver_path = self._getDriverPath(driver_info)
|
||||||
driver_info.browser_info.browser_type,
|
|
||||||
driver_info.browser_info.browser_arch
|
|
||||||
)
|
|
||||||
driver_path = self._getDriverPath(
|
|
||||||
driver_info.driver_type,
|
|
||||||
driver_arch
|
|
||||||
)
|
|
||||||
if driver_path and driver_path.exists() and driver_path.is_file():
|
if driver_path and driver_path.exists() and driver_path.is_file():
|
||||||
driver_info.driver_path = driver_path
|
driver_info.driver_path = driver_path
|
||||||
driver_info.driver_status = DriverStatus.INSTALLED
|
driver_info.driver_status = DriverStatus.INSTALLED
|
||||||
try:
|
|
||||||
driver_info.driver_version = self._getDriverVersion(
|
|
||||||
driver_info.driver_type,
|
|
||||||
driver_info.driver_info.browser_version
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
driver_info.driver_status = DriverStatus.ERROR
|
|
||||||
|
|
||||||
|
|
||||||
def _mapWebBrowserArch(
|
def _mapWebBrowserTypeToDriver(
|
||||||
|
self,
|
||||||
|
browser_type: WebBrowserType
|
||||||
|
) -> WebDriverType:
|
||||||
|
|
||||||
|
if browser_type == WebBrowserType.CHROME:
|
||||||
|
return WebDriverType.CHROME
|
||||||
|
elif browser_type == WebBrowserType.FIREFOX:
|
||||||
|
return WebDriverType.FIREFOX
|
||||||
|
elif browser_type == WebBrowserType.EDGE:
|
||||||
|
return WebDriverType.EDGE
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的 Web 浏览器类型 : {browser_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def _mapWebBrowserArchToDriver(
|
||||||
self,
|
self,
|
||||||
browser_type: WebBrowserType,
|
browser_type: WebBrowserType,
|
||||||
browser_arch: WebBrowserArch
|
browser_arch: WebBrowserArch
|
||||||
@@ -236,12 +241,30 @@ class WebDriverManager:
|
|||||||
raise ValueError(f"无效的 Firefox 版本格式 : {version}") from e
|
raise ValueError(f"无效的 Firefox 版本格式 : {version}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def _getDriverInfo(
|
||||||
|
self,
|
||||||
|
browser_info: WebBrowserInfo
|
||||||
|
) -> WebDriverInfo:
|
||||||
|
|
||||||
|
driver_info = WebDriverInfo()
|
||||||
|
driver_info.driver_type = self._mapWebBrowserTypeToDriver(browser_info.browser_type)
|
||||||
|
driver_info.driver_arch = self._mapWebBrowserArchToDriver(browser_info.browser_type, browser_info.browser_arch)
|
||||||
|
if browser_info.browser_type == WebBrowserType.FIREFOX:
|
||||||
|
driver_info.driver_version = self._mapFirefoxDriverVersion(browser_info.browser_version)
|
||||||
|
else:
|
||||||
|
driver_info.driver_version = browser_info.browser_version
|
||||||
|
driver_info.browser_version = browser_info.browser_version
|
||||||
|
return driver_info
|
||||||
|
|
||||||
|
|
||||||
def _getDriverPath(
|
def _getDriverPath(
|
||||||
self,
|
self,
|
||||||
driver_type: WebDriverType,
|
driver_info: WebDriverInfo
|
||||||
driver_arch: WebDriverArch
|
|
||||||
) -> Optional[Path]:
|
) -> Optional[Path]:
|
||||||
|
|
||||||
|
driver_type = driver_info.driver_type
|
||||||
|
driver_arch = driver_info.driver_arch
|
||||||
|
driver_version = driver_info.driver_version
|
||||||
if driver_type == WebDriverType.CHROME:
|
if driver_type == WebDriverType.CHROME:
|
||||||
driver_name = "chromedriver"
|
driver_name = "chromedriver"
|
||||||
elif driver_type == WebDriverType.FIREFOX:
|
elif driver_type == WebDriverType.FIREFOX:
|
||||||
@@ -259,29 +282,15 @@ class WebDriverManager:
|
|||||||
WebDriverArch.Edge.WINX86_64,
|
WebDriverArch.Edge.WINX86_64,
|
||||||
]
|
]
|
||||||
exe_name = f"{driver_name}.exe" if is_win else driver_name
|
exe_name = f"{driver_name}.exe" if is_win else driver_name
|
||||||
driver_dir = Path(self.__driver_dir) / driver_type.value / driver_arch.value
|
driver_dir = Path(self.__driver_dir)/driver_type.value/driver_version/driver_arch.value
|
||||||
driver_path = driver_dir / exe_name
|
driver_path = driver_dir/exe_name
|
||||||
if driver_path.exists() and driver_path.is_file():
|
|
||||||
return driver_path
|
return driver_path
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _getDriverVersion(
|
|
||||||
self,
|
|
||||||
driver_type: WebDriverType,
|
|
||||||
browser_version: str
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
if driver_type == WebDriverType.FIREFOX:
|
|
||||||
return self._mapFirefoxDriverVersion(browser_version)
|
|
||||||
return browser_version
|
|
||||||
|
|
||||||
|
|
||||||
def refresh(
|
def refresh(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
with self.__lock:
|
|
||||||
self._detectBrowsers()
|
self._detectBrowsers()
|
||||||
self._checkDriverStatus()
|
self._checkDriverStatus()
|
||||||
|
|
||||||
@@ -297,21 +306,21 @@ class WebDriverManager:
|
|||||||
def getDriverInfo(
|
def getDriverInfo(
|
||||||
self,
|
self,
|
||||||
driver_type: WebDriverType
|
driver_type: WebDriverType
|
||||||
) -> Optional[WebDriverInfo]:
|
) -> list[WebDriverInfo]:
|
||||||
|
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
for driver_info in self.__driver_infos:
|
return [
|
||||||
if driver_info.driver_type == driver_type:
|
info
|
||||||
return driver_info
|
for info in self.__driver_infos
|
||||||
return None
|
if info.driver_type == driver_type
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def getDriverPath(
|
def getDriverPath(
|
||||||
self,
|
self,
|
||||||
driver_type: WebDriverType
|
driver_info: WebDriverInfo
|
||||||
) -> Optional[Path]:
|
) -> Optional[Path]:
|
||||||
|
|
||||||
driver_info = self.getDriverInfo(driver_type)
|
|
||||||
if driver_info and driver_info.driver_status == DriverStatus.INSTALLED:
|
if driver_info and driver_info.driver_status == DriverStatus.INSTALLED:
|
||||||
return driver_info.driver_path
|
return driver_info.driver_path
|
||||||
return None
|
return None
|
||||||
@@ -319,24 +328,28 @@ class WebDriverManager:
|
|||||||
|
|
||||||
def installDriver(
|
def installDriver(
|
||||||
self,
|
self,
|
||||||
driver_type: WebDriverType,
|
driver_info: WebDriverInfo,
|
||||||
progress_callback: Optional[Callable[[int, int, float, str], None]] = None
|
progress_callback: Optional[Callable[[float, int, float, str], None]] = None,
|
||||||
|
cancel_event: Optional[threading.Event] = None
|
||||||
) -> Optional[Path]:
|
) -> Optional[Path]:
|
||||||
|
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
driver_info = self.getDriverInfo(driver_type)
|
|
||||||
if not driver_info:
|
if not driver_info:
|
||||||
raise ValueError(f"未找到类型为 {driver_type} 的浏览器")
|
if progress_callback:
|
||||||
if driver_info.driver_status == DriverStatus.DOWNLOADING:
|
progress_callback(0, 0, 0, "未找到浏览器信息")
|
||||||
raise ValueError(f"{driver_type} 驱动正在下载中")
|
else:
|
||||||
driver_info.driver_status = DriverStatus.DOWNLOADING
|
raise ValueError("未找到浏览器信息")
|
||||||
|
if driver_info and driver_info.driver_status == DriverStatus.DOWNLOADING:
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback(0, 0, 0, f"{driver_info.driver_type} 驱动正在下载中")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{driver_info.driver_type} 驱动正在下载中")
|
||||||
try:
|
try:
|
||||||
driver_arch = self._mapWebBrowserArch(
|
if not driver_info:
|
||||||
driver_info.browser_info.browser_type,
|
raise ValueError("未找到浏览器信息")
|
||||||
driver_info.browser_info.browser_arch
|
driver_arch = driver_info.driver_arch
|
||||||
)
|
driver_type = driver_info.driver_type
|
||||||
browser_version = driver_info.browser_info.browser_version
|
driver_version = driver_info.driver_version
|
||||||
driver_version = self._getDriverVersion(driver_type, browser_version)
|
|
||||||
downloader = None
|
downloader = None
|
||||||
if driver_type == WebDriverType.CHROME:
|
if driver_type == WebDriverType.CHROME:
|
||||||
downloader = ChromeDriverDownloader(
|
downloader = ChromeDriverDownloader(
|
||||||
@@ -357,9 +370,13 @@ class WebDriverManager:
|
|||||||
download_dir=self.__driver_dir
|
download_dir=self.__driver_dir
|
||||||
)
|
)
|
||||||
if downloader is None:
|
if downloader is None:
|
||||||
raise ValueError(f"不支持的 Web Driver 类型 : {driver_type}")
|
if progress_callback:
|
||||||
|
progress_callback(0, 0, 0, f"不支持的 Web Driver 类型")
|
||||||
driver_path = downloader.download(progress_callback=progress_callback)
|
else:
|
||||||
|
raise ValueError(f"不支持的 Web Driver 类型")
|
||||||
|
with self.__lock:
|
||||||
|
driver_info.driver_status = DriverStatus.DOWNLOADING
|
||||||
|
driver_path = downloader.download(progress_callback=progress_callback, cancel_event=cancel_event)
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
if driver_path:
|
if driver_path:
|
||||||
driver_info.driver_path = driver_path
|
driver_info.driver_path = driver_path
|
||||||
@@ -369,6 +386,57 @@ class WebDriverManager:
|
|||||||
driver_info.driver_status = DriverStatus.ERROR
|
driver_info.driver_status = DriverStatus.ERROR
|
||||||
return driver_path
|
return driver_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
with self.__lock:
|
||||||
|
driver_info.driver_status = DriverStatus.ERROR
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def cancelDriverDownload(
|
||||||
|
self,
|
||||||
|
driver_info: WebDriverInfo
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
try:
|
||||||
|
driver_path = self._getDriverPath(driver_info)
|
||||||
|
if driver_path:
|
||||||
|
download_dir = driver_path.parent
|
||||||
|
if download_dir.exists():
|
||||||
|
shutil.rmtree(download_dir, ignore_errors=True)
|
||||||
|
with self.__lock:
|
||||||
|
driver_info.driver_path = None
|
||||||
|
driver_info.driver_status = DriverStatus.NOT_INSTALLED
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def uninstallDriver(
|
||||||
|
self,
|
||||||
|
driver_info: WebDriverInfo,
|
||||||
|
progress_callback: Optional[Callable[[int, int, float, str], None]] = None
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
with self.__lock:
|
||||||
|
if not driver_info:
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback(0, 0, 0, "未找到浏览器信息")
|
||||||
|
else:
|
||||||
|
raise ValueError("未找到浏览器信息")
|
||||||
|
if driver_info.driver_status != DriverStatus.INSTALLED:
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback(0, 0, 0, f"{driver_info.driver_type} 驱动未安装")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{driver_info.driver_type} 驱动未安装")
|
||||||
|
try:
|
||||||
|
driver_path = driver_info.driver_path
|
||||||
|
driver_path.unlink()
|
||||||
|
with self.__lock:
|
||||||
|
driver_info.driver_path = None
|
||||||
|
driver_info.driver_status = DriverStatus.NOT_INSTALLED
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
driver_info.driver_status = DriverStatus.ERROR
|
driver_info.driver_status = DriverStatus.ERROR
|
||||||
raise
|
raise
|
||||||
@@ -399,5 +467,5 @@ def instance(
|
|||||||
_webdriver_manager_instance = WebDriverManager(driver_dir)
|
_webdriver_manager_instance = WebDriverManager(driver_dir)
|
||||||
else:
|
else:
|
||||||
if driver_dir and _webdriver_manager_instance.driverDir() != os.path.abspath(driver_dir):
|
if driver_dir and _webdriver_manager_instance.driverDir() != os.path.abspath(driver_dir):
|
||||||
raise ValueError("WebDriverManager 的实例已初始化,不能使用不同的驱动目录")
|
raise ValueError("WebDriverManager 的实例已初始化, 不能使用不同的驱动目录")
|
||||||
return _webdriver_manager_instance
|
return _webdriver_manager_instance
|
||||||
|
|||||||
Reference in New Issue
Block a user