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