mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-16 11:50:29 +00:00
feat(source): 重构下载模块并优化用户界面
- 重写 DownloadThread 类,使用 aria2c 实现更高效的下载 - 更新 ProgressWindow 类,增加更多下载信息显示 - 修改 MainWindow 类,改进下载失败的错误处理和用户提示 - 优化 animations.py 中的动画效果
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -169,3 +169,5 @@ cython_debug/
|
|||||||
|
|
||||||
# PyPI configuration file
|
# PyPI configuration file
|
||||||
.pypirc
|
.pypirc
|
||||||
|
|
||||||
|
nuitka-crash-report.xml
|
||||||
14
README.md
14
README.md
@@ -1,4 +1,4 @@
|
|||||||
# 🍓FRAISEMOE2-Installer🍓
|
# 🍓FRAISEMOE-Addons-Installer-NEXT🍓
|
||||||
|
|
||||||
```
|
```
|
||||||
🔊 注意:本库仍然努力更新中,大部分文档不可用,敬请谅解。
|
🔊 注意:本库仍然努力更新中,大部分文档不可用,敬请谅解。
|
||||||
@@ -63,7 +63,6 @@
|
|||||||
- [使用步骤](#使用步骤)
|
- [使用步骤](#使用步骤)
|
||||||
- [版本控制](#版本控制)
|
- [版本控制](#版本控制)
|
||||||
- [作者](#作者)
|
- [作者](#作者)
|
||||||
- [捐助/打赏本项目](#-捐助遵循自愿原则非强制一经赞赏无法退还)
|
|
||||||
- [特别鸣谢](#特别鸣谢)
|
- [特别鸣谢](#特别鸣谢)
|
||||||
- [协议](#协议)
|
- [协议](#协议)
|
||||||
|
|
||||||
@@ -96,6 +95,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 👨💻 作者
|
||||||
|
|
||||||
|
- [ouyangqiqi](https://github.com/hyb-oyqq): 本仓库现维护者
|
||||||
|
|
||||||
|
## 💡 注意事项
|
||||||
|
|
||||||
|
1. 请勿使用经过二次修改的应用:若使用未知来源或修改后的应用导致个人利益受损,作者和开发人员不承担任何责任。
|
||||||
|
2. 请遵循所有规则:请严格遵守 [使用须知文档](https://github.com/hyb-oyqq/FRAISEMOE2-Installer/blob/master/FAQ.md) 和本文档中的规则,如有违反,作者和开发人员不承担责任。
|
||||||
|
3. 免费开源:本应用免费、开源,如有通过非免费途径获取,请立即向来源申请退款并积极维权。
|
||||||
|
|
||||||
|
|
||||||
## 🎉 特别鸣谢
|
## 🎉 特别鸣谢
|
||||||
- [Yanam1Anna](https://github.com/Yanam1Anna): 本项目的原作者,提供了大量代码和资源。
|
- [Yanam1Anna](https://github.com/Yanam1Anna): 本项目的原作者,提供了大量代码和资源。
|
||||||
- [HTony03](https://github.com/HTony03):对于本项目部分源码的重构、逻辑优化和功能实现提供了大力支持。
|
- [HTony03](https://github.com/HTony03):对于本项目部分源码的重构、逻辑优化和功能实现提供了大力支持。
|
||||||
|
|||||||
267
source/Main.py
267
source/Main.py
@@ -24,6 +24,15 @@ from animations import MultiStageAnimations
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
def resource_path(relative_path):
|
||||||
|
"""获取资源的绝对路径,适用于开发环境和PyInstaller打包环境"""
|
||||||
|
try:
|
||||||
|
# PyInstaller创建的临时文件夹
|
||||||
|
base_path = sys._MEIPASS
|
||||||
|
except Exception:
|
||||||
|
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return os.path.join(base_path, relative_path)
|
||||||
|
|
||||||
def load_base64_image(base64_str):
|
def load_base64_image(base64_str):
|
||||||
pixmap = QPixmap()
|
pixmap = QPixmap()
|
||||||
pixmap.loadFromData(base64.b64decode(base64_str))
|
pixmap.loadFromData(base64.b64decode(base64_str))
|
||||||
@@ -31,13 +40,13 @@ def load_base64_image(base64_str):
|
|||||||
|
|
||||||
# 配置信息
|
# 配置信息
|
||||||
app_data = {
|
app_data = {
|
||||||
"APP_VERSION": "4.10.0.17496",
|
"APP_VERSION": "1.0.0",
|
||||||
"APP_NAME": "@FRAISEMOE2 Addons Installer",
|
"APP_NAME": "FRAISEMOE Addons Installer NEXT",
|
||||||
"TEMP": "TEMP",
|
"TEMP": "TEMP",
|
||||||
"CACHE": "FRAISEMOE",
|
"CACHE": "FRAISEMOE",
|
||||||
"PLUGIN": "PLUGIN",
|
"PLUGIN": "PLUGIN",
|
||||||
"CONFIG_URL": "aHR0cHM6Ly9hcmNoaXZlLm92b2Zpc2guY29tL2FwaS93aWRnZXQvbmVrb3BhcmEvZG93bmxvYWRfdXJsLmpzb24=",
|
"CONFIG_URL": "aHR0cHM6Ly9hcmNoaXZlLm92b2Zpc2guY29tL2FwaS93aWRnZXQvbmVrb3BhcmEvZG93bmxvYWRfdXJsX2RlYnVnLmpzb24=",
|
||||||
"UA": "TW96aWxsYS81LjAgKExpbnV4IGRlYmlhbjEyIEZyYWlzZU1vZS1BY2NlcHQpIEdlY2tvLzIwMTAwMTAxIEZpcmVmb3gvMTE0LjA=",
|
"UA": "TW96aWxsYS81LjAgKExpbnV4IGRlYmlhbjEyIEZyYWlzZU1vZTItQWNjZXB0KSBHZWNrby8yMDEwMDEwMSBGaXJlZm94LzExNC4wIEZyYWlzZU1vZTIvMS4wLjA=",
|
||||||
"game_info": {
|
"game_info": {
|
||||||
"NEKOPARA Vol.1": {
|
"NEKOPARA Vol.1": {
|
||||||
"exe": "nekopara_vol1.exe",
|
"exe": "nekopara_vol1.exe",
|
||||||
@@ -84,7 +93,7 @@ TEMP = os.getenv(app_data["TEMP"]) or app_data["TEMP"]
|
|||||||
CACHE = os.path.join(TEMP, app_data["CACHE"])
|
CACHE = os.path.join(TEMP, app_data["CACHE"])
|
||||||
PLUGIN = os.path.join(CACHE, app_data["PLUGIN"])
|
PLUGIN = os.path.join(CACHE, app_data["PLUGIN"])
|
||||||
CONFIG_URL = decode_base64(app_data["CONFIG_URL"])
|
CONFIG_URL = decode_base64(app_data["CONFIG_URL"])
|
||||||
UA = decode_base64(app_data["UA"]) + f" FraiseMoe/{APP_VERSION}"
|
UA = decode_base64(app_data["UA"])
|
||||||
GAME_INFO = app_data["game_info"]
|
GAME_INFO = app_data["game_info"]
|
||||||
BLOCK_SIZE = 67108864
|
BLOCK_SIZE = 67108864
|
||||||
HASH_SIZE = 134217728
|
HASH_SIZE = 134217728
|
||||||
@@ -266,56 +275,170 @@ class AdminPrivileges:
|
|||||||
|
|
||||||
# 下载线程类
|
# 下载线程类
|
||||||
class DownloadThread(QThread):
|
class DownloadThread(QThread):
|
||||||
progress = Signal(int)
|
progress = Signal(dict)
|
||||||
finished = Signal(bool, str)
|
finished = Signal(bool, str)
|
||||||
|
|
||||||
def __init__(self, url, _7z_path, parent=None):
|
def __init__(self, url, _7z_path, game_version, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.url = url
|
self.url = url
|
||||||
self._7z_path = _7z_path
|
self._7z_path = _7z_path
|
||||||
|
self.game_version = game_version
|
||||||
|
self.process = None
|
||||||
|
self.is_running = True
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.process and self.process.poll() is None:
|
||||||
|
self.is_running = False
|
||||||
|
# 使用 taskkill 强制终止进程及其子进程
|
||||||
|
subprocess.run(['taskkill', '/F', '/T', '/PID', str(self.process.pid)], check=True)
|
||||||
|
self.finished.emit(False, "下载已手动停止。")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"User-Agent": UA}
|
aria2c_path = resource_path("aria2c.exe")
|
||||||
r = requests.get(self.url, headers=headers, stream=True, timeout=10)
|
download_dir = os.path.dirname(self._7z_path)
|
||||||
r.raise_for_status()
|
file_name = os.path.basename(self._7z_path)
|
||||||
total_size = int(r.headers.get("content-length", 0))
|
|
||||||
with open(self._7z_path, "wb") as f:
|
parsed_url = urlparse(self.url)
|
||||||
for chunk in r.iter_content(chunk_size=BLOCK_SIZE):
|
referer = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
||||||
f.write(chunk)
|
|
||||||
self.progress.emit(f.tell() * 100 // total_size)
|
command = [
|
||||||
self.finished.emit(True, "")
|
aria2c_path,
|
||||||
except requests.exceptions.RequestException as e:
|
'--dir', download_dir,
|
||||||
self.finished.emit(False, f"\n网络请求错误\n\n【错误信息】: {e}\n")
|
'--out', file_name,
|
||||||
|
'--user-agent', UA,
|
||||||
|
'--referer', referer,
|
||||||
|
'--header', f'Origin: {referer.rstrip("/")}',
|
||||||
|
'--header', 'Accept: */*',
|
||||||
|
'--header', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
'--header', 'Accept-Encoding: gzip, deflate, br',
|
||||||
|
'--header', 'Cache-Control: no-cache',
|
||||||
|
'--header', 'Pragma: no-cache',
|
||||||
|
'--header', 'DNT: 1',
|
||||||
|
'--header', 'Sec-Fetch-Dest: empty',
|
||||||
|
'--header', 'Sec-Fetch-Mode: cors',
|
||||||
|
'--header', 'Sec-Fetch-Site: same-origin',
|
||||||
|
'--http-accept-gzip=true',
|
||||||
|
'--min-tls-version=TLSv1.2',
|
||||||
|
'--console-log-level=info',
|
||||||
|
'--summary-interval=1',
|
||||||
|
'--log-level=info',
|
||||||
|
'--allow-overwrite=true',
|
||||||
|
'--max-tries=3',
|
||||||
|
'--retry-wait=2',
|
||||||
|
'--connect-timeout=60',
|
||||||
|
'--timeout=60',
|
||||||
|
'--auto-file-renaming=false',
|
||||||
|
self.url
|
||||||
|
]
|
||||||
|
|
||||||
|
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
|
||||||
|
progress_pattern = re.compile(r'\((\d+)%\).*?CN:(\d+).*?DL:([\d\.]+[KMG]?i?B/s).*?ETA:([\w\d]+)')
|
||||||
|
|
||||||
|
full_output = []
|
||||||
|
while self.is_running and self.process.poll() is None:
|
||||||
|
line = self.process.stdout.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
full_output.append(line)
|
||||||
|
print(line.strip()) # 在控制台输出实时日志
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
return_code = self.process.wait()
|
||||||
|
|
||||||
|
if not self.is_running: # 如果是手动停止的
|
||||||
|
return
|
||||||
|
|
||||||
|
if return_code == 0:
|
||||||
|
self.progress.emit({
|
||||||
|
"game": self.game_version,
|
||||||
|
"percent": 100,
|
||||||
|
"threads": "N/A",
|
||||||
|
"speed": "N/A",
|
||||||
|
"eta": "完成"
|
||||||
|
})
|
||||||
|
self.finished.emit(True, "")
|
||||||
|
else:
|
||||||
|
error_message = f"\nAria2c下载失败,退出码: {return_code}\n\n--- Aria2c 输出 ---\n{''.join(full_output)}\n---------------------\n"
|
||||||
|
self.finished.emit(False, error_message)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.finished.emit(False, f"\n未知错误\n\n【错误信息】: {e}\n")
|
if self.is_running:
|
||||||
|
self.finished.emit(False, f"\n下载时发生未知错误\n\n【错误信息】: {e}\n")
|
||||||
|
|
||||||
# 下载进度窗口类
|
# 下载进度窗口类
|
||||||
class ProgressWindow(QDialog):
|
class ProgressWindow(QDialog):
|
||||||
|
# 添加一个信号,用于通知主窗口下载已停止
|
||||||
|
download_stopped = Signal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(ProgressWindow, self).__init__(parent)
|
super(ProgressWindow, self).__init__(parent)
|
||||||
self.setWindowTitle(f"下载进度 {APP_NAME}")
|
self.setWindowTitle(f"下载进度 {APP_NAME}")
|
||||||
self.resize(400, 100)
|
self.resize(450, 180)
|
||||||
self.progress_bar_max = 100
|
|
||||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowSystemMenuHint)
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowSystemMenuHint)
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
self.game_label = QLabel("正在准备下载...")
|
||||||
self.progress_bar = QProgressBar()
|
self.progress_bar = QProgressBar()
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.label = QLabel("\n正在下载...\n")
|
self.stats_label = QLabel("速度: - | 线程: - | 剩余时间: -")
|
||||||
layout.addWidget(self.label)
|
self.stop_button = QtWidgets.QPushButton("停止下载")
|
||||||
|
|
||||||
|
layout.addWidget(self.game_label)
|
||||||
layout.addWidget(self.progress_bar)
|
layout.addWidget(self.progress_bar)
|
||||||
|
layout.addWidget(self.stats_label)
|
||||||
|
layout.addWidget(self.stop_button)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
def setmaxvalue(self, value):
|
def update_progress(self, data):
|
||||||
self.progress_bar_max = value
|
game_version = data.get("game", "未知游戏")
|
||||||
self.progress_bar.setMaximum(value)
|
percent = data.get("percent", 0)
|
||||||
|
speed = data.get("speed", "-")
|
||||||
|
threads = data.get("threads", "-")
|
||||||
|
eta = data.get("eta", "-")
|
||||||
|
|
||||||
def setprogressbarval(self, value):
|
self.game_label.setText(f"正在下载: {game_version}")
|
||||||
self.progress_bar.setValue(value)
|
self.progress_bar.setValue(int(percent))
|
||||||
if value == self.progress_bar_max:
|
self.stats_label.setText(f"速度: {speed} | 线程: {threads} | 剩余时间: {eta}")
|
||||||
QTimer.singleShot(2000, self.close)
|
|
||||||
|
if percent == 100:
|
||||||
|
self.stop_button.setEnabled(False)
|
||||||
|
self.stop_button.setText("下载完成")
|
||||||
|
QTimer.singleShot(1500, self.accept)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
# 覆盖默认的关闭事件,防止用户通过其他方式关闭窗口
|
||||||
|
# 如果需要,可以在这里添加逻辑,例如询问用户是否要停止下载
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
def on_stop_clicked(self):
|
||||||
|
self.stop_button.setEnabled(False)
|
||||||
|
self.stop_button.setText("正在停止...")
|
||||||
|
self.download_stopped.emit()
|
||||||
|
self.reject() # 关闭窗口并返回一个QDialog.Rejected值
|
||||||
|
|
||||||
# 主窗口类
|
# 主窗口类
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
@@ -332,7 +455,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.setWindowIcon(QIcon(pixmap))
|
self.setWindowIcon(QIcon(pixmap))
|
||||||
|
|
||||||
# 设置窗口标题为APP_NAME加版本号
|
# 设置窗口标题为APP_NAME加版本号
|
||||||
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
self.setWindowTitle(f"{APP_NAME} vFraiseMoe2/1.0.0")
|
||||||
|
|
||||||
# 初始化动画系统 (从animations.py导入)
|
# 初始化动画系统 (从animations.py导入)
|
||||||
self.animator = MultiStageAnimations(self.ui)
|
self.animator = MultiStageAnimations(self.ui)
|
||||||
@@ -403,8 +526,13 @@ class MainWindow(QMainWindow):
|
|||||||
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
config_data = response.json()
|
config_data = response.json()
|
||||||
if not all(f"vol.{i+1}.data" in config_data for i in range(4)) or "after.data" not in config_data:
|
# 修正键名检查,确保所有必需的键都存在
|
||||||
raise ValueError("配置文件数据异常")
|
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 {
|
return {
|
||||||
f"vol{i+1}": config_data[f"vol.{i+1}.data"]["url"] for i in range(4)
|
f"vol{i+1}": config_data[f"vol.{i+1}.data"]["url"] for i in range(4)
|
||||||
} | {
|
} | {
|
||||||
@@ -451,35 +579,49 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_result()
|
self.show_result()
|
||||||
return
|
return
|
||||||
|
|
||||||
progress_window = ProgressWindow(self)
|
self.progress_window = ProgressWindow(self)
|
||||||
progress_window.show()
|
|
||||||
|
|
||||||
self.current_download_thread = DownloadThread(url, _7z_path, self)
|
self.current_download_thread = DownloadThread(url, _7z_path, game_version, self)
|
||||||
self.current_download_thread.progress.connect(progress_window.setprogressbarval)
|
self.current_download_thread.progress.connect(self.progress_window.update_progress)
|
||||||
self.current_download_thread.finished.connect(
|
self.current_download_thread.finished.connect(
|
||||||
lambda success, error: self.install_setting(
|
lambda success, error: self.install_setting(
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
progress_window,
|
self.progress_window,
|
||||||
|
url,
|
||||||
game_folder,
|
game_folder,
|
||||||
game_version,
|
game_version,
|
||||||
_7z_path,
|
_7z_path,
|
||||||
plugin_path,
|
plugin_path,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 连接停止按钮的信号
|
||||||
|
self.progress_window.stop_button.clicked.connect(self.current_download_thread.stop)
|
||||||
|
# 连接窗口关闭信号,以处理用户手动停止的情况
|
||||||
|
self.progress_window.download_stopped.connect(self.on_download_stopped)
|
||||||
|
|
||||||
self.current_download_thread.start()
|
self.current_download_thread.start()
|
||||||
|
self.progress_window.exec() # 使用exec()以模态方式显示对话框
|
||||||
|
|
||||||
def install_setting(
|
def install_setting(
|
||||||
self,
|
self,
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
progress_window,
|
progress_window,
|
||||||
|
url,
|
||||||
game_folder,
|
game_folder,
|
||||||
game_version,
|
game_version,
|
||||||
_7z_path,
|
_7z_path,
|
||||||
plugin_path,
|
plugin_path,
|
||||||
):
|
):
|
||||||
progress_window.close()
|
if not success and error == "下载已手动停止。":
|
||||||
|
# 用户手动停止了下载,不需要进行后续操作
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.progress_window.isVisible():
|
||||||
|
self.progress_window.close()
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
try:
|
try:
|
||||||
msg_box = self.hash_manager.hash_pop_window()
|
msg_box = self.hash_manager.hash_pop_window()
|
||||||
@@ -499,9 +641,6 @@ class MainWindow(QMainWindow):
|
|||||||
shutil.copy(sig_path, game_folder)
|
shutil.copy(sig_path, game_folder)
|
||||||
|
|
||||||
self.installed_status[game_version] = True
|
self.installed_status[game_version] = True
|
||||||
QtWidgets.QMessageBox.information(
|
|
||||||
self, f"通知 {APP_NAME}", f"\n{game_version} 补丁已安装\n"
|
|
||||||
)
|
|
||||||
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
@@ -510,13 +649,35 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
msg_box.close()
|
msg_box.close()
|
||||||
|
self.next_download_task()
|
||||||
else:
|
else:
|
||||||
QtWidgets.QMessageBox.critical(
|
print(f"--- Download Failed: {game_version} ---")
|
||||||
self,
|
print(error)
|
||||||
f"错误 {APP_NAME}",
|
print("------------------------------------")
|
||||||
f"\n文件获取失败\n网络状态异常或服务器故障\n\n【错误信息】:{error}\n",
|
msg_box = QtWidgets.QMessageBox(self)
|
||||||
)
|
msg_box.setWindowTitle(f"下载失败 {APP_NAME}")
|
||||||
self.next_download_task()
|
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):
|
def pre_hash_compare(self, install_path, game_version, plugin_hash):
|
||||||
msg_box = self.hash_manager.hash_pop_window()
|
msg_box = self.hash_manager.hash_pop_window()
|
||||||
@@ -570,9 +731,19 @@ class MainWindow(QMainWindow):
|
|||||||
if not self.download_queue:
|
if not self.download_queue:
|
||||||
self.after_hash_compare(PLUGIN_HASH)
|
self.after_hash_compare(PLUGIN_HASH)
|
||||||
return
|
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()
|
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)
|
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):
|
def after_hash_compare(self, plugin_hash):
|
||||||
msg_box = self.hash_manager.hash_pop_window()
|
msg_box = self.hash_manager.hash_pop_window()
|
||||||
result = self.hash_manager.cfg_after_hash_compare(
|
result = self.hash_manager.cfg_after_hash_compare(
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class MultiStageAnimations:
|
|||||||
pos_anim.setDuration(duration)
|
pos_anim.setDuration(duration)
|
||||||
pos_anim.setStartValue(QPoint(-widget.width(), end_pos.y()))
|
pos_anim.setStartValue(QPoint(-widget.width(), end_pos.y()))
|
||||||
pos_anim.setEndValue(end_pos)
|
pos_anim.setEndValue(end_pos)
|
||||||
pos_anim.setEasingCurve(QEasingCurve.OutBack)
|
pos_anim.setEasingCurve(QEasingCurve.Type.OutBack)
|
||||||
|
|
||||||
# 透明度动画
|
# 透明度动画
|
||||||
opacity_anim = QPropertyAnimation(widget.graphicsEffect(), b"opacity")
|
opacity_anim = QPropertyAnimation(widget.graphicsEffect(), b"opacity")
|
||||||
@@ -107,16 +107,6 @@ class MultiStageAnimations:
|
|||||||
anim_group.start()
|
anim_group.start()
|
||||||
self.animations.append(anim_group)
|
self.animations.append(anim_group)
|
||||||
|
|
||||||
def start_mainbg_animation(self):
|
|
||||||
"""启动主背景淡入动画"""
|
|
||||||
main_anim = QPropertyAnimation(self.ui.Mainbg.graphicsEffect(), b"opacity")
|
|
||||||
main_anim.setDuration(800)
|
|
||||||
main_anim.setStartValue(0)
|
|
||||||
main_anim.setEndValue(1)
|
|
||||||
main_anim.finished.connect(self.start_menu_animations)
|
|
||||||
main_anim.start()
|
|
||||||
self.animations.append(main_anim)
|
|
||||||
|
|
||||||
def start_mainbg_animation(self):
|
def start_mainbg_animation(self):
|
||||||
"""启动主背景淡入动画(带延迟)"""
|
"""启动主背景淡入动画(带延迟)"""
|
||||||
main_anim = QPropertyAnimation(self.ui.Mainbg.graphicsEffect(), b"opacity")
|
main_anim = QPropertyAnimation(self.ui.Mainbg.graphicsEffect(), b"opacity")
|
||||||
@@ -141,7 +131,7 @@ class MultiStageAnimations:
|
|||||||
pos_anim.setDuration(item["duration"])
|
pos_anim.setDuration(item["duration"])
|
||||||
pos_anim.setStartValue(QPoint(item["end_pos"].x(), self.canvas_height + 100))
|
pos_anim.setStartValue(QPoint(item["end_pos"].x(), self.canvas_height + 100))
|
||||||
pos_anim.setEndValue(item["end_pos"])
|
pos_anim.setEndValue(item["end_pos"])
|
||||||
pos_anim.setEasingCurve(QEasingCurve.OutBack)
|
pos_anim.setEasingCurve(QEasingCurve.Type.OutBack)
|
||||||
|
|
||||||
# 透明度动画
|
# 透明度动画
|
||||||
opacity_anim = QPropertyAnimation(item["widget"].graphicsEffect(), b"opacity")
|
opacity_anim = QPropertyAnimation(item["widget"].graphicsEffect(), b"opacity")
|
||||||
|
|||||||
BIN
source/aria2c.exe
Normal file
BIN
source/aria2c.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user