diff --git a/source/core/ipv6_manager.py b/source/core/ipv6_manager.py new file mode 100644 index 0000000..cff1dd6 --- /dev/null +++ b/source/core/ipv6_manager.py @@ -0,0 +1,345 @@ +import os +import sys +import time +import subprocess +import urllib.request +import ssl +import threading +from PySide6.QtCore import QObject, Signal +from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QTextEdit, QProgressBar, QMessageBox + +from data.config import APP_NAME +from utils import msgbox_frame + + +class IPv6Manager: + """管理IPv6相关功能的类""" + + def __init__(self, main_window): + """初始化IPv6管理器 + + Args: + main_window: 主窗口实例,用于显示对话框和访问配置 + """ + self.main_window = main_window + self.config = getattr(main_window, 'config', {}) + + def check_ipv6_availability(self): + """检查IPv6是否可用 + + 通过访问IPv6专用图片URL测试IPv6连接 + + Returns: + bool: IPv6是否可用 + """ + import urllib.request + import time + + print("开始检测IPv6可用性...") + + try: + # 获取IPv6测试请求 + ipv6_test_url, req, context = self._get_ipv6_test_request() + + # 设置3秒超时,避免长时间等待 + start_time = time.time() + with urllib.request.urlopen(req, timeout=3, context=context) as response: + # 读取图片数据 + image_data = response.read() + + # 检查是否成功 + if response.status == 200 and len(image_data) > 0: + elapsed = time.time() - start_time + print(f"IPv6测试成功! 用时: {elapsed:.2f}秒") + return True + else: + print(f"IPv6测试失败: 状态码 {response.status}") + return False + except Exception as e: + print(f"IPv6测试失败: {e}") + return False + + def _get_ipv6_test_request(self): + """获取IPv6测试请求 + + Returns: + tuple: (测试URL, 请求对象, SSL上下文) + """ + import urllib.request + import ssl + + # IPv6测试URL - 这是一个只能通过IPv6访问的资源 + ipv6_test_url = "https://ipv6.testipv6.cn/images-nc/knob_green.png?&testdomain=www.test-ipv6.com&testname=sites" + + # 创建SSL上下文 + context = ssl._create_unverified_context() + + # 创建请求并添加常见的HTTP头 + req = urllib.request.Request(ipv6_test_url) + req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') + req.add_header('Accept', 'image/webp,image/apng,image/*,*/*;q=0.8') + + return ipv6_test_url, req, context + + def get_ipv6_address(self): + """获取公网IPv6地址 + + Returns: + str: IPv6地址,如果失败则返回None + """ + try: + # 使用curl命令获取IPv6地址 + process = subprocess.Popen( + ["curl", "-6", "6.ipw.cn"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding='utf-8', + errors='replace', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + + # 设置超时 + timeout = 5 # 5秒超时 + start_time = time.time() + while process.poll() is None and (time.time() - start_time) < timeout: + time.sleep(0.1) + + # 如果进程仍在运行,则强制终止 + if process.poll() is None: + process.terminate() + print("获取IPv6地址超时") + return None + + stdout, stderr = process.communicate() + + if process.returncode == 0 and stdout.strip(): + ipv6_address = stdout.strip() + print(f"获取到IPv6地址: {ipv6_address}") + return ipv6_address + else: + print("未能获取到IPv6地址") + if stderr: + print(f"错误信息: {stderr}") + return None + + except Exception as e: + print(f"获取IPv6地址失败: {e}") + return None + + def show_ipv6_details(self): + """显示IPv6连接详情""" + class SignalEmitter(QObject): + update_signal = Signal(str) + complete_signal = Signal(bool, float) + + # 创建对话框 + dialog = QDialog(self.main_window) + dialog.setWindowTitle(f"IPv6连接测试 - {APP_NAME}") + dialog.resize(500, 300) + + # 创建布局 + layout = QVBoxLayout(dialog) + + # 创建状态标签 + status_label = QLabel("正在测试IPv6连接...", dialog) + layout.addWidget(status_label) + + # 创建进度条 + progress = QProgressBar(dialog) + progress.setRange(0, 0) # 不确定进度 + layout.addWidget(progress) + + # 创建结果文本框 + result_text = QTextEdit(dialog) + result_text.setReadOnly(True) + layout.addWidget(result_text) + + # 创建关闭按钮 + close_button = QPushButton("关闭", dialog) + close_button.clicked.connect(dialog.accept) + close_button.setEnabled(False) # 测试完成前禁用 + layout.addWidget(close_button) + + # 信号发射器 + signal_emitter = SignalEmitter() + + # 连接信号 + signal_emitter.update_signal.connect( + lambda text: result_text.append(text) + ) + + def on_test_complete(success, elapsed_time): + # 停止进度条动画 + progress.setRange(0, 100) + progress.setValue(100 if success else 0) + + # 更新状态 + if success: + status_label.setText(f"IPv6连接测试完成: 可用 (用时: {elapsed_time:.2f}秒)") + else: + status_label.setText("IPv6连接测试完成: 不可用") + + # 启用关闭按钮 + close_button.setEnabled(True) + + signal_emitter.complete_signal.connect(on_test_complete) + + # 测试函数 + def test_ipv6(): + try: + signal_emitter.update_signal.emit("正在测试IPv6连接,请稍候...") + + # 先进行标准的IPv6连接测试 + signal_emitter.update_signal.emit("正在进行标准IPv6连接测试...") + + # 使用IPv6测试URL + ipv6_test_url, req, context = self._get_ipv6_test_request() + ipv6_connected = False + ipv6_test_elapsed_time = 0 + + try: + # 设置5秒超时 + start_time = time.time() + signal_emitter.update_signal.emit(f"开始连接: {ipv6_test_url}") + + # 尝试下载图片 + with urllib.request.urlopen(req, timeout=5, context=context) as response: + image_data = response.read() + + # 计算耗时 + elapsed_time = time.time() - start_time + ipv6_test_elapsed_time = elapsed_time + + # 检查是否成功 + if response.status == 200 and len(image_data) > 0: + ipv6_connected = True + signal_emitter.update_signal.emit(f"✓ 成功! 已下载 {len(image_data)} 字节") + signal_emitter.update_signal.emit(f"✓ 响应时间: {elapsed_time:.2f}秒") + else: + signal_emitter.update_signal.emit(f"✗ 失败: 状态码 {response.status}") + signal_emitter.update_signal.emit("\n结论: 您的网络不支持IPv6连接 ✗") + signal_emitter.complete_signal.emit(False, 0) + return + + except Exception as e: + signal_emitter.update_signal.emit(f"✗ 连接失败: {e}") + signal_emitter.update_signal.emit("\n结论: 您的网络不支持IPv6连接 ✗") + signal_emitter.complete_signal.emit(False, 0) + return + + # 如果IPv6连接测试成功,再尝试获取公网IPv6地址 + if ipv6_connected: + signal_emitter.update_signal.emit("\n正在获取您的公网IPv6地址...") + + try: + # 使用curl命令获取IPv6地址 + process = subprocess.Popen( + ["curl", "-6", "6.ipw.cn"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding='utf-8', + errors='replace', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + + # 设置超时 + timeout = 5 # 5秒超时 + start_time = time.time() + while process.poll() is None and (time.time() - start_time) < timeout: + time.sleep(0.1) + + # 如果进程仍在运行,则强制终止 + if process.poll() is None: + process.terminate() + signal_emitter.update_signal.emit("✗ 获取IPv6地址超时") + else: + stdout, stderr = process.communicate() + + if process.returncode == 0 and stdout.strip(): + ipv6_address = stdout.strip() + signal_emitter.update_signal.emit(f"✓ 获取到的IPv6地址: {ipv6_address}") + else: + signal_emitter.update_signal.emit("✗ 未能获取到IPv6地址") + if stderr: + signal_emitter.update_signal.emit(f"错误信息: {stderr}") + + except Exception as e: + signal_emitter.update_signal.emit(f"✗ 获取IPv6地址失败: {e}") + + # 输出最终结论 + signal_emitter.update_signal.emit("\n结论: 您的网络支持IPv6连接 ✓") + signal_emitter.complete_signal.emit(True, ipv6_test_elapsed_time) + return + + except Exception as e: + signal_emitter.update_signal.emit(f"测试过程中出错: {e}") + signal_emitter.complete_signal.emit(False, 0) + + # 启动测试线程 + threading.Thread(target=test_ipv6, daemon=True).start() + + # 显示对话框 + dialog.exec() + + def toggle_ipv6_support(self, enabled): + """切换IPv6支持 + + Args: + enabled: 是否启用IPv6支持 + """ + print(f"Toggle IPv6 support: {enabled}") + + # 如果用户尝试启用IPv6,检查系统是否支持IPv6并发出警告 + if enabled: + # 先显示警告提示 + warning_msg_box = self._create_message_box( + "警告", + "\n目前IPv6支持功能仍在测试阶段,可能会发生意料之外的bug!\n\n您确定需要启用吗?\n", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + response = warning_msg_box.exec() + + # 如果用户选择不启用,直接返回 + if response != QMessageBox.StandardButton.Yes: + return False + + # 用户确认启用后,继续检查IPv6可用性 + ipv6_available = self.check_ipv6_availability() + + if not ipv6_available: + msg_box = self._create_message_box("错误", "\n未检测到可用的IPv6连接,无法启用IPv6支持。\n\n请确保您的网络环境支持IPv6且已正确配置。\n") + msg_box.exec() + return False + + # 保存设置到配置 + if self.config is not None: + self.config["ipv6_enabled"] = enabled + # 直接使用utils.save_config保存配置 + from utils import save_config + save_config(self.config) + + # 显示设置已保存的消息 + status = "启用" if enabled else "禁用" + msg_box = self._create_message_box("IPv6设置", f"\nIPv6支持已{status}。新的设置将在下一次下载时生效。\n") + msg_box.exec() + return True + + def _create_message_box(self, title, message, buttons=QMessageBox.StandardButton.Ok): + """创建统一风格的消息框 + + Args: + title: 消息框标题 + message: 消息内容 + buttons: 按钮类型,默认为确定按钮 + + Returns: + QMessageBox: 配置好的消息框实例 + """ + msg_box = msgbox_frame( + f"{title} - {APP_NAME}", + message, + buttons, + ) + return msg_box \ No newline at end of file diff --git a/source/core/ui_manager.py b/source/core/ui_manager.py index a03e415..9350580 100644 --- a/source/core/ui_manager.py +++ b/source/core/ui_manager.py @@ -6,6 +6,7 @@ import os from utils import load_base64_image, msgbox_frame, resource_path from data.config import APP_NAME, APP_VERSION, LOG_FILE +from core.ipv6_manager import IPv6Manager # 导入新的IPv6Manager类 class UIManager: def __init__(self, main_window): @@ -24,6 +25,9 @@ class UIManager: self.about_menu = None # 关于菜单 self.about_btn = None # 关于按钮 + # 获取主窗口的IPv6Manager实例 + self.ipv6_manager = getattr(main_window, 'ipv6_manager', None) + def setup_ui(self): """设置UI元素,包括窗口图标、标题和菜单""" # 设置窗口图标 @@ -286,7 +290,10 @@ class UIManager: # 添加IPv6检测按钮,用于显示详细信息 self.ipv6_test_action = QAction("测试IPv6连接", self.main_window) self.ipv6_test_action.setFont(menu_font) - self.ipv6_test_action.triggered.connect(self.show_ipv6_details) + if self.ipv6_manager: + self.ipv6_test_action.triggered.connect(self.ipv6_manager.show_ipv6_details) + else: + self.ipv6_test_action.triggered.connect(self.show_ipv6_manager_not_ready) # 创建IPv6支持子菜单 self.ipv6_submenu = QMenu("IPv6支持", self.main_window) @@ -294,7 +301,9 @@ class UIManager: self.ipv6_submenu.setStyleSheet(menu_style) # 检查IPv6是否可用 - ipv6_available = self._check_ipv6_availability() + ipv6_available = False + if self.ipv6_manager: + ipv6_available = self.ipv6_manager.check_ipv6_availability() if not ipv6_available: self.ipv6_action.setText("启用IPv6支持 (不可用)") @@ -317,7 +326,7 @@ class UIManager: self.ipv6_action.setChecked(ipv6_enabled) # 连接IPv6支持切换事件 - self.ipv6_action.triggered.connect(self.toggle_ipv6_support) + self.ipv6_action.triggered.connect(self._handle_ipv6_toggle) # 将选项添加到IPv6子菜单 self.ipv6_submenu.addAction(self.ipv6_action) @@ -332,9 +341,15 @@ class UIManager: self.clean_hosts_action.setFont(menu_font) self.clean_hosts_action.triggered.connect(self.clean_hosts_entries) + # 添加打开hosts文件选项 + self.open_hosts_action = QAction("打开hosts文件", self.main_window) + self.open_hosts_action.setFont(menu_font) + self.open_hosts_action.triggered.connect(self.open_hosts_file) + # 添加到hosts子菜单 self.hosts_submenu.addAction(self.restore_hosts_action) self.hosts_submenu.addAction(self.clean_hosts_action) + self.hosts_submenu.addAction(self.open_hosts_action) # 创建Debug开关选项 self.debug_action = QAction("Debug开关", self.main_window, checkable=True) @@ -396,169 +411,26 @@ class UIManager: self.dev_menu.addMenu(self.hosts_submenu) # 添加hosts文件选项子菜单 self.dev_menu.addMenu(self.ipv6_submenu) # 添加IPv6支持子菜单 - def _check_ipv6_availability(self): - """检查IPv6是否可用 + def _handle_ipv6_toggle(self, enabled): + """处理IPv6支持切换事件 - 通过访问IPv6专用图片URL测试IPv6连接 - - Returns: - bool: IPv6是否可用 + Args: + enabled: 是否启用IPv6支持 """ - import urllib.request - import ssl - import time - - print("开始检测IPv6可用性...") - - # IPv6测试URL - 这是一个只能通过IPv6访问的资源 - ipv6_test_url = "https://ipv6.testipv6.cn/images-nc/knob_green.png?&testdomain=www.test-ipv6.com&testname=sites" - - try: - # 设置3秒超时,避免长时间等待 - context = ssl._create_unverified_context() + if not self.ipv6_manager: + # 显示错误提示 + msg_box = self._create_message_box("错误", "\nIPv6管理器尚未初始化,请稍后再试。\n") + msg_box.exec() + # 恢复复选框状态 + self.ipv6_action.setChecked(not enabled) + return - # 创建请求并添加常见的HTTP头 - req = urllib.request.Request(ipv6_test_url) - req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') - req.add_header('Accept', 'image/webp,image/apng,image/*,*/*;q=0.8') - - # 尝试连接 - start_time = time.time() - with urllib.request.urlopen(req, timeout=3, context=context) as response: - # 读取图片数据 - image_data = response.read() - - # 检查是否成功 - if response.status == 200 and len(image_data) > 0: - elapsed = time.time() - start_time - print(f"IPv6测试成功! 用时: {elapsed:.2f}秒") - return True - else: - print(f"IPv6测试失败: 状态码 {response.status}") - return False - except Exception as e: - print(f"IPv6测试失败: {e}") - return False - - def show_ipv6_details(self): - """显示IPv6连接详情""" - import threading - import urllib.request - import ssl - import time - from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QTextEdit, QProgressBar - from PySide6.QtCore import Signal, QObject - - class SignalEmitter(QObject): - update_signal = Signal(str) - complete_signal = Signal(bool, float) - - # 创建对话框 - dialog = QDialog(self.main_window) - dialog.setWindowTitle(f"IPv6连接测试 - {APP_NAME}") - dialog.resize(500, 300) - - # 创建布局 - layout = QVBoxLayout(dialog) - - # 创建状态标签 - status_label = QLabel("正在测试IPv6连接...", dialog) - layout.addWidget(status_label) - - # 创建进度条 - progress = QProgressBar(dialog) - progress.setRange(0, 0) # 不确定进度 - layout.addWidget(progress) - - # 创建结果文本框 - result_text = QTextEdit(dialog) - result_text.setReadOnly(True) - layout.addWidget(result_text) - - # 创建关闭按钮 - close_button = QPushButton("关闭", dialog) - close_button.clicked.connect(dialog.accept) - close_button.setEnabled(False) # 测试完成前禁用 - layout.addWidget(close_button) - - # 信号发射器 - signal_emitter = SignalEmitter() - - # 连接信号 - signal_emitter.update_signal.connect( - lambda text: result_text.append(text) - ) - - def on_test_complete(success, elapsed_time): - # 停止进度条动画 - progress.setRange(0, 100) - progress.setValue(100 if success else 0) - - # 更新状态 - if success: - status_label.setText(f"IPv6连接测试完成: 可用 (用时: {elapsed_time:.2f}秒)") - else: - status_label.setText("IPv6连接测试完成: 不可用") - - # 启用关闭按钮 - close_button.setEnabled(True) - - signal_emitter.complete_signal.connect(on_test_complete) - - # 测试函数 - def test_ipv6(): - try: - signal_emitter.update_signal.emit("正在测试IPv6连接,请稍候...") - - # 使用与_check_ipv6_availability相同的测试URL - ipv6_test_url = "https://ipv6.testipv6.cn/images-nc/knob_green.png?&testdomain=www.test-ipv6.com&testname=sites" - - try: - # 设置3秒超时 - context = ssl._create_unverified_context() - req = urllib.request.Request(ipv6_test_url) - req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') - req.add_header('Accept', 'image/webp,image/apng,image/*,*/*;q=0.8') - - # 记录开始时间 - start_time = time.time() - signal_emitter.update_signal.emit(f"开始连接: {ipv6_test_url}") - - # 尝试下载图片 - with urllib.request.urlopen(req, timeout=5, context=context) as response: - image_data = response.read() - - # 计算耗时 - elapsed_time = time.time() - start_time - - # 检查是否成功 - if response.status == 200 and len(image_data) > 0: - signal_emitter.update_signal.emit(f"✓ 成功! 已下载 {len(image_data)} 字节") - signal_emitter.update_signal.emit(f"✓ 响应时间: {elapsed_time:.2f}秒") - signal_emitter.update_signal.emit("\n结论: 您的网络支持IPv6连接 ✓") - signal_emitter.complete_signal.emit(True, elapsed_time) - return - else: - signal_emitter.update_signal.emit(f"✗ 失败: 状态码 {response.status}") - signal_emitter.update_signal.emit("\n结论: 您的网络不支持IPv6连接 ✗") - signal_emitter.complete_signal.emit(False, 0) - return - - except Exception as e: - signal_emitter.update_signal.emit(f"✗ 连接失败: {e}") - signal_emitter.update_signal.emit("\n结论: 您的网络不支持IPv6连接 ✗") - signal_emitter.complete_signal.emit(False, 0) - - except Exception as e: - signal_emitter.update_signal.emit(f"测试过程中出错: {e}") - signal_emitter.complete_signal.emit(False, 0) - - # 启动测试线程 - threading.Thread(target=test_ipv6, daemon=True).start() - - # 显示对话框 - dialog.exec() - + # 使用IPv6Manager处理切换 + success = self.ipv6_manager.toggle_ipv6_support(enabled) + # 如果切换失败,恢复复选框状态 + if not success: + self.ipv6_action.setChecked(not enabled) + def show_menu(self, menu, button): """显示菜单 @@ -608,8 +480,8 @@ class UIManager: def revoke_privacy_agreement(self): """撤回隐私协议同意,并重启软件""" # 创建确认对话框 - msg_box = msgbox_frame( - f"确认操作 - {APP_NAME}", + msg_box = self._create_message_box( + "确认操作", "\n您确定要撤回隐私协议同意吗?\n\n撤回后软件将立即重启,您需要重新阅读并同意隐私协议。\n", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, ) @@ -628,10 +500,9 @@ class UIManager: privacy_manager = PrivacyManager() if privacy_manager.reset_privacy_agreement(): # 显示重启提示 - restart_msg = msgbox_frame( - f"操作成功 - {APP_NAME}", - "\n已成功撤回隐私协议同意。\n\n软件将立即重启。\n", - QMessageBox.StandardButton.Ok, + restart_msg = self._create_message_box( + "操作成功", + "\n已成功撤回隐私协议同意。\n\n软件将立即重启。\n" ) restart_msg.exec() @@ -649,28 +520,40 @@ class UIManager: sys.exit(0) else: # 显示失败提示 - fail_msg = msgbox_frame( - f"操作失败 - {APP_NAME}", - "\n撤回隐私协议同意失败。\n\n请检查应用权限或稍后再试。\n", - QMessageBox.StandardButton.Ok, + fail_msg = self._create_message_box( + "操作失败", + "\n撤回隐私协议同意失败。\n\n请检查应用权限或稍后再试。\n" ) fail_msg.exec() except Exception as e: # 显示错误提示 - error_msg = msgbox_frame( - f"错误 - {APP_NAME}", - f"\n撤回隐私协议同意时发生错误:\n\n{str(e)}\n", - QMessageBox.StandardButton.Ok, + error_msg = self._create_message_box( + "错误", + f"\n撤回隐私协议同意时发生错误:\n\n{str(e)}\n" ) error_msg.exec() + def _create_message_box(self, title, message, buttons=QMessageBox.StandardButton.Ok): + """创建统一风格的消息框 + + Args: + title: 消息框标题 + message: 消息内容 + buttons: 按钮类型,默认为确定按钮 + + Returns: + QMessageBox: 配置好的消息框实例 + """ + msg_box = msgbox_frame( + f"{title} - {APP_NAME}", + message, + buttons, + ) + return msg_box + def show_under_development(self): """显示功能正在开发中的提示""" - msg_box = msgbox_frame( - f"提示 - {APP_NAME}", - "\n该功能正在开发中,敬请期待!\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("提示", "\n该功能正在开发中,敬请期待!\n") msg_box.exec() def show_download_thread_settings(self): @@ -679,36 +562,20 @@ class UIManager: self.main_window.download_manager.show_download_thread_settings() else: # 如果下载管理器不可用,显示错误信息 - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - "\n下载管理器未初始化,无法修改下载线程设置。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("错误", "\n下载管理器未初始化,无法修改下载线程设置。\n") msg_box.exec() def open_log_file(self): """打开log.txt文件""" - if os.path.exists(LOG_FILE): - try: - # 使用操作系统默认程序打开日志文件 - if os.name == 'nt': # Windows - os.startfile(LOG_FILE) - else: # macOS 和 Linux - import subprocess - subprocess.call(['xdg-open', LOG_FILE]) - except Exception as e: - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - f"\n打开log.txt文件失败:\n\n{str(e)}\n", - QMessageBox.StandardButton.Ok, - ) - msg_box.exec() - else: - msg_box = msgbox_frame( - f"提示 - {APP_NAME}", - "\nlog.txt文件不存在,请确保Debug模式已开启并生成日志。\n", - QMessageBox.StandardButton.Ok, - ) + try: + # 使用操作系统默认程序打开日志文件 + if os.name == 'nt': # Windows + os.startfile(LOG_FILE) + else: # macOS 和 Linux + import subprocess + subprocess.call(['xdg-open', LOG_FILE]) + except Exception as e: + msg_box = self._create_message_box("错误", f"\n打开log.txt文件失败:\n\n{str(e)}\n") msg_box.exec() def restore_hosts_backup(self): @@ -719,88 +586,18 @@ class UIManager: result = self.main_window.download_manager.hosts_manager.restore() if result: - msg_box = msgbox_frame( - f"成功 - {APP_NAME}", - "\nhosts文件已成功还原为备份版本。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("成功", "\nhosts文件已成功还原为备份版本。\n") else: - msg_box = msgbox_frame( - f"警告 - {APP_NAME}", - "\n还原hosts文件失败或没有找到备份文件。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("警告", "\n还原hosts文件失败或没有找到备份文件。\n") msg_box.exec() except Exception as e: - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - f"\n还原hosts文件时发生错误:\n\n{str(e)}\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("错误", f"\n还原hosts文件时发生错误:\n\n{str(e)}\n") msg_box.exec() else: - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - "\n无法访问hosts管理器。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("错误", "\n无法访问hosts管理器。\n") msg_box.exec() - def toggle_ipv6_support(self, enabled): - """切换IPv6支持 - - Args: - enabled: 是否启用IPv6支持 - """ - print(f"Toggle IPv6 support: {enabled}") - - # 如果用户尝试启用IPv6,检查系统是否支持IPv6并发出警告 - if enabled: - # 先显示警告提示 - warning_msg_box = msgbox_frame( - f"警告 - {APP_NAME}", - "\n目前IPv6支持功能仍在测试阶段,可能会发生意料之外的bug!\n\n您确定需要启用吗?\n", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - ) - response = warning_msg_box.exec() - - # 如果用户选择不启用,直接返回 - if response != QMessageBox.StandardButton.Yes: - self.ipv6_action.setChecked(False) - return - - # 用户确认启用后,继续检查IPv6可用性 - ipv6_available = self._check_ipv6_availability() - - if not ipv6_available: - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - "\n未检测到可用的IPv6连接,无法启用IPv6支持。\n\n请确保您的网络环境支持IPv6且已正确配置。\n", - QMessageBox.StandardButton.Ok, - ) - msg_box.exec() - - # 恢复复选框状态 - self.ipv6_action.setChecked(False) - return - - # 保存设置到配置 - if hasattr(self.main_window, 'config'): - self.main_window.config["ipv6_enabled"] = enabled - # 直接使用utils.save_config保存配置 - from utils import save_config - save_config(self.main_window.config) - - # 显示设置已保存的消息 - status = "启用" if enabled else "禁用" - msg_box = msgbox_frame( - f"IPv6设置 - {APP_NAME}", - f"\nIPv6支持已{status}。新的设置将在下一次下载时生效。\n", - QMessageBox.StandardButton.Ok, - ) - msg_box.exec() - def clean_hosts_entries(self): """手动删除软件添加的hosts条目""" if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'): @@ -809,32 +606,43 @@ class UIManager: result = self.main_window.download_manager.hosts_manager.check_and_clean_all_entries() if result: - msg_box = msgbox_frame( - f"成功 - {APP_NAME}", - "\n已成功清理软件添加的hosts条目。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("成功", "\n已成功清理软件添加的hosts条目。\n") else: - msg_box = msgbox_frame( - f"提示 - {APP_NAME}", - "\n未发现软件添加的hosts条目或清理操作失败。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("提示", "\n未发现软件添加的hosts条目或清理操作失败。\n") msg_box.exec() except Exception as e: - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - f"\n清理hosts条目时发生错误:\n\n{str(e)}\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("错误", f"\n清理hosts条目时发生错误:\n\n{str(e)}\n") msg_box.exec() else: - msg_box = msgbox_frame( - f"错误 - {APP_NAME}", - "\n无法访问hosts管理器。\n", - QMessageBox.StandardButton.Ok, - ) + msg_box = self._create_message_box("错误", "\n无法访问hosts管理器。\n") + msg_box.exec() + + def open_hosts_file(self): + """打开系统hosts文件""" + try: + # 获取hosts文件路径 + hosts_path = os.path.join(os.environ['SystemRoot'], 'System32', 'drivers', 'etc', 'hosts') + + # 检查文件是否存在 + if os.path.exists(hosts_path): + # 使用操作系统默认程序打开hosts文件 + if os.name == 'nt': # Windows + # 尝试以管理员权限打开记事本编辑hosts文件 + try: + # 使用PowerShell以管理员身份启动记事本 + subprocess.Popen(["powershell", "Start-Process", "notepad", hosts_path, "-Verb", "RunAs"]) + except Exception as e: + # 如果失败,尝试直接打开 + os.startfile(hosts_path) + else: # macOS 和 Linux + import subprocess + subprocess.call(['xdg-open', hosts_path]) + else: + msg_box = self._create_message_box("错误", f"\nhosts文件不存在:\n{hosts_path}\n") + msg_box.exec() + except Exception as e: + msg_box = self._create_message_box("错误", f"\n打开hosts文件时发生错误:\n\n{str(e)}\n") msg_box.exec() def show_about_dialog(self): @@ -857,4 +665,9 @@ class UIManager: QMessageBox.StandardButton.Ok, ) msg_box.setTextFormat(Qt.TextFormat.RichText) # 使用Qt.TextFormat + msg_box.exec() + + def show_ipv6_manager_not_ready(self): + """显示IPv6管理器未准备好的提示""" + msg_box = self._create_message_box("错误", "\nIPv6管理器尚未初始化,请稍后再试。\n") msg_box.exec() \ No newline at end of file diff --git a/source/main_window.py b/source/main_window.py index 3ab23e5..a6b64c5 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -28,6 +28,7 @@ from core import ( MultiStageAnimations, UIManager, DownloadManager, DebugManager, WindowManager, GameDetector, PatchManager, ConfigManager ) +from core.ipv6_manager import IPv6Manager class MainWindow(QMainWindow): def __init__(self): @@ -55,23 +56,30 @@ class MainWindow(QMainWindow): self.hash_manager = HashManager(BLOCK_SIZE) self.admin_privileges = AdminPrivileges() - # 初始化管理器 + # 初始化各种管理器 + # 1. 首先创建必要的基础管理器 self.animator = MultiStageAnimations(self.ui, self) - self.ui_manager = UIManager(self) - - # 首先设置UI - 确保debug_action已初始化 - self.ui_manager.setup_ui() - - # 初始化新的管理器类 self.window_manager = WindowManager(self) self.debug_manager = DebugManager(self) - # 为debug_manager设置ui_manager引用 + + # 2. 初始化IPv6Manager(应在UIManager之前) + self.ipv6_manager = IPv6Manager(self) + + # 3. 创建UIManager(依赖IPv6Manager) + self.ui_manager = UIManager(self) + + # 4. 为debug_manager设置ui_manager引用 self.debug_manager.set_ui_manager(self.ui_manager) + + # 设置UI - 确保debug_action已初始化 + self.ui_manager.setup_ui() + + # 5. 初始化其他管理器 self.config_manager = ConfigManager(APP_NAME, CONFIG_URL, UA, self.debug_manager) self.game_detector = GameDetector(GAME_INFO, self.debug_manager) self.patch_manager = PatchManager(APP_NAME, GAME_INFO, self.debug_manager) - # 初始化下载管理器 - 应该放在其他管理器之后,因为它可能依赖于它们 + # 6. 初始化下载管理器 - 放在最后,因为它可能依赖于其他管理器 self.download_manager = DownloadManager(self) # 加载用户下载线程设置 diff --git a/source/workers/ip_optimizer.py b/source/workers/ip_optimizer.py index 3878037..654f6c5 100644 --- a/source/workers/ip_optimizer.py +++ b/source/workers/ip_optimizer.py @@ -302,7 +302,11 @@ class IpOptimizer: class IpOptimizerThread(QThread): - """用于在后台线程中运行IP优化的类""" + """用于在后台线程中运行IP优化的类 + + 注意:IPv6连接测试功能已迁移至IPv6Manager类, + 本类仅负责IP优化相关功能 + """ finished = Signal(str) def __init__(self, url, parent=None, use_ipv6=False):