From 3fc74555cb876a0ba354c3537ee0a5d3a939fd6e Mon Sep 17 00:00:00 2001 From: hyb-oyqq <1512383570@qq.com> Date: Fri, 1 Aug 2025 17:54:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=AE=A1=E7=90=86=E5=99=A8=E5=92=8C=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在下载管理器中引入 CloudflareOptimizer、DownloadTaskManager 和 ExtractionHandler 模块,重构下载流程 - 优化下载进度更新频率,减少 UI 卡顿 - 改进下载和解压缩的处理逻辑,确保更流畅的用户体验 - 更新下载线程设置,支持更灵活的下载管理 --- source/core/__init__.py | 8 +- source/core/cloudflare_optimizer.py | 254 +++++++++++++ source/core/download_manager.py | 514 +++------------------------ source/core/download_task_manager.py | 221 ++++++++++++ source/core/extraction_handler.py | 81 +++++ source/main_window.py | 41 +-- source/workers/download.py | 74 +++- 7 files changed, 696 insertions(+), 497 deletions(-) create mode 100644 source/core/cloudflare_optimizer.py create mode 100644 source/core/download_task_manager.py create mode 100644 source/core/extraction_handler.py diff --git a/source/core/__init__.py b/source/core/__init__.py index 711de19..0c0afa1 100644 --- a/source/core/__init__.py +++ b/source/core/__init__.py @@ -7,6 +7,9 @@ from .game_detector import GameDetector from .patch_manager import PatchManager from .config_manager import ConfigManager from .privacy_manager import PrivacyManager +from .cloudflare_optimizer import CloudflareOptimizer +from .download_task_manager import DownloadTaskManager +from .extraction_handler import ExtractionHandler __all__ = [ 'MultiStageAnimations', @@ -17,5 +20,8 @@ __all__ = [ 'GameDetector', 'PatchManager', 'ConfigManager', - 'PrivacyManager' + 'PrivacyManager', + 'CloudflareOptimizer', + 'DownloadTaskManager', + 'ExtractionHandler' ] \ No newline at end of file diff --git a/source/core/cloudflare_optimizer.py b/source/core/cloudflare_optimizer.py new file mode 100644 index 0000000..a413d28 --- /dev/null +++ b/source/core/cloudflare_optimizer.py @@ -0,0 +1,254 @@ +import os +from urllib.parse import urlparse +from PySide6 import QtWidgets +from PySide6.QtCore import Qt, QTimer +from PySide6.QtGui import QIcon, QPixmap + +from utils import msgbox_frame, resource_path +from workers import IpOptimizerThread + + +class CloudflareOptimizer: + """Cloudflare IP优化器,负责处理IP优化和Cloudflare加速相关功能""" + + def __init__(self, main_window, hosts_manager): + """初始化Cloudflare优化器 + + Args: + main_window: 主窗口实例,用于访问UI和状态 + hosts_manager: Hosts文件管理器实例 + """ + self.main_window = main_window + self.hosts_manager = hosts_manager + self.optimized_ip = None + self.optimization_done = False # 标记是否已执行过优选 + self.countdown_finished = False # 标记倒计时是否结束 + self.optimizing_msg_box = None + self.optimization_cancelled = False + self.ip_optimizer_thread = None + + def is_optimization_done(self): + """检查是否已完成优化 + + Returns: + bool: 是否已完成优化 + """ + return self.optimization_done + + def is_countdown_finished(self): + """检查倒计时是否已完成 + + Returns: + bool: 倒计时是否已完成 + """ + return self.countdown_finished + + def get_optimized_ip(self): + """获取优选的IP地址 + + Returns: + str: 优选的IP地址,如果未优选则为None + """ + return self.optimized_ip + + def start_ip_optimization(self, url): + """开始IP优化过程 + + Args: + url: 用于优化的URL + """ + # 创建取消状态标记 + self.optimization_cancelled = False + self.countdown_finished = False + + # 使用Cloudflare图标创建消息框 + self.optimizing_msg_box = msgbox_frame( + f"通知 - {self.main_window.APP_NAME}", + "\n正在优选Cloudflare IP,请稍候...\n\n这可能需要5-10分钟,请耐心等待喵~" + ) + # 设置Cloudflare图标 + cf_icon_path = resource_path("IMG/ICO/cloudflare_logo_icon.ico") + if os.path.exists(cf_icon_path): + cf_pixmap = QPixmap(cf_icon_path) + if not cf_pixmap.isNull(): + self.optimizing_msg_box.setWindowIcon(QIcon(cf_pixmap)) + self.optimizing_msg_box.setIconPixmap(cf_pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation)) + + # 添加取消按钮 + self.optimizing_msg_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Cancel) + self.optimizing_msg_box.buttonClicked.connect(self._on_optimization_dialog_clicked) + self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal) + + # 创建并启动优化线程 + self.ip_optimizer_thread = IpOptimizerThread(url) + self.ip_optimizer_thread.finished.connect(self.on_optimization_finished) + self.ip_optimizer_thread.start() + + # 显示消息框(非模态,不阻塞) + self.optimizing_msg_box.open() + + def _on_optimization_dialog_clicked(self, button): + """处理优化对话框按钮点击 + + Args: + button: 被点击的按钮 + """ + if button.text() == "Cancel": # 如果是取消按钮 + # 标记已取消 + self.optimization_cancelled = True + + # 停止优化线程 + if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning(): + self.ip_optimizer_thread.stop() + + # 恢复主窗口状态 + self.main_window.setEnabled(True) + self.main_window.ui.start_install_text.setText("开始安装") + + # 显示取消消息 + QtWidgets.QMessageBox.information( + self.main_window, + f"已取消 - {self.main_window.APP_NAME}", + "\n已取消IP优选和安装过程。\n" + ) + + def on_optimization_finished(self, ip): + """IP优化完成后的处理 + + Args: + ip: 优选的IP地址,如果失败则为空字符串 + """ + # 如果已经取消,则不继续处理 + if hasattr(self, 'optimization_cancelled') and self.optimization_cancelled: + return + + self.optimized_ip = ip + self.optimization_done = True + self.countdown_finished = False # 确保倒计时标志重置 + + # 关闭提示框 + if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box: + if self.optimizing_msg_box.isVisible(): + self.optimizing_msg_box.accept() + self.optimizing_msg_box = None + + # 显示优选结果 + if not ip: + # 临时启用窗口以显示对话框 + self.main_window.setEnabled(True) + + msg_box = QtWidgets.QMessageBox(self.main_window) + msg_box.setWindowTitle(f"优选失败 - {self.main_window.APP_NAME}") + msg_box.setText("\n未能找到合适的Cloudflare IP,将使用默认网络进行下载。\n\n10秒后自动继续...") + msg_box.setIcon(QtWidgets.QMessageBox.Icon.Warning) + ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole) + cancel_button = msg_box.addButton("取消安装", QtWidgets.QMessageBox.ButtonRole.RejectRole) + + # 创建计时器实现倒计时 + countdown = 10 + timer = QTimer(self.main_window) + + def update_countdown(): + nonlocal countdown + countdown -= 1 + ok_button.setText(f"确定 ({countdown})") + if countdown <= 0: + timer.stop() + if msg_box.isVisible(): + msg_box.accept() + + timer.timeout.connect(update_countdown) + timer.start(1000) # 每秒更新一次 + + # 显示对话框并等待用户响应 + result = msg_box.exec() + + # 停止计时器 + timer.stop() + + # 如果用户点击了取消安装 + if msg_box.clickedButton() == cancel_button: + # 恢复主窗口状态 + self.main_window.setEnabled(True) + self.main_window.ui.start_install_text.setText("开始安装") + return False + + # 用户点击了继续,重新禁用主窗口 + self.main_window.setEnabled(False) + # 标记倒计时已完成 + self.countdown_finished = True + return True + else: + # 应用优选IP到hosts文件 + hostname = urlparse(self.main_window.current_url).hostname if hasattr(self.main_window, 'current_url') else None + + if hostname: + # 先清理可能存在的旧记录 + self.hosts_manager.clean_hostname_entries(hostname) + + # 临时启用窗口以显示对话框 + self.main_window.setEnabled(True) + + if self.hosts_manager.apply_ip(hostname, ip): + msg_box = QtWidgets.QMessageBox(self.main_window) + msg_box.setWindowTitle(f"成功 - {self.main_window.APP_NAME}") + msg_box.setText(f"\n已将优选IP ({ip}) 应用到hosts文件。\n\n10秒后自动继续...") + msg_box.setIcon(QtWidgets.QMessageBox.Icon.Information) + ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole) + cancel_button = msg_box.addButton("取消安装", QtWidgets.QMessageBox.ButtonRole.RejectRole) + + # 创建计时器实现倒计时 + countdown = 10 + timer = QTimer(self.main_window) + + def update_countdown(): + nonlocal countdown + countdown -= 1 + ok_button.setText(f"确定 ({countdown})") + if countdown <= 0: + timer.stop() + if msg_box.isVisible(): + msg_box.accept() + + timer.timeout.connect(update_countdown) + timer.start(1000) # 每秒更新一次 + + # 显示对话框并等待用户响应 + result = msg_box.exec() + + # 停止计时器 + timer.stop() + + # 如果用户点击了取消安装 + if msg_box.clickedButton() == cancel_button: + # 恢复主窗口状态 + self.main_window.setEnabled(True) + self.main_window.ui.start_install_text.setText("开始安装") + return False + else: + QtWidgets.QMessageBox.critical( + self.main_window, + f"错误 - {self.main_window.APP_NAME}", + "\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n" + ) + # 恢复主窗口状态 + self.main_window.ui.start_install_text.setText("开始安装") + return False + + # 用户点击了继续,重新禁用主窗口 + self.main_window.setEnabled(False) + # 标记倒计时已完成 + self.countdown_finished = True + + return True + + def stop_optimization(self): + """停止正在进行的IP优化""" + if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning(): + self.ip_optimizer_thread.stop() + self.ip_optimizer_thread.wait() + if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box: + if self.optimizing_msg_box.isVisible(): + self.optimizing_msg_box.accept() + self.optimizing_msg_box = None \ No newline at end of file diff --git a/source/core/download_manager.py b/source/core/download_manager.py index 981f23b..4bf0a2d 100644 --- a/source/core/download_manager.py +++ b/source/core/download_manager.py @@ -12,6 +12,9 @@ from PySide6.QtGui import QIcon, QPixmap, QFont from utils import msgbox_frame, HostsManager, resource_path from data.config import APP_NAME, PLUGIN, GAME_INFO, UA, CONFIG_URL, DOWNLOAD_THREADS, DEFAULT_DOWNLOAD_THREAD_LEVEL from workers import IpOptimizerThread +from core.cloudflare_optimizer import CloudflareOptimizer +from core.download_task_manager import DownloadTaskManager +from core.extraction_handler import ExtractionHandler class DownloadManager: def __init__(self, main_window): @@ -21,16 +24,20 @@ class DownloadManager: main_window: 主窗口实例,用于访问UI和状态 """ self.main_window = main_window + self.main_window.APP_NAME = APP_NAME # 为了让子模块能够访问APP_NAME self.selected_folder = "" self.download_queue = deque() self.current_download_thread = None self.hosts_manager = HostsManager() - self.optimized_ip = None - self.optimization_done = False # 标记是否已执行过优选 - self.optimizing_msg_box = None + # 添加下载线程级别 self.download_thread_level = DEFAULT_DOWNLOAD_THREAD_LEVEL + # 初始化子模块 + self.cloudflare_optimizer = CloudflareOptimizer(main_window, self.hosts_manager) + self.download_task_manager = DownloadTaskManager(main_window, self.download_thread_level) + self.extraction_handler = ExtractionHandler(main_window) + def file_dialog(self): """显示文件夹选择对话框,选择游戏安装目录""" self.selected_folder = QtWidgets.QFileDialog.getExistingDirectory( @@ -67,12 +74,6 @@ class DownloadManager: if debug_mode: print(f"DEBUG: 使用识别到的游戏目录 {game}: {game_dir}") print(f"DEBUG: 安装路径设置为: {install_path}") - else: - # 回退到原始路径计算方式 - install_path = os.path.join(self.selected_folder, info["install_path"]) - install_paths[game] = install_path - if debug_mode: - print(f"DEBUG: 未识别到游戏目录 {game}, 使用默认路径: {install_path}") return install_paths @@ -442,209 +443,28 @@ class DownloadManager: use_optimization = clicked_button == yes_button - if use_optimization and not self.optimization_done: + if use_optimization and not self.cloudflare_optimizer.is_optimization_done(): first_url = self.download_queue[0][0] - self._start_ip_optimization(first_url) + # 保存当前URL供CloudflareOptimizer使用 + self.main_window.current_url = first_url + # 使用CloudflareOptimizer进行IP优化 + self.cloudflare_optimizer.start_ip_optimization(first_url) + # 等待CloudflareOptimizer的回调 + # on_optimization_finished会被调用,然后决定是否继续 + QtCore.QTimer.singleShot(100, self.check_optimization_status) else: # 如果用户选择不优化,或已经优化过,直接开始下载 self.next_download_task() - - def _start_ip_optimization(self, url): - """开始IP优化过程 - - Args: - url: 用于优化的URL - """ - # 创建取消状态标记 - self.optimization_cancelled = False - - # 使用Cloudflare图标创建消息框 - self.optimizing_msg_box = msgbox_frame( - f"通知 - {APP_NAME}", - "\n正在优选Cloudflare IP,请稍候...\n\n这可能需要5-10分钟,请耐心等待喵~" - ) - # 设置Cloudflare图标 - cf_icon_path = resource_path("IMG/ICO/cloudflare_logo_icon.ico") - if os.path.exists(cf_icon_path): - cf_pixmap = QPixmap(cf_icon_path) - if not cf_pixmap.isNull(): - self.optimizing_msg_box.setWindowIcon(QIcon(cf_pixmap)) - self.optimizing_msg_box.setIconPixmap(cf_pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation)) - - # 添加取消按钮 - self.optimizing_msg_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Cancel) - self.optimizing_msg_box.buttonClicked.connect(self._on_optimization_dialog_clicked) - self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal) - - # 创建并启动优化线程 - self.ip_optimizer_thread = IpOptimizerThread(url) - self.ip_optimizer_thread.finished.connect(self.on_optimization_finished) - self.ip_optimizer_thread.start() - - # 显示消息框(非模态,不阻塞) - self.optimizing_msg_box.open() - - def _on_optimization_dialog_clicked(self, button): - """处理优化对话框按钮点击 - - Args: - button: 被点击的按钮 - """ - if button.text() == "Cancel": # 如果是取消按钮 - # 标记已取消 - self.optimization_cancelled = True - - # 停止优化线程 - if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning(): - self.ip_optimizer_thread.stop() - - # 恢复主窗口状态 - self.main_window.setEnabled(True) - self.main_window.ui.start_install_text.setText("开始安装") - - # 清空下载队列 - self.download_queue.clear() - - # 显示取消消息 - QtWidgets.QMessageBox.information( - self.main_window, - f"已取消 - {APP_NAME}", - "\n已取消IP优选和安装过程。\n" - ) - - def on_optimization_finished(self, ip): - """IP优化完成后的处理 - - Args: - ip: 优选的IP地址,如果失败则为空字符串 - """ - # 如果已经取消,则不继续处理 - if hasattr(self, 'optimization_cancelled') and self.optimization_cancelled: - return - - self.optimized_ip = ip - self.optimization_done = True - - # 关闭提示框 - if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box: - if self.optimizing_msg_box.isVisible(): - self.optimizing_msg_box.accept() - self.optimizing_msg_box = None - - # 显示优选结果 - if not ip: - # 临时启用窗口以显示对话框 - self.main_window.setEnabled(True) - - msg_box = QtWidgets.QMessageBox(self.main_window) - msg_box.setWindowTitle(f"优选失败 - {APP_NAME}") - msg_box.setText("\n未能找到合适的Cloudflare IP,将使用默认网络进行下载。\n\n10秒后自动继续...") - msg_box.setIcon(QtWidgets.QMessageBox.Icon.Warning) - ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole) - cancel_button = msg_box.addButton("取消安装", QtWidgets.QMessageBox.ButtonRole.RejectRole) - - # 创建计时器实现倒计时 - countdown = 10 - timer = QtCore.QTimer(self.main_window) - - def update_countdown(): - nonlocal countdown - countdown -= 1 - ok_button.setText(f"确定 ({countdown})") - if countdown <= 0: - timer.stop() - if msg_box.isVisible(): - msg_box.accept() - - timer.timeout.connect(update_countdown) - timer.start(1000) # 每秒更新一次 - - # 显示对话框并等待用户响应 - result = msg_box.exec() - - # 停止计时器 - timer.stop() - - # 如果用户点击了取消安装 - if msg_box.clickedButton() == cancel_button: - # 恢复主窗口状态 - self.main_window.setEnabled(True) - self.main_window.ui.start_install_text.setText("开始安装") - # 清空下载队列 - self.download_queue.clear() - return - - # 用户点击了继续,重新禁用主窗口 - self.main_window.setEnabled(False) + + def check_optimization_status(self): + """检查IP优化状态并继续下载流程""" + # 必须同时满足:优化已完成且倒计时已结束 + if self.cloudflare_optimizer.is_optimization_done() and self.cloudflare_optimizer.is_countdown_finished(): + self.next_download_task() else: - # 应用优选IP到hosts文件 - if self.download_queue: - first_url = self.download_queue[0][0] - hostname = urlparse(first_url).hostname - - # 先清理可能存在的旧记录 - self.hosts_manager.clean_hostname_entries(hostname) - - # 临时启用窗口以显示对话框 - self.main_window.setEnabled(True) - - if self.hosts_manager.apply_ip(hostname, ip): - msg_box = QtWidgets.QMessageBox(self.main_window) - msg_box.setWindowTitle(f"成功 - {APP_NAME}") - msg_box.setText(f"\n已将优选IP ({ip}) 应用到hosts文件。\n\n10秒后自动继续...") - msg_box.setIcon(QtWidgets.QMessageBox.Icon.Information) - ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole) - cancel_button = msg_box.addButton("取消安装", QtWidgets.QMessageBox.ButtonRole.RejectRole) - - # 创建计时器实现倒计时 - countdown = 10 - timer = QtCore.QTimer(self.main_window) - - def update_countdown(): - nonlocal countdown - countdown -= 1 - ok_button.setText(f"确定 ({countdown})") - if countdown <= 0: - timer.stop() - if msg_box.isVisible(): - msg_box.accept() - - timer.timeout.connect(update_countdown) - timer.start(1000) # 每秒更新一次 - - # 显示对话框并等待用户响应 - result = msg_box.exec() - - # 停止计时器 - timer.stop() - - # 如果用户点击了取消安装 - if msg_box.clickedButton() == cancel_button: - # 恢复主窗口状态 - self.main_window.setEnabled(True) - self.main_window.ui.start_install_text.setText("开始安装") - # 清空下载队列 - self.download_queue.clear() - return - else: - QtWidgets.QMessageBox.critical( - self.main_window, - f"错误 - {APP_NAME}", - "\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n" - ) - # 恢复主窗口状态 - self.main_window.ui.start_install_text.setText("开始安装") - # 清空下载队列并退出 - self.download_queue.clear() - return - - # 用户点击了继续,重新禁用主窗口 - self.main_window.setEnabled(False) - - # 计时器结束或用户点击确定时,继续下载 - QtCore.QTimer.singleShot(100, self.next_download_task) - + # 否则,继续等待100ms后再次检查 + QtCore.QTimer.singleShot(100, self.check_optimization_status) + def next_download_task(self): """处理下载队列中的下一个任务""" if not self.download_queue: @@ -652,7 +472,7 @@ class DownloadManager: return # 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务 - if self.current_download_thread and self.current_download_thread.isRunning(): + if self.download_task_manager.current_download_thread and self.download_task_manager.current_download_thread.isRunning(): return # 获取下一个下载任务并开始 @@ -696,69 +516,18 @@ class DownloadManager: # 创建进度窗口并开始下载 self.main_window.progress_window = self.main_window.create_progress_window() - self.start_download(url, _7z_path, game_version, game_folder, plugin_path) - - def start_download(self, url, _7z_path, game_version, game_folder, plugin_path): - """启动下载线程 - - Args: - url: 下载URL - _7z_path: 7z文件保存路径 - game_version: 游戏版本名称 - game_folder: 游戏文件夹路径 - plugin_path: 插件路径 - """ - # 按钮在file_dialog中已设置为禁用状态 + # 从CloudflareOptimizer获取已优选的IP + self.optimized_ip = self.cloudflare_optimizer.get_optimized_ip() if self.optimized_ip: print(f"已为 {game_version} 获取到优选IP: {self.optimized_ip}") else: print(f"未能为 {game_version} 获取优选IP,将使用默认线路。") - # 创建并连接下载线程 - self.current_download_thread = self.main_window.create_download_thread(url, _7z_path, game_version) - self.current_download_thread.progress.connect(self.main_window.progress_window.update_progress) - self.current_download_thread.finished.connect( - lambda success, error: self.on_download_finished( - success, - error, - url, - game_folder, - game_version, - _7z_path, - plugin_path, - ) - ) - - # 连接停止按钮到我们的on_download_stopped方法 - self.main_window.progress_window.stop_button.clicked.connect(self.on_download_stopped) - - # 连接暂停/恢复按钮 - self.main_window.progress_window.pause_resume_button.clicked.connect(self.toggle_download_pause) - - # 启动线程和显示进度窗口 - self.current_download_thread.start() - self.main_window.progress_window.exec() - - def toggle_download_pause(self): - """切换下载的暂停/恢复状态""" - if not self.current_download_thread: - return - - # 获取当前暂停状态 - is_paused = self.current_download_thread.is_paused() - - if is_paused: - # 如果已暂停,则恢复下载 - success = self.current_download_thread.resume() - if success: - self.main_window.progress_window.update_pause_button_state(False) - else: - # 如果未暂停,则暂停下载 - success = self.current_download_thread.pause() - if success: - self.main_window.progress_window.update_pause_button_state(True) + # 使用DownloadTaskManager开始下载 + self.download_task_manager.start_download(url, _7z_path, game_version, game_folder, plugin_path) + # 连接到主窗口中的下载完成处理函数 def on_download_finished(self, success, error, url, game_folder, game_version, _7z_path, plugin_path): """下载完成后的处理 @@ -810,79 +579,31 @@ class DownloadManager: self.on_download_stopped() return - # 下载成功,开始解压缩 - self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="extraction") - - # 创建并启动解压线程 - self.main_window.extraction_thread = self.main_window.create_extraction_thread( - _7z_path, game_folder, plugin_path, game_version - ) - self.main_window.extraction_thread.finished.connect(self.on_extraction_finished) - self.main_window.extraction_thread.start() + # 下载成功,使用ExtractionHandler开始解压缩 + self.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version) + # extraction_handler的回调会处理下一步操作 - def on_extraction_finished(self, success, error_message, game_version): - """解压完成后的处理 + def on_extraction_finished(self, continue_download): + """解压完成后的回调,决定是否继续下载队列 Args: - success: 是否解压成功 - error_message: 错误信息 - game_version: 游戏版本 + continue_download: 是否继续下载队列中的下一个任务 """ - # 关闭哈希检查窗口 - if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible(): - self.main_window.hash_msg_box.close() - self.main_window.hash_msg_box = None - - # 处理解压结果 - if not success: - # 临时启用窗口以显示错误消息 - self.main_window.setEnabled(True) - - QtWidgets.QMessageBox.critical(self.main_window, f"错误 - {APP_NAME}", error_message) - self.main_window.installed_status[game_version] = False - - # 询问用户是否继续其他游戏的安装 - reply = QtWidgets.QMessageBox.question( - self.main_window, - f"继续安装? - {APP_NAME}", - f"\n{game_version} 的补丁安装失败。\n\n是否继续安装其他游戏的补丁?\n", - QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, - QtWidgets.QMessageBox.StandardButton.Yes - ) - - if reply == QtWidgets.QMessageBox.StandardButton.Yes: - # 继续下一个,重新禁用窗口 - self.main_window.setEnabled(False) - self.next_download_task() - else: - # 用户选择停止,保持窗口启用状态 - self.main_window.ui.start_install_text.setText("开始安装") - # 清空剩余队列 - self.download_queue.clear() - # 显示已完成的安装结果 - self.main_window.show_result() - return + if continue_download: + # 继续下一个下载任务 + self.next_download_task() else: - self.main_window.installed_status[game_version] = True - - # 继续下一个下载任务 - self.next_download_task() + # 清空剩余队列并显示结果 + self.download_queue.clear() + self.main_window.show_result() def on_download_stopped(self): """当用户点击停止按钮或选择结束时调用的函数""" # 停止IP优化线程 - if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning(): - self.ip_optimizer_thread.stop() - self.ip_optimizer_thread.wait() - if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box: - if self.optimizing_msg_box.isVisible(): - self.optimizing_msg_box.accept() - self.optimizing_msg_box = None + self.cloudflare_optimizer.stop_optimization() # 停止当前可能仍在运行的下载线程 - if self.current_download_thread and self.current_download_thread.isRunning(): - self.current_download_thread.stop() - self.current_download_thread.wait() # 等待线程完全终止 + self.download_task_manager.stop_download() # 清空下载队列,因为用户决定停止 self.download_queue.clear() @@ -893,7 +614,7 @@ class DownloadManager: self.main_window.progress_window.reject() self.main_window.progress_window = None - # 可以在这里决定是否立即进行哈希比较或显示结果 + # 退出应用程序 print("下载已全部停止。") # 恢复主窗口状态 @@ -906,141 +627,16 @@ class DownloadManager: f"已取消 - {APP_NAME}", "\n已成功取消安装进程。\n" ) - + + # 以下方法委托给DownloadTaskManager def get_download_thread_count(self): - """获取当前下载线程设置对应的线程数 - - Returns: - int: 下载线程数 - """ - # 获取当前线程级别对应的线程数 - thread_count = DOWNLOAD_THREADS.get(self.download_thread_level, DOWNLOAD_THREADS["medium"]) - return thread_count + """获取当前下载线程设置对应的线程数""" + return self.download_task_manager.get_download_thread_count() def set_download_thread_level(self, level): - """设置下载线程级别 - - Args: - level: 线程级别 (low, medium, high, extreme, insane) - - Returns: - bool: 设置是否成功 - """ - if level in DOWNLOAD_THREADS: - old_level = self.download_thread_level - self.download_thread_level = level - - # 只有非极端级别才保存到配置 - if level not in ["extreme", "insane"]: - if hasattr(self.main_window, 'config'): - self.main_window.config["download_thread_level"] = level - self.main_window.save_config(self.main_window.config) - - return True - return False + """设置下载线程级别""" + return self.download_task_manager.set_download_thread_level(level) def show_download_thread_settings(self): """显示下载线程设置对话框""" - # 创建对话框 - from PySide6.QtWidgets import QDialog, QVBoxLayout, QRadioButton, QPushButton, QLabel, QButtonGroup, QHBoxLayout - from PySide6.QtCore import Qt - - dialog = QDialog(self.main_window) - dialog.setWindowTitle(f"下载线程设置 - {APP_NAME}") - dialog.setMinimumWidth(350) - - layout = QVBoxLayout(dialog) - - # 添加说明标签 - info_label = QLabel("选择下载线程数量(更多线程通常可以提高下载速度):", dialog) - info_label.setWordWrap(True) - layout.addWidget(info_label) - - # 创建按钮组 - button_group = QButtonGroup(dialog) - - # 添加线程选项 - thread_options = { - "low": f"低速 - {DOWNLOAD_THREADS['low']}线程(慢慢来,不着急)", - "medium": f"中速 - {DOWNLOAD_THREADS['medium']}线程(快人半步)", - "high": f"高速 - {DOWNLOAD_THREADS['high']}线程(默认,推荐配置)", - "extreme": f"极速 - {DOWNLOAD_THREADS['extreme']}线程(如果你对你的网和电脑很自信的话)", - "insane": f"狂暴 - {DOWNLOAD_THREADS['insane']}线程(看看是带宽和性能先榨干还是牛牛先榨干)" - } - - radio_buttons = {} - - for level, text in thread_options.items(): - radio = QRadioButton(text, dialog) - - # 选中当前使用的线程级别 - if level == self.download_thread_level: - radio.setChecked(True) - - button_group.addButton(radio) - layout.addWidget(radio) - radio_buttons[level] = radio - - layout.addSpacing(10) - - # 添加按钮区域 - btn_layout = QHBoxLayout() - - ok_button = QPushButton("确定", dialog) - cancel_button = QPushButton("取消", dialog) - - btn_layout.addWidget(ok_button) - btn_layout.addWidget(cancel_button) - - layout.addLayout(btn_layout) - - # 连接按钮事件 - ok_button.clicked.connect(dialog.accept) - cancel_button.clicked.connect(dialog.reject) - - # 显示对话框 - result = dialog.exec() - - # 处理结果 - if result == QDialog.DialogCode.Accepted: - # 获取用户选择的线程级别 - selected_level = None - for level, radio in radio_buttons.items(): - if radio.isChecked(): - selected_level = level - break - - if selected_level: - # 为极速和狂暴模式显示警告 - if selected_level in ["extreme", "insane"]: - warning_result = QtWidgets.QMessageBox.warning( - self.main_window, - f"高风险警告 - {APP_NAME}", - "警告!过高的线程数可能导致CPU负载过高或其他恶性问题!\n你确定要这么做吗?", - QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, - QtWidgets.QMessageBox.StandardButton.No - ) - - if warning_result != QtWidgets.QMessageBox.StandardButton.Yes: - return False - - success = self.set_download_thread_level(selected_level) - - if success: - # 显示设置成功消息 - thread_count = DOWNLOAD_THREADS[selected_level] - message = f"\n已成功设置下载线程为: {thread_count}线程\n" - - # 对于极速和狂暴模式,添加仅本次生效的提示 - if selected_level in ["extreme", "insane"]: - message += "\n注意:极速/狂暴模式仅本次生效。软件重启后将恢复默认设置。\n" - - QtWidgets.QMessageBox.information( - self.main_window, - f"设置成功 - {APP_NAME}", - message - ) - - return True - - return False \ No newline at end of file + return self.download_task_manager.show_download_thread_settings() \ No newline at end of file diff --git a/source/core/download_task_manager.py b/source/core/download_task_manager.py new file mode 100644 index 0000000..72ad5ee --- /dev/null +++ b/source/core/download_task_manager.py @@ -0,0 +1,221 @@ +from PySide6 import QtWidgets +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QDialog, QVBoxLayout, QRadioButton, QPushButton, QLabel, QButtonGroup, QHBoxLayout +from PySide6.QtGui import QFont + +from data.config import DOWNLOAD_THREADS + + +class DownloadTaskManager: + """下载任务管理器,负责管理下载任务和线程设置""" + + def __init__(self, main_window, download_thread_level="medium"): + """初始化下载任务管理器 + + Args: + main_window: 主窗口实例,用于访问UI和状态 + download_thread_level: 下载线程级别,默认为"medium" + """ + self.main_window = main_window + self.APP_NAME = main_window.APP_NAME if hasattr(main_window, 'APP_NAME') else "" + self.current_download_thread = None + self.download_thread_level = download_thread_level + + def start_download(self, url, _7z_path, game_version, game_folder, plugin_path): + """启动下载线程 + + Args: + url: 下载URL + _7z_path: 7z文件保存路径 + game_version: 游戏版本名称 + game_folder: 游戏文件夹路径 + plugin_path: 插件路径 + """ + # 按钮在file_dialog中已设置为禁用状态 + + # 创建并连接下载线程 + self.current_download_thread = self.main_window.create_download_thread(url, _7z_path, game_version) + self.current_download_thread.progress.connect(self.main_window.progress_window.update_progress) + self.current_download_thread.finished.connect( + lambda success, error: self.main_window.download_manager.on_download_finished( + success, + error, + url, + game_folder, + game_version, + _7z_path, + plugin_path, + ) + ) + + # 连接停止按钮到download_manager的on_download_stopped方法 + self.main_window.progress_window.stop_button.clicked.connect(self.main_window.download_manager.on_download_stopped) + + # 连接暂停/恢复按钮 + self.main_window.progress_window.pause_resume_button.clicked.connect(self.toggle_download_pause) + + # 启动线程和显示进度窗口 + self.current_download_thread.start() + self.main_window.progress_window.exec() + + def toggle_download_pause(self): + """切换下载的暂停/恢复状态""" + if not self.current_download_thread: + return + + # 获取当前暂停状态 + is_paused = self.current_download_thread.is_paused() + + if is_paused: + # 如果已暂停,则恢复下载 + success = self.current_download_thread.resume() + if success: + self.main_window.progress_window.update_pause_button_state(False) + else: + # 如果未暂停,则暂停下载 + success = self.current_download_thread.pause() + if success: + self.main_window.progress_window.update_pause_button_state(True) + + def get_download_thread_count(self): + """获取当前下载线程设置对应的线程数 + + Returns: + int: 下载线程数 + """ + # 获取当前线程级别对应的线程数 + thread_count = DOWNLOAD_THREADS.get(self.download_thread_level, DOWNLOAD_THREADS["medium"]) + return thread_count + + def set_download_thread_level(self, level): + """设置下载线程级别 + + Args: + level: 线程级别 (low, medium, high, extreme, insane) + + Returns: + bool: 设置是否成功 + """ + if level in DOWNLOAD_THREADS: + old_level = self.download_thread_level + self.download_thread_level = level + + # 只有非极端级别才保存到配置 + if level not in ["extreme", "insane"]: + if hasattr(self.main_window, 'config'): + self.main_window.config["download_thread_level"] = level + self.main_window.save_config(self.main_window.config) + + return True + return False + + def show_download_thread_settings(self): + """显示下载线程设置对话框""" + # 创建对话框 + dialog = QDialog(self.main_window) + dialog.setWindowTitle(f"下载线程设置 - {self.APP_NAME}") + dialog.setMinimumWidth(350) + + layout = QVBoxLayout(dialog) + + # 添加说明标签 + info_label = QLabel("选择下载线程数量(更多线程通常可以提高下载速度):", dialog) + info_label.setWordWrap(True) + layout.addWidget(info_label) + + # 创建按钮组 + button_group = QButtonGroup(dialog) + + # 添加线程选项 + thread_options = { + "low": f"低速 - {DOWNLOAD_THREADS['low']}线程(慢慢来,不着急)", + "medium": f"中速 - {DOWNLOAD_THREADS['medium']}线程(快人半步)", + "high": f"高速 - {DOWNLOAD_THREADS['high']}线程(默认,推荐配置)", + "extreme": f"极速 - {DOWNLOAD_THREADS['extreme']}线程(如果你对你的网和电脑很自信的话)", + "insane": f"狂暴 - {DOWNLOAD_THREADS['insane']}线程(看看是带宽和性能先榨干还是牛牛先榨干)" + } + + radio_buttons = {} + + for level, text in thread_options.items(): + radio = QRadioButton(text, dialog) + + # 选中当前使用的线程级别 + if level == self.download_thread_level: + radio.setChecked(True) + + button_group.addButton(radio) + layout.addWidget(radio) + radio_buttons[level] = radio + + layout.addSpacing(10) + + # 添加按钮区域 + btn_layout = QHBoxLayout() + + ok_button = QPushButton("确定", dialog) + cancel_button = QPushButton("取消", dialog) + + btn_layout.addWidget(ok_button) + btn_layout.addWidget(cancel_button) + + layout.addLayout(btn_layout) + + # 连接按钮事件 + ok_button.clicked.connect(dialog.accept) + cancel_button.clicked.connect(dialog.reject) + + # 显示对话框 + result = dialog.exec() + + # 处理结果 + if result == QDialog.DialogCode.Accepted: + # 获取用户选择的线程级别 + selected_level = None + for level, radio in radio_buttons.items(): + if radio.isChecked(): + selected_level = level + break + + if selected_level: + # 为极速和狂暴模式显示警告 + if selected_level in ["extreme", "insane"]: + warning_result = QtWidgets.QMessageBox.warning( + self.main_window, + f"高风险警告 - {self.APP_NAME}", + "警告!过高的线程数可能导致CPU负载过高或其他恶性问题!\n你确定要这么做吗?", + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.No + ) + + if warning_result != QtWidgets.QMessageBox.StandardButton.Yes: + return False + + success = self.set_download_thread_level(selected_level) + + if success: + # 显示设置成功消息 + thread_count = DOWNLOAD_THREADS[selected_level] + message = f"\n已成功设置下载线程为: {thread_count}线程\n" + + # 对于极速和狂暴模式,添加仅本次生效的提示 + if selected_level in ["extreme", "insane"]: + message += "\n注意:极速/狂暴模式仅本次生效。软件重启后将恢复默认设置。\n" + + QtWidgets.QMessageBox.information( + self.main_window, + f"设置成功 - {self.APP_NAME}", + message + ) + + return True + + return False + + def stop_download(self): + """停止当前下载线程""" + if self.current_download_thread and self.current_download_thread.isRunning(): + self.current_download_thread.stop() + self.current_download_thread.wait() # 等待线程完全终止 + return True + return False \ No newline at end of file diff --git a/source/core/extraction_handler.py b/source/core/extraction_handler.py new file mode 100644 index 0000000..69c08f5 --- /dev/null +++ b/source/core/extraction_handler.py @@ -0,0 +1,81 @@ +import os +from PySide6 import QtWidgets +from PySide6.QtWidgets import QMessageBox + + +class ExtractionHandler: + """解压处理器,负责管理解压任务和结果处理""" + + def __init__(self, main_window): + """初始化解压处理器 + + Args: + main_window: 主窗口实例,用于访问UI和状态 + """ + self.main_window = main_window + self.APP_NAME = main_window.APP_NAME if hasattr(main_window, 'APP_NAME') else "" + + def start_extraction(self, _7z_path, game_folder, plugin_path, game_version): + """开始解压任务 + + Args: + _7z_path: 7z文件路径 + game_folder: 游戏文件夹路径 + plugin_path: 插件路径 + game_version: 游戏版本名称 + """ + # 显示解压中的消息窗口 + self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="extraction") + + # 创建并启动解压线程 + self.main_window.extraction_thread = self.main_window.create_extraction_thread( + _7z_path, game_folder, plugin_path, game_version + ) + self.main_window.extraction_thread.finished.connect(self.on_extraction_finished) + self.main_window.extraction_thread.start() + + def on_extraction_finished(self, success, error_message, game_version): + """解压完成后的处理 + + Args: + success: 是否解压成功 + error_message: 错误信息 + game_version: 游戏版本 + """ + # 关闭哈希检查窗口 + if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible(): + self.main_window.hash_msg_box.close() + self.main_window.hash_msg_box = None + + # 处理解压结果 + if not success: + # 临时启用窗口以显示错误消息 + self.main_window.setEnabled(True) + + QtWidgets.QMessageBox.critical(self.main_window, f"错误 - {self.APP_NAME}", error_message) + self.main_window.installed_status[game_version] = False + + # 询问用户是否继续其他游戏的安装 + reply = QtWidgets.QMessageBox.question( + self.main_window, + f"继续安装? - {self.APP_NAME}", + f"\n{game_version} 的补丁安装失败。\n\n是否继续安装其他游戏的补丁?\n", + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.Yes + ) + + if reply == QtWidgets.QMessageBox.StandardButton.Yes: + # 继续下一个,重新禁用窗口 + self.main_window.setEnabled(False) + # 通知DownloadManager继续下一个下载任务 + self.main_window.download_manager.on_extraction_finished(True) + else: + # 用户选择停止,保持窗口启用状态 + self.main_window.ui.start_install_text.setText("开始安装") + # 通知DownloadManager停止下载队列 + self.main_window.download_manager.on_extraction_finished(False) + else: + # 更新安装状态 + self.main_window.installed_status[game_version] = True + # 通知DownloadManager继续下一个下载任务 + self.main_window.download_manager.on_extraction_finished(True) \ No newline at end of file diff --git a/source/main_window.py b/source/main_window.py index 7b281b0..3ab23e5 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -361,25 +361,27 @@ class MainWindow(QMainWindow): install_paths = self.download_manager.get_install_paths() if hasattr(self.download_manager, "get_install_paths") else {} for game_version, is_installed in self.installed_status.items(): - path = install_paths.get(game_version, "") - - # 检查游戏是否存在但未通过本次安装补丁 - if is_installed: - # 游戏已安装补丁 - if hasattr(self, 'download_queue_history') and game_version not in self.download_queue_history: - # 已有补丁,被跳过下载 - skipped_versions.append(game_version) + # 只处理install_paths中存在的游戏版本 + if game_version in install_paths: + path = install_paths[game_version] + + # 检查游戏是否存在但未通过本次安装补丁 + if is_installed: + # 游戏已安装补丁 + if hasattr(self, 'download_queue_history') and game_version not in self.download_queue_history: + # 已有补丁,被跳过下载 + skipped_versions.append(game_version) + else: + # 本次成功安装 + installed_versions.append(game_version) else: - # 本次成功安装 - installed_versions.append(game_version) - else: - # 游戏未安装补丁 - if os.path.exists(path): - # 游戏文件夹存在,但安装失败 - failed_versions.append(game_version) - else: - # 游戏文件夹不存在 - not_found_versions.append(game_version) + # 游戏未安装补丁 + if os.path.exists(path): + # 游戏文件夹存在,但安装失败 + failed_versions.append(game_version) + else: + # 游戏文件夹不存在 + not_found_versions.append(game_version) # 构建结果信息 result_text = f"\n安装结果:\n" @@ -387,7 +389,6 @@ class MainWindow(QMainWindow): # 总数统计 - 不再显示已跳过的数量 total_installed = len(installed_versions) total_failed = len(failed_versions) - total_not_found = len(not_found_versions) result_text += f"安装成功:{total_installed} 个 安装失败:{total_failed} 个\n\n" @@ -399,7 +400,7 @@ class MainWindow(QMainWindow): result_text += f"【安装失败】:\n{chr(10).join(failed_versions)}\n\n" if not_found_versions: - # 将"未在指定目录找到"改为"尚未安装补丁的游戏" + # 只有在真正检测到了游戏但未安装补丁时才显示 result_text += f"【尚未安装补丁的游戏】:\n{chr(10).join(not_found_versions)}\n" QMessageBox.information( diff --git a/source/workers/download.py b/source/workers/download.py index b44b2c5..4a0b1e5 100644 --- a/source/workers/download.py +++ b/source/workers/download.py @@ -10,6 +10,7 @@ from utils import resource_path from data.config import APP_NAME, UA import signal import ctypes +import time # Windows API常量和函数 if sys.platform == 'win32': @@ -136,6 +137,23 @@ class DownloadThread(QThread): def is_paused(self): """返回当前下载是否处于暂停状态""" return self._is_paused + + def _emit_progress(self, percent, threads, speed, eta): + """用于直接连接的进度发射方法 + + Args: + percent: 完成百分比 + threads: 线程数 + speed: 下载速度 + eta: 预计完成时间 + """ + self.progress.emit({ + "game": self.game_version, + "percent": percent, + "threads": threads, + "speed": speed, + "eta": eta + }) def run(self): try: @@ -178,7 +196,7 @@ class DownloadThread(QThread): '--header', 'Sec-Fetch-Site: same-origin', '--http-accept-gzip=true', '--console-log-level=notice', - '--summary-interval=1', + '--summary-interval=0.5', # 减小摘要间隔到0.5秒,提高进度更新频率 '--log-level=notice', '--max-tries=3', '--retry-wait=2', @@ -193,7 +211,6 @@ class DownloadThread(QThread): '--file-allocation=none', # 禁用文件预分配加快开始 '--async-dns=true', # 使用异步DNS '--disable-ipv6=true', # 禁用IPv6提高速度 - '--quiet=true' # 减少非必要日志输出 ]) # 证书验证现在总是需要,因为我们依赖hosts文件 @@ -211,6 +228,10 @@ class DownloadThread(QThread): # 例如: #1 GID[...]( 5%) CN:1 DL:10.5MiB/s ETA:1m30s progress_pattern = re.compile(r'\((\d{1,3})%\).*?CN:(\d+).*?DL:\s*([^\s]+).*?ETA:\s*([^\s\]]+)') + # 添加限流计时器,防止更新过于频繁导致UI卡顿 + last_update_time = 0 + update_interval = 0.2 # 限制UI更新频率,每0.2秒最多更新一次 + full_output = [] while self._is_running and self.process.poll() is None: if self.process.stdout: @@ -225,17 +246,26 @@ class DownloadThread(QThread): match = progress_pattern.search(line) if match: - percent = int(match.group(1)) - threads = match.group(2) - speed = match.group(3) - eta = match.group(4) - self.progress.emit({ - "game": self.game_version, - "percent": percent, - "threads": threads, - "speed": speed, - "eta": eta - }) + # 检查是否达到更新间隔 + current_time = time.time() + if current_time - last_update_time >= update_interval: + percent = int(match.group(1)) + threads = match.group(2) + speed = match.group(3) + eta = match.group(4) + + # 直接更新UI,不经过事件队列,减少延迟 + QtCore.QMetaObject.invokeMethod( + self, + "_emit_progress", + Qt.ConnectionType.DirectConnection, + QtCore.Q_ARG(int, percent), + QtCore.Q_ARG(str, threads), + QtCore.Q_ARG(str, speed), + QtCore.Q_ARG(str, eta) + ) + + last_update_time = current_time return_code = self.process.wait() @@ -299,6 +329,9 @@ class ProgressWindow(QDialog): # 设置暂停/恢复状态 self.is_paused = False + # 添加最后进度记录,用于优化UI更新 + self._last_percent = -1 + def update_pause_button_state(self, is_paused): """更新暂停按钮的显示状态 @@ -321,10 +354,17 @@ class ProgressWindow(QDialog): # 清除ETA值中可能存在的"]"符号 if isinstance(eta, str): eta = eta.replace("]", "") - - self.game_label.setText(f"正在下载 {game_version} 的补丁") - self.progress_bar.setValue(int(percent)) - self.stats_label.setText(f"速度: {speed} | 线程: {threads} | 剩余时间: {eta}") + + # 优化UI更新 + if hasattr(self, '_last_percent') and self._last_percent == percent and percent < 100: + # 如果百分比没变,只更新速度和ETA信息 + self.stats_label.setText(f"速度: {speed} | 线程: {threads} | 剩余时间: {eta}") + else: + # 百分比变化或初次更新,更新所有信息 + self._last_percent = percent + self.game_label.setText(f"正在下载 {game_version} 的补丁") + self.progress_bar.setValue(int(percent)) + self.stats_label.setText(f"速度: {speed} | 线程: {threads} | 剩余时间: {eta}") if percent == 100: self.pause_resume_button.setEnabled(False)