From 8b4dedc8c660ee49eae4f211d1242755187156c8 Mon Sep 17 00:00:00 2001 From: hyb-oyqq <1512383570@qq.com> Date: Mon, 4 Aug 2025 12:46:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=E4=B8=BB?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=92=8C=E4=B8=8B=E8=BD=BD=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除冗余注释,简化代码可读性。 - 更新隐私协议管理器的异常处理逻辑,确保用户体验流畅。 - 改进下载管理器中的下载流程,优化用户选择游戏的对话框逻辑。 - 调整下载线程设置,确保更高效的下载管理。 --- source/Main.py | 6 +- source/core/download_manager.py | 110 ++------------------------------ source/workers/download.py | 49 +++----------- source/workers/ip_optimizer.py | 61 +++++------------- 4 files changed, 30 insertions(+), 196 deletions(-) diff --git a/source/Main.py b/source/Main.py index 266bc6c..c089e3c 100644 --- a/source/Main.py +++ b/source/Main.py @@ -5,13 +5,11 @@ from core.privacy_manager import PrivacyManager from utils.logger import setup_logger if __name__ == "__main__": - # 初始化日志 logger = setup_logger("main") logger.info("应用启动") app = QApplication(sys.argv) - # 初始化隐私协议管理器 try: privacy_manager = PrivacyManager() except Exception as e: @@ -23,12 +21,10 @@ if __name__ == "__main__": ) sys.exit(1) - # 显示隐私协议对话框 if not privacy_manager.show_privacy_dialog(): logger.info("用户未同意隐私协议,程序退出") - sys.exit(0) # 如果用户不同意隐私协议,退出程序 + sys.exit(0) - # 用户已同意隐私协议,继续启动程序 logger.info("隐私协议已同意,启动主程序") window = MainWindow() window.show() diff --git a/source/core/download_manager.py b/source/core/download_manager.py index 80c7107..143e54d 100644 --- a/source/core/download_manager.py +++ b/source/core/download_manager.py @@ -3,7 +3,7 @@ import requests import json from collections import deque from urllib.parse import urlparse -import re # Added for recursive search +import re from PySide6 import QtWidgets, QtCore from PySide6.QtCore import Qt @@ -24,16 +24,14 @@ class DownloadManager: main_window: 主窗口实例,用于访问UI和状态 """ self.main_window = main_window - self.main_window.APP_NAME = APP_NAME # 为了让子模块能够访问APP_NAME + self.main_window.APP_NAME = APP_NAME self.selected_folder = "" self.download_queue = deque() self.current_download_thread = None self.hosts_manager = HostsManager() - # 添加下载线程级别 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) @@ -49,17 +47,14 @@ class DownloadManager: ) return - # 将按钮文本设为安装中状态 self.main_window.ui.start_install_text.setText("正在安装") - # 禁用整个主窗口,防止用户操作 self.main_window.setEnabled(False) self.download_action() def get_install_paths(self): """获取所有游戏版本的安装路径""" - # 使用改进的目录识别功能 game_dirs = self.main_window.game_detector.identify_game_directories_improved(self.selected_folder) install_paths = {} @@ -67,7 +62,6 @@ class DownloadManager: for game, info in GAME_INFO.items(): if game in game_dirs: - # 如果找到了游戏目录,使用它 game_dir = game_dirs[game] install_path = os.path.join(game_dir, os.path.basename(info["install_path"])) install_paths[game] = install_path @@ -96,7 +90,6 @@ class DownloadManager: print("--- Using pre-fetched cloud config ---") config_data = self.main_window.cloud_config else: - # 如果没有预加载的配置,则同步获取 headers = {"User-Agent": UA} response = requests.get(CONFIG_URL, headers=headers, timeout=10) response.raise_for_status() @@ -108,7 +101,6 @@ class DownloadManager: if self.is_debug_mode(): print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}") - # 统一处理URL提取,确保返回扁平化的字典 urls = {} for i in range(4): key = f"vol.{i+1}.data" @@ -118,7 +110,6 @@ class DownloadManager: if "after.data" in config_data and "url" in config_data["after.data"]: urls["after"] = config_data["after.data"]["url"] - # 检查是否成功提取了所有URL if len(urls) != 5: missing_keys_map = { f"vol{i+1}": f"vol.{i+1}.data" for i in range(4) @@ -169,42 +160,32 @@ class DownloadManager: def download_action(self): """开始下载流程""" - # 主窗口在file_dialog中已被禁用 - - # 清空下载历史记录 self.main_window.download_queue_history = [] - # 使用改进的目录识别功能 game_dirs = self.main_window.game_detector.identify_game_directories_improved(self.selected_folder) debug_mode = self.is_debug_mode() if debug_mode: print(f"DEBUG: 开始下载流程, 识别到 {len(game_dirs)} 个游戏目录") - # 检查是否找到任何游戏目录 if not game_dirs: if debug_mode: print("DEBUG: 未识别到任何游戏目录,设置目录未找到错误") - # 设置特定的错误类型,以便在按钮点击处理中区分处理 self.main_window.last_error_message = "directory_not_found" QtWidgets.QMessageBox.warning( self.main_window, f"目录错误 - {APP_NAME}", "\n未能识别到任何游戏目录。\n\n请确认您选择的是游戏的上级目录,并且该目录中包含NEKOPARA系列游戏文件夹。\n" ) - # 恢复主窗口 self.main_window.setEnabled(True) self.main_window.ui.start_install_text.setText("开始安装") return - # 显示哈希检查窗口 self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre") - # 执行预检查,先判断哪些游戏版本已安装了补丁 install_paths = self.get_install_paths() self.main_window.hash_thread = self.main_window.create_hash_thread("pre", install_paths) - # 使用lambda连接,传递game_dirs参数 self.main_window.hash_thread.pre_finished.connect( lambda updated_status: self.on_pre_hash_finished_with_dirs(updated_status, game_dirs) ) @@ -224,10 +205,8 @@ class DownloadManager: debug_mode = self.is_debug_mode() - # 临时启用窗口以显示选择对话框 self.main_window.setEnabled(True) - # 获取可安装的游戏版本列表(尚未安装补丁的版本) installable_games = [] already_installed_games = [] for game_version, game_dir in game_dirs.items(): @@ -240,34 +219,26 @@ class DownloadManager: print(f"DEBUG: {game_version} 未安装补丁,可以安装") installable_games.append(game_version) - # 显示状态消息 status_message = "" if already_installed_games: status_message += f"已安装补丁的游戏:\n{chr(10).join(already_installed_games)}\n\n" if not installable_games: - # 如果没有可安装的游戏 QtWidgets.QMessageBox.information( self.main_window, f"信息 - {APP_NAME}", f"\n所有检测到的游戏都已安装补丁。\n\n{status_message}" ) - # 恢复主窗口 self.main_window.setEnabled(True) self.main_window.ui.start_install_text.setText("开始安装") return - # 如果有可安装的游戏版本,让用户选择 - from PySide6.QtWidgets import QInputDialog, QListWidget, QVBoxLayout, QDialog, QLabel, QPushButton, QAbstractItemView, QHBoxLayout - - # 创建自定义选择对话框 dialog = QDialog(self.main_window) dialog.setWindowTitle("选择要安装的游戏") dialog.resize(400, 300) layout = QVBoxLayout(dialog) - # 先显示已安装补丁的游戏 if already_installed_games: already_installed_label = QLabel("已安装补丁的游戏:", dialog) already_installed_label.setFont(QFont(already_installed_label.font().family(), already_installed_label.font().pointSize(), QFont.Bold)) @@ -276,27 +247,22 @@ class DownloadManager: already_installed_list = QLabel(chr(10).join(already_installed_games), dialog) layout.addWidget(already_installed_list) - # 添加一些间距 layout.addSpacing(10) - # 添加"请选择你需要安装补丁的游戏"的标签 info_label = QLabel("请选择你需要安装补丁的游戏:", dialog) info_label.setFont(QFont(info_label.font().family(), info_label.font().pointSize(), QFont.Bold)) layout.addWidget(info_label) - # 添加列表控件 list_widget = QListWidget(dialog) - list_widget.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) # 允许多选 + list_widget.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) for game in installable_games: list_widget.addItem(game) layout.addWidget(list_widget) - # 添加全选按钮 select_all_btn = QPushButton("全选", dialog) select_all_btn.clicked.connect(lambda: list_widget.selectAll()) layout.addWidget(select_all_btn) - # 添加确定和取消按钮 buttons_layout = QHBoxLayout() ok_button = QPushButton("确定", dialog) cancel_button = QPushButton("取消", dialog) @@ -304,50 +270,39 @@ class DownloadManager: buttons_layout.addWidget(cancel_button) layout.addLayout(buttons_layout) - # 连接按钮事件 ok_button.clicked.connect(dialog.accept) cancel_button.clicked.connect(dialog.reject) - # 显示对话框并等待用户选择 result = dialog.exec() if result != QDialog.DialogCode.Accepted or list_widget.selectedItems() == []: - # 用户取消或未选择任何游戏 self.main_window.setEnabled(True) self.main_window.ui.start_install_text.setText("开始安装") return - # 获取用户选择的游戏 selected_games = [item.text() for item in list_widget.selectedItems()] if debug_mode: print(f"DEBUG: 用户选择了以下游戏进行安装: {selected_games}") - # 过滤game_dirs,只保留选中的游戏 selected_game_dirs = {game: game_dirs[game] for game in selected_games if game in game_dirs} - # 重新禁用窗口 self.main_window.setEnabled(False) - # 获取下载配置 config = self.get_download_url() if not config: QtWidgets.QMessageBox.critical( self.main_window, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n" ) - # 网络故障时,恢复主窗口 self.main_window.setEnabled(True) self.main_window.ui.start_install_text.setText("开始安装") return - # 填充下载队列,传入选定的游戏目录 self._fill_download_queue(config, selected_game_dirs) - # 如果没有需要下载的内容,直接进行最终校验 if not self.download_queue: self.main_window.after_hash_compare() return - # 询问是否使用Cloudflare加速 self._show_cloudflare_option() def _fill_download_queue(self, config, game_dirs): @@ -357,10 +312,8 @@ class DownloadManager: config: 包含下载URL的配置字典 game_dirs: 包含游戏文件夹路径的字典 """ - # 清空现有队列 self.download_queue.clear() - # 创建下载历史记录列表,用于跟踪本次安装的游戏 if not hasattr(self.main_window, 'download_queue_history'): self.main_window.download_queue_history = [] @@ -368,15 +321,12 @@ class DownloadManager: if debug_mode: print(f"DEBUG: 填充下载队列, 游戏目录: {game_dirs}") - # 添加nekopara 1-4 for i in range(1, 5): game_version = f"NEKOPARA Vol.{i}" - # 只处理game_dirs中包含的游戏版本(如果用户选择了特定版本) if game_version in game_dirs and not self.main_window.installed_status.get(game_version, False): url = config.get(f"vol{i}") if not url: continue - # 获取识别到的游戏文件夹路径 game_folder = game_dirs[game_version] if debug_mode: print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}") @@ -384,16 +334,12 @@ class DownloadManager: _7z_path = os.path.join(PLUGIN, f"vol.{i}.7z") plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"]) self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path)) - # 记录到下载历史 self.main_window.download_queue_history.append(game_version) - # 添加nekopara after game_version = "NEKOPARA After" - # 只处理game_dirs中包含的游戏版本(如果用户选择了特定版本) if game_version in game_dirs and not self.main_window.installed_status.get(game_version, False): url = config.get("after") if url: - # 获取识别到的游戏文件夹路径 game_folder = game_dirs[game_version] if debug_mode: print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}") @@ -401,28 +347,23 @@ class DownloadManager: _7z_path = os.path.join(PLUGIN, "after.7z") plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"]) self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path)) - # 记录到下载历史 self.main_window.download_queue_history.append(game_version) def _show_cloudflare_option(self): """显示Cloudflare加速选择对话框""" - # 首先检查队列中第一个URL的域名是否已在hosts中有优选IP if self.download_queue: first_url = self.download_queue[0][0] hostname = urlparse(first_url).hostname - # 检查hosts文件中是否已有该域名的IP记录 if hostname: existing_ips = self.cloudflare_optimizer.hosts_manager.get_hostname_entries(hostname) if existing_ips: print(f"发现hosts文件中已有域名 {hostname} 的优选IP记录,跳过询问直接使用") - # 设置标记为已优选完成 self.cloudflare_optimizer.optimization_done = True self.cloudflare_optimizer.countdown_finished = True - # 尝试获取现有的IPv4和IPv6地址 ipv4_entries = [ip for ip in existing_ips if ':' not in ip] ipv6_entries = [ip for ip in existing_ips if ':' in ip] @@ -431,21 +372,17 @@ class DownloadManager: if ipv6_entries: self.cloudflare_optimizer.optimized_ipv6 = ipv6_entries[0] - # 保存当前URL供CloudflareOptimizer使用 self.main_window.current_url = first_url - # 直接开始下载,跳过询问 self.next_download_task() return - # 临时启用窗口以显示对话框 self.main_window.setEnabled(True) msg_box = QtWidgets.QMessageBox(self.main_window) msg_box.setWindowTitle(f"下载优化 - {APP_NAME}") msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。\n如您的杀毒软件提醒有软件正在修改hosts文件,请注意放行。") - # 设置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) @@ -464,37 +401,28 @@ class DownloadManager: clicked_button = msg_box.clickedButton() if clicked_button == cancel_button: - # 用户取消了安装,保持主窗口启用 self.main_window.setEnabled(True) self.main_window.ui.start_install_text.setText("开始安装") - self.download_queue.clear() # 清空下载队列 + self.download_queue.clear() return - # 用户点击了继续按钮,重新禁用主窗口 self.main_window.setEnabled(False) use_optimization = clicked_button == yes_button if use_optimization and not self.cloudflare_optimizer.is_optimization_done(): first_url = self.download_queue[0][0] - # 保存当前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 check_optimization_status(self): """检查IP优化状态并继续下载流程""" - # 必须同时满足:优化已完成且倒计时已结束 if self.cloudflare_optimizer.is_optimization_done() and self.cloudflare_optimizer.is_countdown_finished(): self.next_download_task() else: - # 否则,继续等待100ms后再次检查 QtCore.QTimer.singleShot(100, self.check_optimization_status) def next_download_task(self): @@ -503,11 +431,9 @@ class DownloadManager: self.main_window.after_hash_compare() return - # 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务 if self.download_task_manager.current_download_thread and self.download_task_manager.current_download_thread.isRunning(): return - # 获取下一个下载任务并开始 url, game_folder, game_version, _7z_path, plugin_path = self.download_queue.popleft() self.download_setting(url, game_folder, game_version, _7z_path, plugin_path) @@ -521,7 +447,6 @@ class DownloadManager: _7z_path: 7z文件保存路径 plugin_path: 插件路径 """ - # 使用改进的目录识别获取安装路径 install_paths = self.get_install_paths() debug_mode = self.is_debug_mode() @@ -529,11 +454,8 @@ class DownloadManager: print(f"DEBUG: 准备下载游戏 {game_version}") print(f"DEBUG: 游戏文件夹: {game_folder}") - # 游戏可执行文件已在填充下载队列时验证过,不需要再次检查 - # 因为game_folder是从已验证的game_dirs中获取的 game_exe_exists = True - # 检查游戏是否已安装 if ( not game_exe_exists or self.main_window.installed_status[game_version] @@ -546,20 +468,16 @@ class DownloadManager: self.next_download_task() return - # 创建进度窗口并开始下载 self.main_window.progress_window = self.main_window.create_progress_window() - # 从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,将使用默认线路。") - # 使用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): """下载完成后的处理 @@ -572,18 +490,15 @@ class DownloadManager: _7z_path: 7z文件保存路径 plugin_path: 插件路径 """ - # 关闭进度窗口 if self.main_window.progress_window and self.main_window.progress_window.isVisible(): self.main_window.progress_window.reject() self.main_window.progress_window = None - # 处理下载失败 if not success: print(f"--- Download Failed: {game_version} ---") print(error) print("------------------------------------") - # 临时启用窗口以显示对话框 self.main_window.setEnabled(True) msg_box = QtWidgets.QMessageBox(self.main_window) @@ -597,23 +512,18 @@ class DownloadManager: msg_box.exec() clicked_button = msg_box.clickedButton() - # 处理用户选择 if clicked_button == retry_button: - # 重试,重新禁用窗口 self.main_window.setEnabled(False) self.download_setting(url, game_folder, game_version, _7z_path, plugin_path) elif clicked_button == next_button: - # 继续下一个,重新禁用窗口 self.main_window.setEnabled(False) self.next_download_task() else: - # 结束,保持窗口启用 self.on_download_stopped() return - # 下载成功,使用ExtractionHandler开始解压缩 self.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version) - # extraction_handler的回调会处理下一步操作 + self.extraction_handler.extraction_finished.connect(self.on_extraction_finished) def on_extraction_finished(self, continue_download): """解压完成后的回调,决定是否继续下载队列 @@ -622,45 +532,35 @@ class DownloadManager: continue_download: 是否继续下载队列中的下一个任务 """ if continue_download: - # 继续下一个下载任务 self.next_download_task() else: - # 清空剩余队列并显示结果 self.download_queue.clear() self.main_window.show_result() def on_download_stopped(self): """当用户点击停止按钮或选择结束时调用的函数""" - # 停止IP优化线程 self.cloudflare_optimizer.stop_optimization() - # 停止当前可能仍在运行的下载线程 self.download_task_manager.stop_download() - # 清空下载队列,因为用户决定停止 self.download_queue.clear() - # 确保进度窗口已关闭 if hasattr(self.main_window, 'progress_window') and self.main_window.progress_window: if self.main_window.progress_window.isVisible(): self.main_window.progress_window.reject() self.main_window.progress_window = None - # 退出应用程序 print("下载已全部停止。") - # 恢复主窗口状态 self.main_window.setEnabled(True) self.main_window.ui.start_install_text.setText("开始安装") - # 显示取消安装的消息 QtWidgets.QMessageBox.information( self.main_window, f"已取消 - {APP_NAME}", "\n已成功取消安装进程。\n" ) - # 以下方法委托给DownloadTaskManager def get_download_thread_count(self): """获取当前下载线程设置对应的线程数""" return self.download_task_manager.get_download_thread_count() diff --git a/source/workers/download.py b/source/workers/download.py index 4209ba0..cf10873 100644 --- a/source/workers/download.py +++ b/source/workers/download.py @@ -12,7 +12,6 @@ import signal import ctypes import time -# Windows API常量和函数 if sys.platform == 'win32': kernel32 = ctypes.windll.kernel32 PROCESS_ALL_ACCESS = 0x1F0FFF @@ -30,7 +29,6 @@ if sys.platform == 'win32': ('dwFlags', ctypes.c_ulong) ] -# 下载线程类 class DownloadThread(QThread): progress = Signal(dict) finished = Signal(bool, str) @@ -49,7 +47,6 @@ class DownloadThread(QThread): if self.process and self.process.poll() is None: self._is_running = False try: - # 使用 taskkill 强制终止进程及其子进程,并隐藏窗口 subprocess.run(['taskkill', '/F', '/T', '/PID', str(self.process.pid)], check=True, creationflags=subprocess.CREATE_NO_WINDOW) except (subprocess.CalledProcessError, FileNotFoundError) as e: print(f"停止下载进程时出错: {e}") @@ -81,13 +78,11 @@ class DownloadThread(QThread): if not self._is_paused and self.process and self.process.poll() is None: try: if sys.platform == 'win32': - # 获取所有线程 self.threads = self._get_process_threads(self.process.pid) if not self.threads: print("未找到可暂停的线程") return False - # 暂停所有线程 for thread_id in self.threads: h_thread = kernel32.OpenThread(THREAD_SUSPEND_RESUME, False, thread_id) if h_thread: @@ -98,7 +93,6 @@ class DownloadThread(QThread): print(f"下载进程已暂停: PID {self.process.pid}, 线程数: {len(self.threads)}") return True else: - # 在Unix系统上使用SIGSTOP os.kill(self.process.pid, signal.SIGSTOP) self._is_paused = True print(f"下载进程已暂停: PID {self.process.pid}") @@ -113,7 +107,6 @@ class DownloadThread(QThread): if self._is_paused and self.process and self.process.poll() is None: try: if sys.platform == 'win32': - # 恢复所有线程 for thread_id in self.threads: h_thread = kernel32.OpenThread(THREAD_SUSPEND_RESUME, False, thread_id) if h_thread: @@ -124,7 +117,6 @@ class DownloadThread(QThread): print(f"下载进程已恢复: PID {self.process.pid}, 线程数: {len(self.threads)}") return True else: - # 在Unix系统上使用SIGCONT os.kill(self.process.pid, signal.SIGCONT) self._is_paused = False print(f"下载进程已恢复: PID {self.process.pid}") @@ -155,21 +147,16 @@ class DownloadThread(QThread): aria2c_path, ] - # 获取主窗口的下载管理器对象 thread_count = 64 # 默认值 if hasattr(self.parent(), 'download_manager'): - # 从下载管理器获取线程数设置 thread_count = self.parent().download_manager.get_download_thread_count() - # 检查是否启用IPv6支持 ipv6_enabled = False if hasattr(self.parent(), 'config'): ipv6_enabled = self.parent().config.get("ipv6_enabled", False) - # 打印IPv6状态 print(f"IPv6支持状态: {ipv6_enabled}") - # 将所有的优化参数应用于每个下载任务 command.extend([ '--dir', download_dir, '--out', file_name, @@ -196,36 +183,31 @@ class DownloadThread(QThread): '--auto-file-renaming=false', '--allow-overwrite=true', '--split=128', - f'--max-connection-per-server={thread_count}', # 使用动态的线程数 - '--min-split-size=1M', # 减小最小分片大小 - '--optimize-concurrent-downloads=true', # 优化并发下载 - '--file-allocation=none', # 禁用文件预分配加快开始 - '--async-dns=true', # 使用异步DNS + f'--max-connection-per-server={thread_count}', + '--min-split-size=1M', + '--optimize-concurrent-downloads=true', + '--file-allocation=none', + '--async-dns=true', ]) - # 根据IPv6设置决定是否禁用IPv6 if not ipv6_enabled: command.append('--disable-ipv6=true') print("已禁用IPv6支持") else: print("已启用IPv6支持") - # 证书验证现在总是需要,因为我们依赖hosts文件 command.append('--check-certificate=false') command.append(self.url) - # 打印将要执行的命令,用于调试 print(f"即将执行的 Aria2c 命令: {' '.join(command)}") creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='replace', creationflags=creation_flags) - # 正则表达式用于解析aria2c的输出 - # 例如: #1 GID[...]( 5%) CN:1 DL:10.5MiB/s ETA:1m30s + # 正则表达式用于解析aria2c的输出: #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秒最多更新一次 @@ -239,11 +221,10 @@ class DownloadThread(QThread): break full_output.append(line) - print(line.strip()) # 在控制台输出实时日志 + print(line.strip()) match = progress_pattern.search(line) if match: - # 检查是否达到更新间隔 current_time = time.time() if current_time - last_update_time >= update_interval: percent = int(match.group(1)) @@ -251,7 +232,6 @@ class DownloadThread(QThread): speed = match.group(3) eta = match.group(4) - # 直接发送进度信号,不使用invokeMethod self.progress.emit({ "game": self.game_version, "percent": percent, @@ -264,7 +244,7 @@ class DownloadThread(QThread): return_code = self.process.wait() - if not self._is_running: # 如果是手动停止的 + if not self._is_running: self.finished.emit(False, "下载已手动停止。") return @@ -285,7 +265,6 @@ class DownloadThread(QThread): if self._is_running: self.finished.emit(False, f"\n下载时发生未知错误\n\n【错误信息】: {e}\n") -# 下载进度窗口类 class ProgressWindow(QDialog): def __init__(self, parent=None): super(ProgressWindow, self).__init__(parent) @@ -300,18 +279,14 @@ class ProgressWindow(QDialog): self.progress_bar.setValue(0) self.stats_label = QLabel("速度: - | 线程: - | 剩余时间: -") - # 创建按钮布局 button_layout = QHBoxLayout() - # 创建暂停/恢复按钮 self.pause_resume_button = QtWidgets.QPushButton("暂停下载") self.pause_resume_button.setToolTip("暂停或恢复下载") - # 创建停止按钮 self.stop_button = QtWidgets.QPushButton("取消下载") self.stop_button.setToolTip("取消整个下载过程") - # 添加按钮到按钮布局 button_layout.addWidget(self.pause_resume_button) button_layout.addWidget(self.stop_button) @@ -321,10 +296,7 @@ class ProgressWindow(QDialog): layout.addLayout(button_layout) self.setLayout(layout) - # 设置暂停/恢复状态 self.is_paused = False - - # 添加最后进度记录,用于优化UI更新 self._last_percent = -1 def update_pause_button_state(self, is_paused): @@ -346,16 +318,12 @@ class ProgressWindow(QDialog): threads = data.get("threads", "-") eta = data.get("eta", "-") - # 清除ETA值中可能存在的"]"符号 if isinstance(eta, str): eta = eta.replace("]", "") - # 优化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)) @@ -368,5 +336,4 @@ class ProgressWindow(QDialog): QTimer.singleShot(1500, self.accept) def closeEvent(self, event): - # 覆盖默认的关闭事件,防止用户通过其他方式关闭窗口 event.ignore() \ No newline at end of file diff --git a/source/workers/ip_optimizer.py b/source/workers/ip_optimizer.py index 654f6c5..5ef704f 100644 --- a/source/workers/ip_optimizer.py +++ b/source/workers/ip_optimizer.py @@ -30,15 +30,14 @@ class IpOptimizer: ip_txt_path = resource_path("ip.txt") - # 正确的参数设置,根据cfst帮助文档 command = [ cst_path, - "-n", "1000", # 延迟测速线程数 (默认200) - "-p", "1", # 显示结果数量 (默认10个) - "-url", url, # 指定测速地址 - "-f", ip_txt_path, # IP文件 - "-dd", # 禁用下载测速,按延迟排序 - "-o"," " # 不写入结果文件 + "-n", "1000", # 延迟测速线程数 + "-p", "1", # 显示结果数量 + "-url", url, + "-f", ip_txt_path, + "-dd", # 禁用下载测速 + "-o"," " # 不写入结果文件 ] creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 @@ -57,11 +56,9 @@ class IpOptimizer: bufsize=0 ) - # 更新正则表达式以匹配cfst输出中的IP格式 # 匹配格式: IP地址在行首,后面跟着一些数字和文本 ip_pattern = re.compile(r'^(\d+\.\d+\.\d+\.\d+)\s+.*') - # 标记是否已经找到结果表头和完成标记 found_header = False found_completion = False @@ -72,7 +69,7 @@ class IpOptimizer: optimal_ip = None timeout_counter = 0 - max_timeout = 300 # 增加超时时间到5分钟 + max_timeout = 300 # 超时时间5分钟 while True: if self.process.poll() is not None: @@ -98,35 +95,29 @@ class IpOptimizer: if cleaned_line: print(cleaned_line) - # 检测结果表头 if "IP 地址" in cleaned_line and "平均延迟" in cleaned_line: print("检测到IP结果表头,准备获取IP地址...") found_header = True continue - # 检测完成标记 if "完整测速结果已写入" in cleaned_line or "按下 回车键 或 Ctrl+C 退出" in cleaned_line: print("检测到测速完成信息") found_completion = True - # 如果已经找到了IP,可以退出了 if optimal_ip: break - # 已找到表头后,尝试匹配IP地址行 if found_header: match = ip_pattern.search(cleaned_line) - if match and not optimal_ip: # 只保存第一个匹配的IP(最优IP) + if match and not optimal_ip: optimal_ip = match.group(1) print(f"找到最优 IP: {optimal_ip}") - # 找到最优IP后立即退出循环,不等待完成标记 break except Exception as e: print(f"读取输出时发生错误: {e}") break - # 确保完全读取输出后再发送退出信号 if self.process and self.process.poll() is None: try: if self.process.stdin and not self.process.stdin.closed: @@ -166,14 +157,13 @@ class IpOptimizer: print(f"错误: ipv6.txt 未在资源路径中找到。") return None - # 正确的参数设置,根据cfst帮助文档 command = [ cst_path, - "-n", "1000", # 延迟测速线程数,IPv6测试线程稍少 - "-p", "1", # 显示结果数量 (默认10个) - "-url", url, # 指定测速地址 - "-f", ipv6_txt_path, # IPv6文件 - "-dd", # 禁用下载测速,按延迟排序 + "-n", "1000", # 延迟测速线程数 + "-p", "1", # 显示结果数量 + "-url", url, + "-f", ipv6_txt_path, + "-dd", # 禁用下载测速 "-o", " " # 不写入结果文件 ] @@ -193,11 +183,9 @@ class IpOptimizer: bufsize=0 ) - # 更新正则表达式以匹配cfst输出中的IPv6格式 - # IPv6格式更加复杂,可能有多种表示形式 + # IPv6格式可能有多种表示形式 ipv6_pattern = re.compile(r'^([0-9a-fA-F:]+)\s+.*') - # 标记是否已经找到结果表头和完成标记 found_header = False found_completion = False @@ -208,7 +196,7 @@ class IpOptimizer: optimal_ipv6 = None timeout_counter = 0 - max_timeout = 300 # 增加超时时间到5分钟 + max_timeout = 300 # 超时时间5分钟 while True: if self.process.poll() is not None: @@ -234,35 +222,29 @@ class IpOptimizer: if cleaned_line: print(cleaned_line) - # 检测结果表头 if "IP 地址" in cleaned_line and "平均延迟" in cleaned_line: print("检测到IPv6结果表头,准备获取IPv6地址...") found_header = True continue - # 检测完成标记 if "完整测速结果已写入" in cleaned_line or "按下 回车键 或 Ctrl+C 退出" in cleaned_line: print("检测到IPv6测速完成信息") found_completion = True - # 如果已经找到了IPv6,可以退出了 if optimal_ipv6: break - # 已找到表头后,尝试匹配IPv6地址行 if found_header: match = ipv6_pattern.search(cleaned_line) - if match and not optimal_ipv6: # 只保存第一个匹配的IPv6(最优IPv6) + if match and not optimal_ipv6: optimal_ipv6 = match.group(1) print(f"找到最优 IPv6: {optimal_ipv6}") - # 找到最优IPv6后立即退出循环,不等待完成标记 break except Exception as e: print(f"读取输出时发生错误: {e}") break - # 确保完全读取输出后再发送退出信号 if self.process and self.process.poll() is None: try: if self.process.stdin and not self.process.stdin.closed: @@ -324,14 +306,3 @@ class IpOptimizerThread(QThread): def stop(self): self.optimizer.stop() - - -if __name__ == '__main__': - # 用于直接测试此模块 - test_url = "https://speed.cloudflare.com/__down?during=download&bytes=104857600" - optimizer = IpOptimizer() - ip = optimizer.get_optimal_ip(test_url) - if ip: - print(f"为 {test_url} 找到的最优 IP 是: {ip}") - else: - print(f"未能为 {test_url} 找到最优 IP。") \ No newline at end of file