mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-16 20:10:28 +00:00
- 删除了大量未使用的导入语句 - 移除了冗长的配置信息和全局变量定义 - 精简了主窗口类 MainWindow 的代码 - 提取了部分代码到新的文件 main_window.py 中
471 lines
19 KiB
Python
471 lines
19 KiB
Python
import os
|
||
import sys
|
||
import shutil
|
||
import webbrowser
|
||
import requests
|
||
import py7zr
|
||
from collections import deque
|
||
from PySide6 import QtWidgets
|
||
from PySide6.QtCore import QTimer
|
||
from PySide6.QtGui import QIcon, QAction
|
||
from PySide6.QtWidgets import QMainWindow, QFileDialog, QApplication
|
||
|
||
from Ui_install import Ui_MainWindows
|
||
from animations import MultiStageAnimations
|
||
from config import (
|
||
APP_NAME, APP_VERSION, PLUGIN, GAME_INFO, BLOCK_SIZE,
|
||
PLUGIN_HASH, UA, CONFIG_URL
|
||
)
|
||
from utils import (
|
||
load_base64_image, HashManager, AdminPrivileges, msgbox_frame
|
||
)
|
||
from download import DownloadThread, ProgressWindow
|
||
from pic_data import img_data
|
||
|
||
class MainWindow(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
|
||
# 初始化UI (从Ui_install.py导入)
|
||
self.ui = Ui_MainWindows()
|
||
self.ui.setupUi(self)
|
||
|
||
icon_data = img_data.get("icon")
|
||
if icon_data:
|
||
pixmap = load_base64_image(icon_data)
|
||
self.setWindowIcon(QIcon(pixmap))
|
||
|
||
# 设置窗口标题为APP_NAME加版本号
|
||
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
||
|
||
# 初始化动画系统 (从animations.py导入)
|
||
self.animator = MultiStageAnimations(self.ui)
|
||
|
||
# 初始化功能变量
|
||
self.selected_folder = ""
|
||
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
|
||
self.installed_status["NEKOPARA After"] = False # 添加After的状态
|
||
self.download_queue = deque()
|
||
self.current_download_thread = None
|
||
self.hash_manager = HashManager(BLOCK_SIZE)
|
||
|
||
# 检查管理员权限和进程
|
||
admin_privileges = AdminPrivileges()
|
||
admin_privileges.request_admin_privileges()
|
||
admin_privileges.check_and_terminate_processes()
|
||
|
||
# 创建缓存目录
|
||
if not os.path.exists(PLUGIN):
|
||
try:
|
||
os.makedirs(PLUGIN)
|
||
except OSError as e:
|
||
QtWidgets.QMessageBox.critical(
|
||
self,
|
||
f"错误 - {APP_NAME}",
|
||
f"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n\n【错误信息】:{e}\n",
|
||
)
|
||
sys.exit(1)
|
||
|
||
# 连接信号 (使用Ui_install.py中的组件名称)
|
||
self.ui.start_install_btn.clicked.connect(self.file_dialog)
|
||
self.ui.exit_btn.clicked.connect(self.shutdown_app)
|
||
|
||
# “关于”菜单
|
||
about_action = QAction("项目主页", self)
|
||
about_action.triggered.connect(self.open_about_page)
|
||
version_action = QAction("版本信息", self)
|
||
version_action.triggered.connect(self.show_version_info)
|
||
self.ui.menu_2.addAction(version_action)
|
||
self.ui.menu_2.addAction(about_action)
|
||
|
||
# 在窗口显示前设置初始状态
|
||
self.animator.initialize()
|
||
|
||
# 窗口显示后延迟100ms启动动画
|
||
QTimer.singleShot(100, self.start_animations)
|
||
|
||
def start_animations(self):
|
||
self.animator.start_animations()
|
||
|
||
def get_install_paths(self):
|
||
return {
|
||
game: os.path.join(self.selected_folder, info["install_path"])
|
||
for game, info in GAME_INFO.items()
|
||
}
|
||
|
||
def file_dialog(self):
|
||
self.selected_folder = QFileDialog.getExistingDirectory(
|
||
self, f"选择游戏所在【上级目录】 {APP_NAME}"
|
||
)
|
||
if not self.selected_folder:
|
||
QtWidgets.QMessageBox.warning(
|
||
self, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||
)
|
||
return
|
||
self.download_action()
|
||
|
||
def get_download_url(self) -> dict:
|
||
try:
|
||
headers = {"User-Agent": UA}
|
||
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
||
response.raise_for_status()
|
||
config_data = response.json()
|
||
# 修正键名检查,确保所有必需的键都存在
|
||
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的逻辑,确保使用正确的键
|
||
return {
|
||
f"vol{i+1}": config_data[f"vol.{i+1}.data"]["url"] for i in range(4)
|
||
} | {
|
||
"after": config_data["after.data"]["url"]
|
||
}
|
||
except requests.exceptions.RequestException as e:
|
||
status_code = e.response.status_code if e.response is not None else "未知"
|
||
try:
|
||
error_response = e.response.json() if e.response else {}
|
||
json_title = error_response.get("title", "无错误类型")
|
||
json_message = error_response.get("message", "无附加错误信息")
|
||
except (ValueError, AttributeError):
|
||
json_title = "配置文件异常,无法解析错误类型"
|
||
json_message = "配置文件异常,无法解析错误信息"
|
||
|
||
print(f"获取下载配置时出错: {e}") # 添加详细错误日志
|
||
QtWidgets.QMessageBox.critical(
|
||
self,
|
||
f"错误 - {APP_NAME}",
|
||
f"\n下载配置获取失败\n\n【HTTP状态】:{status_code}\n【错误类型】:{json_title}\n【错误信息】:{json_message}\n",
|
||
)
|
||
return {}
|
||
except ValueError as e:
|
||
QtWidgets.QMessageBox.critical(
|
||
self,
|
||
f"错误 - {APP_NAME}",
|
||
f"\n配置文件格式异常\n\n【错误信息】:{e}\n",
|
||
)
|
||
return {}
|
||
|
||
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
||
game_exe = {
|
||
game: os.path.join(
|
||
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
|
||
)
|
||
for game, info in GAME_INFO.items()
|
||
}
|
||
|
||
if (
|
||
game_version not in game_exe
|
||
or not os.path.exists(game_exe[game_version])
|
||
or self.installed_status[game_version]
|
||
):
|
||
self.installed_status[game_version] = False
|
||
self.show_result()
|
||
return
|
||
|
||
self.progress_window = ProgressWindow(self)
|
||
|
||
self.current_download_thread = DownloadThread(url, _7z_path, game_version, self)
|
||
self.current_download_thread.progress.connect(self.progress_window.update_progress)
|
||
self.current_download_thread.finished.connect(
|
||
lambda success, error: self.install_setting(
|
||
success,
|
||
error,
|
||
self.progress_window,
|
||
url,
|
||
game_folder,
|
||
game_version,
|
||
_7z_path,
|
||
plugin_path,
|
||
)
|
||
)
|
||
|
||
# 连接停止按钮的信号
|
||
self.progress_window.stop_button.clicked.connect(self.current_download_thread.stop)
|
||
self.current_download_thread.start()
|
||
self.progress_window.exec() # 使用exec()以模态方式显示对话框
|
||
|
||
def install_setting(
|
||
self,
|
||
success,
|
||
error,
|
||
progress_window,
|
||
url,
|
||
game_folder,
|
||
game_version,
|
||
_7z_path,
|
||
plugin_path,
|
||
):
|
||
if progress_window.isVisible():
|
||
progress_window.reject()
|
||
|
||
if not success: # 处理所有失败情况,包括手动停止
|
||
print(f"--- Download Failed: {game_version} ---")
|
||
print(error)
|
||
print("------------------------------------")
|
||
msg_box = QtWidgets.QMessageBox(self)
|
||
msg_box.setWindowTitle(f"下载失败 - {APP_NAME}")
|
||
# 如果是手动停止,显示特定信息
|
||
if error == "下载已手动停止。":
|
||
msg_box.setText(f"\n下载已手动终止: {game_version}\n\n是否重试?")
|
||
else:
|
||
msg_box.setText(f"\n文件获取失败: {game_version}\n\n是否重试?")
|
||
|
||
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||
end_button = msg_box.addButton("结束", QtWidgets.QMessageBox.ButtonRole.RejectRole)
|
||
|
||
icon_data = img_data.get("icon")
|
||
if icon_data:
|
||
pixmap = load_base64_image(icon_data)
|
||
if not pixmap.isNull():
|
||
msg_box.setWindowIcon(QIcon(pixmap))
|
||
|
||
msg_box.exec()
|
||
clicked_button = msg_box.clickedButton()
|
||
|
||
if clicked_button == retry_button:
|
||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||
elif clicked_button == next_button:
|
||
self.next_download_task()
|
||
else: # End button or closed dialog
|
||
self.download_queue.clear()
|
||
self.after_hash_compare(PLUGIN_HASH)
|
||
return # 确保失败后不再执行成功逻辑
|
||
|
||
if success:
|
||
try:
|
||
msg_box = self.hash_manager.hash_pop_window()
|
||
QApplication.processEvents()
|
||
with py7zr.SevenZipFile(_7z_path, mode="r") as archive:
|
||
archive.extractall(path=PLUGIN)
|
||
|
||
# 创建游戏目录(如果不存在)
|
||
os.makedirs(game_folder, exist_ok=True)
|
||
|
||
# 复制主文件
|
||
shutil.copy(plugin_path, game_folder)
|
||
|
||
# 如果是After版本,还需要复制签名文件
|
||
if game_version == "NEKOPARA After":
|
||
sig_path = os.path.join(PLUGIN, GAME_INFO[game_version]["sig_path"])
|
||
shutil.copy(sig_path, game_folder)
|
||
|
||
self.installed_status[game_version] = True
|
||
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
||
QtWidgets.QMessageBox.critical(
|
||
self,
|
||
f"错误 - {APP_NAME}",
|
||
f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n",
|
||
)
|
||
finally:
|
||
msg_box.close()
|
||
self.next_download_task()
|
||
self.progress_window.close()
|
||
|
||
if success:
|
||
try:
|
||
msg_box = self.hash_manager.hash_pop_window()
|
||
QApplication.processEvents()
|
||
with py7zr.SevenZipFile(_7z_path, mode="r") as archive:
|
||
archive.extractall(path=PLUGIN)
|
||
|
||
# 创建游戏目录(如果不存在)
|
||
os.makedirs(game_folder, exist_ok=True)
|
||
|
||
# 复制主文件
|
||
shutil.copy(plugin_path, game_folder)
|
||
|
||
# 如果是After版本,还需要复制签名文件
|
||
if game_version == "NEKOPARA After":
|
||
sig_path = os.path.join(PLUGIN, GAME_INFO[game_version]["sig_path"])
|
||
shutil.copy(sig_path, game_folder)
|
||
|
||
self.installed_status[game_version] = True
|
||
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
||
QtWidgets.QMessageBox.critical(
|
||
self,
|
||
f"错误 - {APP_NAME}",
|
||
f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n",
|
||
)
|
||
finally:
|
||
msg_box.close()
|
||
self.next_download_task()
|
||
else:
|
||
print(f"--- Download Failed: {game_version} ---")
|
||
print(error)
|
||
print("------------------------------------")
|
||
msg_box = QtWidgets.QMessageBox(self)
|
||
msg_box.setWindowTitle(f"下载失败 {APP_NAME}")
|
||
msg_box.setText(f"\n文件获取失败: {game_version}\n\n是否重试?")
|
||
|
||
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||
end_button = msg_box.addButton("结束", QtWidgets.QMessageBox.ButtonRole.RejectRole)
|
||
|
||
icon_data = img_data.get("icon")
|
||
if icon_data:
|
||
pixmap = load_base64_image(icon_data)
|
||
if not pixmap.isNull():
|
||
msg_box.setWindowIcon(QIcon(pixmap))
|
||
|
||
msg_box.exec()
|
||
clicked_button = msg_box.clickedButton()
|
||
|
||
if clicked_button == retry_button:
|
||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||
elif clicked_button == next_button:
|
||
self.next_download_task()
|
||
else: # End button or closed dialog
|
||
self.download_queue.clear()
|
||
self.after_hash_compare(PLUGIN_HASH)
|
||
|
||
def pre_hash_compare(self, install_path, game_version, plugin_hash):
|
||
msg_box = self.hash_manager.hash_pop_window()
|
||
self.hash_manager.cfg_pre_hash_compare(
|
||
install_path, game_version, plugin_hash, self.installed_status
|
||
)
|
||
msg_box.close()
|
||
|
||
def download_action(self):
|
||
install_paths = self.get_install_paths()
|
||
for game_version, install_path in install_paths.items():
|
||
self.pre_hash_compare(install_path, game_version, PLUGIN_HASH)
|
||
|
||
config = self.get_download_url()
|
||
if not config:
|
||
QtWidgets.QMessageBox.critical(
|
||
self, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||
)
|
||
return
|
||
|
||
# 处理1-4卷
|
||
for i in range(1, 5):
|
||
game_version = f"NEKOPARA Vol.{i}"
|
||
if not self.installed_status[game_version]:
|
||
url = config[f"vol{i}"]
|
||
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
|
||
_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)
|
||
)
|
||
|
||
# 处理After
|
||
game_version = "NEKOPARA After"
|
||
if not self.installed_status[game_version]:
|
||
url = config["after"]
|
||
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
|
||
_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.next_download_task()
|
||
|
||
def next_download_task(self):
|
||
if not self.download_queue:
|
||
self.after_hash_compare(PLUGIN_HASH)
|
||
return
|
||
# 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务
|
||
if self.current_download_thread and self.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)
|
||
|
||
def on_download_stopped(self):
|
||
"""当用户点击停止按钮时调用的槽函数"""
|
||
# 清空下载队列,因为用户决定停止
|
||
self.download_queue.clear()
|
||
# 可以在这里决定是否立即进行哈希比较或显示结果
|
||
self.after_hash_compare(PLUGIN_HASH)
|
||
|
||
def after_hash_compare(self, plugin_hash):
|
||
msg_box = self.hash_manager.hash_pop_window()
|
||
result = self.hash_manager.cfg_after_hash_compare(
|
||
self.get_install_paths(), plugin_hash, self.installed_status
|
||
)
|
||
msg_box.close()
|
||
self.show_result()
|
||
return result
|
||
|
||
def show_result(self):
|
||
installed_version = "\n".join(
|
||
[i for i in self.installed_status if self.installed_status[i]]
|
||
)
|
||
failed_ver = "\n".join(
|
||
[i for i in self.installed_status if not self.installed_status[i]]
|
||
)
|
||
QtWidgets.QMessageBox.information(
|
||
self,
|
||
f"完成 - {APP_NAME}",
|
||
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
|
||
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
|
||
)
|
||
|
||
def show_version_info(self):
|
||
"""显示版本信息对话框"""
|
||
msg_box = msgbox_frame(
|
||
f"版本信息 - {APP_NAME}",
|
||
f"\n{APP_NAME}\n\n版本: {APP_VERSION}\n",
|
||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||
)
|
||
msg_box.exec()
|
||
|
||
def open_about_page(self):
|
||
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")
|
||
|
||
def closeEvent(self, event):
|
||
self.shutdown_app(event)
|
||
|
||
def shutdown_app(self, event=None):
|
||
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 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 event:
|
||
event.accept()
|
||
else:
|
||
sys.exit(0)
|
||
else:
|
||
if event:
|
||
event.ignore() |