From 2e6f71d962ef405dd20fe76cf9f02930383e901e Mon Sep 17 00:00:00 2001 From: hyb-oyqq <1512383570@qq.com> Date: Fri, 18 Jul 2025 12:01:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(main):=20=E9=A2=84=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E4=BA=91=E7=AB=AF=E9=85=8D=E7=BD=AE=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在主线程中添加云端配置预加载功能 - 优化下载 URL 获取逻辑,使用预加载的配置数据 - 添加配置数据缺失和版本更新提示功能 - 调整动画系统,添加动画完成信号 - 重构部分代码以提高可维护性和可读性 --- source/Ui_install.py | 14 --- source/animations.py | 14 ++- source/config.py | 2 +- source/main_window.py | 261 +++++++++++++++++++++++++++++------------- 4 files changed, 191 insertions(+), 100 deletions(-) diff --git a/source/Ui_install.py b/source/Ui_install.py index 19cf8a2..42deb41 100644 --- a/source/Ui_install.py +++ b/source/Ui_install.py @@ -1,12 +1,3 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'install.ui' -## -## Created by: Qt User Interface Compiler version 6.9.1 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ from pic_data import img_data from PySide6.QtGui import QPixmap import base64 @@ -42,8 +33,6 @@ class Ui_MainWindows(object): MainWindows.setAnimated(True) MainWindows.setDocumentMode(False) MainWindows.setDockNestingEnabled(False) - self.action_2 = QAction(MainWindows) - self.action_2.setObjectName(u"action_2") self.centralwidget = QWidget(MainWindows) self.centralwidget.setObjectName(u"centralwidget") self.centralwidget.setAutoFillBackground(True) @@ -140,8 +129,6 @@ class Ui_MainWindows(object): self.menubar.addAction(self.menu.menuAction()) self.menubar.addAction(self.menu_2.menuAction()) self.menu.addSeparator() - self.menu.addAction(self.action_2) - self.retranslateUi(MainWindows) QMetaObject.connectSlotsByName(MainWindows) @@ -149,7 +136,6 @@ class Ui_MainWindows(object): def retranslateUi(self, MainWindows): MainWindows.setWindowTitle(QCoreApplication.translate("MainWindows", u" UI Test", None)) - self.action_2.setText(QCoreApplication.translate("MainWindows", u"\u68c0\u67e5\u66f4\u65b0(\u672a\u5b8c\u6210)", None)) self.loadbg.setText("") self.vol1bg.setText("") self.vol2bg.setText("") diff --git a/source/animations.py b/source/animations.py index e169abb..ca690ae 100644 --- a/source/animations.py +++ b/source/animations.py @@ -1,9 +1,11 @@ -from PySide6.QtCore import (QPropertyAnimation, QParallelAnimationGroup, - QPoint, QEasingCurve, QTimer) +from PySide6.QtCore import (QObject, QPropertyAnimation, QParallelAnimationGroup, + QPoint, QEasingCurve, QTimer, Signal) from PySide6.QtWidgets import QGraphicsOpacityEffect -class MultiStageAnimations: - def __init__(self, ui): +class MultiStageAnimations(QObject): + animation_finished = Signal() + def __init__(self, ui, parent=None): + super().__init__(parent) self.ui = ui # 获取画布尺寸 self.canvas_width = ui.centralwidget.width() @@ -141,6 +143,10 @@ class MultiStageAnimations: anim_group.addAnimation(pos_anim) anim_group.addAnimation(opacity_anim) + + if item["widget"] == self.ui.exit_btn: + anim_group.finished.connect(self.animation_finished.emit) + anim_group.start() self.animations.append(anim_group) def start_animations(self): diff --git a/source/config.py b/source/config.py index dd8cc61..eb961ab 100644 --- a/source/config.py +++ b/source/config.py @@ -3,7 +3,7 @@ import base64 # 配置信息 app_data = { - "APP_VERSION": "1.1.0", + "APP_VERSION": "1.1.1", "APP_NAME": "FRAISEMOE Addons Installer NEXT", "TEMP": "TEMP", "CACHE": "FRAISEMOE", diff --git a/source/main_window.py b/source/main_window.py index 134c795..af1a85a 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -113,6 +113,56 @@ class ExtractionThread(QThread): self.finished.emit(False, f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n", self.game_version) +class ConfigFetchThread(QThread): + finished = Signal(object, str) # data, error_message + + def __init__(self, url, headers, debug_mode=False, parent=None): + super().__init__(parent) + self.url = url + self.headers = headers + self.debug_mode = debug_mode + + def run(self): + try: + if self.debug_mode: + print("--- Starting to fetch cloud config ---") + print(f"DEBUG: Requesting URL: {self.url}") + print(f"DEBUG: Using Headers: {self.headers}") + + response = requests.get(self.url, headers=self.headers, timeout=10) + + if self.debug_mode: + print(f"DEBUG: Response Status Code: {response.status_code}") + print(f"DEBUG: Response Headers: {response.headers}") + print(f"DEBUG: Response Text: {response.text}") + + response.raise_for_status() + + # 首先,总是尝试解析JSON + config_data = response.json() + + # 检查是否是要求更新的错误信息 + if config_data.get("message") == "请使用最新版本的FRAISEMOE Addons Installer NEXT进行下载安装": + self.finished.emit(None, "update_required") + return + + # 检查是否是有效的配置文件 + required_keys = [f"vol.{i+1}.data" for i in range(4)] + ["after.data"] + missing_keys = [key for key in required_keys if key not in config_data] + if missing_keys: + self.finished.emit(None, f"missing_keys:{','.join(missing_keys)}") + return + + self.finished.emit(config_data, "") + except requests.exceptions.RequestException as e: + self.finished.emit(None, f"网络请求失败: {e}") + except (ValueError, json.JSONDecodeError) as e: + self.finished.emit(None, f"JSON解析失败: {e}") + finally: + if self.debug_mode: + print("--- Finished fetching cloud config ---") + + class MainWindow(QMainWindow): def __init__(self): super().__init__() @@ -130,7 +180,7 @@ class MainWindow(QMainWindow): self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}") # 初始化动画系统 (从animations.py导入) - self.animator = MultiStageAnimations(self.ui) + self.animator = MultiStageAnimations(self.ui, self) # 初始化功能变量 self.selected_folder = "" @@ -146,6 +196,8 @@ class MainWindow(QMainWindow): self.optimization_done = False # 标记是否已执行过优选 self.logger = None self.hosts_manager = HostsManager() # 实例化HostsManager + self.cloud_config = None + self.config_fetch_thread = None # 加载配置 self.config = load_config() @@ -188,6 +240,11 @@ class MainWindow(QMainWindow): self.debug_action.triggered.connect(self.toggle_debug_mode) self.ui.menu.addAction(self.debug_action) + # 为未来功能预留的“切换下载源”按钮 + self.switch_source_action = QAction("切换下载源", self) + self.switch_source_action.setEnabled(False) # 暂时禁用 + self.ui.menu.addAction(self.switch_source_action) + # 根据初始配置决定是否开启Debug模式 if self.debug_action.isChecked(): self.start_logging() @@ -199,7 +256,49 @@ class MainWindow(QMainWindow): QTimer.singleShot(100, self.start_animations) def start_animations(self): + self.ui.exit_btn.setEnabled(False) + self.animator.animation_finished.connect(self.on_animations_finished) self.animator.start_animations() + self.fetch_cloud_config() + + def on_animations_finished(self): + self.ui.exit_btn.setEnabled(True) + + def fetch_cloud_config(self): + headers = {"User-Agent": UA} + debug_mode = self.debug_action.isChecked() + self.config_fetch_thread = ConfigFetchThread(CONFIG_URL, headers, debug_mode, self) + self.config_fetch_thread.finished.connect(self.on_config_fetched) + self.config_fetch_thread.start() + + def on_config_fetched(self, data, error_message): + if error_message: + if error_message == "update_required": + msg_box = msgbox_frame( + f"更新提示 - {APP_NAME}", + "\n当前版本过低,请及时更新。\n", + QtWidgets.QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + self.open_project_home_page() + self.shutdown_app(force_exit=True) + elif "missing_keys" in error_message: + missing_versions = error_message.split(":")[1] + msg_box = msgbox_frame( + f"配置缺失 - {APP_NAME}", + f'\n云端缺失下载链接,可能云服务器正在维护,不影响其他版本下载。\n当前缺失版本:"{missing_versions}"\n', + QtWidgets.QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + else: + # 其他错误暂时只在debug模式下打印 + if self.debug_action.isChecked(): + print(f"获取云端配置失败: {error_message}") + else: + self.cloud_config = data + if self.debug_action.isChecked(): + print("--- Cloud config fetched successfully ---") + print(json.dumps(data, indent=2)) def toggle_debug_mode(self, checked): self.config["debug_mode"] = checked @@ -253,45 +352,51 @@ class MainWindow(QMainWindow): def get_download_url(self) -> dict: try: - headers = {"User-Agent": UA} - if self.debug_action.isChecked(): - print("--- Starting to get download URL ---") - print(f"DEBUG: Requesting URL: {CONFIG_URL}") - print(f"DEBUG: Using Headers: {headers}") + if self.cloud_config: + if self.debug_action.isChecked(): + print("--- Using pre-fetched cloud config ---") + config_data = self.cloud_config + else: + # 如果没有预加载的配置,则同步获取 + headers = {"User-Agent": UA} + response = requests.get(CONFIG_URL, headers=headers, timeout=10) + response.raise_for_status() + config_data = response.json() - response = requests.get(CONFIG_URL, headers=headers, timeout=10) + if not config_data: + raise ValueError("未能获取或解析配置数据") - if self.debug_action.isChecked(): - print(f"DEBUG: Response Status Code: {response.status_code}") - print(f"DEBUG: Response Headers: {response.headers}") - print(f"DEBUG: Response Text: {response.text}") - - response.raise_for_status() - - # 从响应文本中提取有效的 JSON 部分 - response_text = response.text - json_start_index = response_text.find('{') - if json_start_index == -1: - raise ValueError("响应中未找到有效的 JSON 对象") - - json_text = response_text[json_start_index:] - config_data = json.loads(json_text) - if self.debug_action.isChecked(): print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}") - # 修正键名检查,确保所有必需的键都存在 - required_keys = [f"vol.{i+1}.data" for i in range(4)] + ["after.data"] - if not all(key in config_data for key in required_keys): - missing_keys = [key for key in required_keys if key not in config_data] - raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_keys)}") + # 统一处理URL提取,确保返回扁平化的字典 + urls = {} + for i in range(4): + key = f"vol.{i+1}.data" + if key in config_data and "url" in config_data[key]: + urls[f"vol{i+1}"] = config_data[key]["url"] - # 修正提取URL的逻辑,确保使用正确的键 - urls = { - f"vol{i+1}": config_data[f"vol.{i+1}.data"]["url"] for i in range(4) - } | { - "after": config_data["after.data"]["url"] - } + 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) + } + missing_keys_map["after"] = "after.data" + + extracted_keys = set(urls.keys()) + all_keys = set(missing_keys_map.keys()) + missing_simple_keys = all_keys - extracted_keys + + missing_original_keys = [missing_keys_map[k] for k in missing_simple_keys] + raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_original_keys)}") + + if self.debug_action.isChecked(): + print(f"DEBUG: Extracted URLs: {urls}") + print("--- Finished getting download URL successfully ---") + return urls if self.debug_action.isChecked(): print(f"DEBUG: Extracted URLs: {urls}") print("--- Finished getting download URL successfully ---") @@ -367,7 +472,6 @@ class MainWindow(QMainWindow): else: QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", "\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n") - self.setEnabled(True) self.next_download_task() def start_download_with_ip(self, preferred_ip, url, _7z_path, game_version, game_folder, plugin_path): @@ -434,7 +538,6 @@ class MainWindow(QMainWindow): # --- Start Extraction in a new thread --- self.hash_msg_box = self.hash_manager.hash_pop_window() - self.setEnabled(False) self.extraction_thread = ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self) self.extraction_thread.finished.connect(self.on_extraction_finished) @@ -443,7 +546,6 @@ class MainWindow(QMainWindow): def on_extraction_finished(self, success, error_message, game_version): if self.hash_msg_box and self.hash_msg_box.isVisible(): self.hash_msg_box.close() - self.setEnabled(True) if not success: QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", error_message) @@ -457,7 +559,7 @@ class MainWindow(QMainWindow): # 询问用户是否使用Cloudflare加速 msg_box = QMessageBox(self) msg_box.setWindowTitle(f"下载优化 - {APP_NAME}") - msg_box.setText("\n是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。") + msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。") msg_box.setIcon(QMessageBox.Icon.Question) yes_button = msg_box.addButton("是,开启加速", QMessageBox.ButtonRole.YesRole) @@ -468,7 +570,6 @@ class MainWindow(QMainWindow): use_optimization = msg_box.clickedButton() == yes_button self.hash_msg_box = self.hash_manager.hash_pop_window() - self.setEnabled(False) install_paths = self.get_install_paths() @@ -488,7 +589,6 @@ class MainWindow(QMainWindow): QtWidgets.QMessageBox.critical( self, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n" ) - self.setEnabled(True) return # --- 填充下载队列 --- @@ -532,7 +632,6 @@ class MainWindow(QMainWindow): self.ip_optimizer_thread.start() else: # 如果用户选择不优化,或已经优化过,直接开始下载 - self.setEnabled(True) self.next_download_task() def next_download_task(self): @@ -578,7 +677,6 @@ class MainWindow(QMainWindow): def after_hash_compare(self, plugin_hash): self.hash_msg_box = self.hash_manager.hash_pop_window() - self.setEnabled(False) install_paths = self.get_install_paths() @@ -589,7 +687,6 @@ class MainWindow(QMainWindow): def on_after_hash_finished(self, result): if self.hash_msg_box and self.hash_msg_box.isVisible(): self.hash_msg_box.close() - self.setEnabled(True) if not result["passed"]: game = result.get("game", "未知游戏") @@ -639,50 +736,52 @@ class MainWindow(QMainWindow): def closeEvent(self, event): self.shutdown_app(event) - def shutdown_app(self, event=None): + def shutdown_app(self, event=None, force_exit=False): self.hosts_manager.restore() # 恢复hosts文件 self.stop_logging() # 确保在退出时停止日志记录 - reply = QtWidgets.QMessageBox.question( - self, - "退出程序", - "\n是否确定退出?\n", - QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, - QtWidgets.QMessageBox.StandardButton.No, - ) - if reply == QtWidgets.QMessageBox.StandardButton.Yes: - if ( - self.current_download_thread - and self.current_download_thread.isRunning() - ): - QtWidgets.QMessageBox.critical( - self, - f"错误 - {APP_NAME}", - "\n当前有下载任务正在进行,完成后再试\n", - ) + if not force_exit: + reply = QtWidgets.QMessageBox.question( + self, + "退出程序", + "\n是否确定退出?\n", + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.No, + ) + if reply != QtWidgets.QMessageBox.StandardButton.Yes: if event: event.ignore() return - if os.path.exists(PLUGIN): - for attempt in range(3): - try: - shutil.rmtree(PLUGIN) - break - except Exception as e: - if attempt == 2: - QtWidgets.QMessageBox.critical( - self, - f"错误 - {APP_NAME}", - f"\n清理缓存失败\n\n【错误信息】:{e}\n", - ) - if event: - event.accept() - sys.exit(1) + if ( + self.current_download_thread + and self.current_download_thread.isRunning() + ): + QtWidgets.QMessageBox.critical( + self, + f"错误 - {APP_NAME}", + "\n当前有下载任务正在进行,完成后再试\n", + ) if event: - event.accept() - else: - sys.exit(0) + event.ignore() + return + + if os.path.exists(PLUGIN): + for attempt in range(3): + try: + shutil.rmtree(PLUGIN) + break + except Exception as e: + if attempt == 2: + QtWidgets.QMessageBox.critical( + self, + f"错误 - {APP_NAME}", + f"\n清理缓存失败\n\n【错误信息】:{e}\n", + ) + if event: + event.accept() + sys.exit(1) + if event: + event.accept() else: - if event: - event.ignore() \ No newline at end of file + sys.exit(0) \ No newline at end of file