mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-16 11:50:29 +00:00
feat(core): 增强日志记录和错误处理功能
- 更新日志记录机制,将日志文件存储在程序根目录下的log文件夹中,并使用日期+时间戳格式命名。 - 在多个模块中添加详细的错误处理逻辑,确保在发生异常时能够记录相关信息,便于后续排查。 - 优化UI管理器中的日志文件打开功能,增加对日志文件存在性和大小的检查,提升用户体验。 - 在下载管理器和补丁管理器中增强调试信息的记录,确保在关键操作中提供更清晰的反馈。
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -179,3 +179,4 @@ vol.1.7z
|
||||
vol.2.7z
|
||||
vol.3.7z
|
||||
vol.4.7z
|
||||
log/
|
||||
|
||||
@@ -1,13 +1,44 @@
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||
from main_window import MainWindow
|
||||
from core.privacy_manager import PrivacyManager
|
||||
from utils.logger import setup_logger
|
||||
from data.config import LOG_FILE, APP_NAME
|
||||
from utils import load_config
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 设置主日志
|
||||
logger = setup_logger("main")
|
||||
logger.info("应用启动")
|
||||
|
||||
# 检查配置中是否启用了调试模式
|
||||
config = load_config()
|
||||
debug_mode = config.get("debug_mode", False)
|
||||
|
||||
# 如果调试模式已启用,确保立即创建主日志文件
|
||||
if debug_mode:
|
||||
try:
|
||||
# 确保log目录存在
|
||||
log_dir = os.path.dirname(LOG_FILE)
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
logger.info(f"已创建日志目录: {log_dir}")
|
||||
|
||||
# 创建新的日志文件(使用覆盖模式)
|
||||
with open(LOG_FILE, 'w', encoding='utf-8') as f:
|
||||
current_time = datetime.datetime.now()
|
||||
formatted_date = current_time.strftime("%Y-%m-%d")
|
||||
formatted_time = current_time.strftime("%H:%M:%S")
|
||||
f.write(f"--- 新调试会话开始于 {os.path.basename(LOG_FILE)} ---\n")
|
||||
f.write(f"--- 应用版本: {APP_NAME} ---\n")
|
||||
f.write(f"--- 日期: {formatted_date} 时间: {formatted_time} ---\n\n")
|
||||
|
||||
logger.info(f"调试模式已启用,日志文件路径: {os.path.abspath(LOG_FILE)}")
|
||||
except Exception as e:
|
||||
logger.error(f"创建日志文件失败: {e}")
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
try:
|
||||
|
||||
@@ -4,6 +4,8 @@ from PySide6 import QtWidgets
|
||||
from data.config import LOG_FILE
|
||||
from utils.logger import setup_logger
|
||||
from utils import Logger
|
||||
import datetime
|
||||
from data.config import APP_NAME
|
||||
|
||||
# 初始化logger
|
||||
logger = setup_logger("debug_manager")
|
||||
@@ -60,6 +62,25 @@ class DebugManager:
|
||||
self.main_window.config["debug_mode"] = checked
|
||||
self.main_window.save_config(self.main_window.config)
|
||||
|
||||
# 创建或删除debug_mode.txt标记文件
|
||||
try:
|
||||
from data.config import CACHE
|
||||
debug_file = os.path.join(os.path.dirname(CACHE), "debug_mode.txt")
|
||||
|
||||
if checked:
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(debug_file), exist_ok=True)
|
||||
# 创建标记文件
|
||||
with open(debug_file, 'w', encoding='utf-8') as f:
|
||||
f.write(f"Debug mode enabled at {os.path.abspath(debug_file)}\n")
|
||||
logger.info(f"已创建调试模式标记文件: {debug_file}")
|
||||
elif os.path.exists(debug_file):
|
||||
# 删除标记文件
|
||||
os.remove(debug_file)
|
||||
logger.info(f"已删除调试模式标记文件: {debug_file}")
|
||||
except Exception as e:
|
||||
logger.warning(f"处理调试模式标记文件时发生错误: {e}")
|
||||
|
||||
# 更新打开log文件按钮状态
|
||||
if hasattr(self, 'ui_manager') and hasattr(self.ui_manager, 'open_log_action'):
|
||||
self.ui_manager.open_log_action.setEnabled(checked)
|
||||
@@ -88,16 +109,32 @@ class DebugManager:
|
||||
"""启动日志记录"""
|
||||
if self.logger is None:
|
||||
try:
|
||||
if os.path.exists(LOG_FILE):
|
||||
os.remove(LOG_FILE)
|
||||
# 确保log目录存在
|
||||
log_dir = os.path.dirname(LOG_FILE)
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
logger.info(f"已创建日志目录: {log_dir}")
|
||||
|
||||
# 创建新的日志文件,使用覆盖模式而不是追加模式
|
||||
with open(LOG_FILE, 'w', encoding='utf-8') as f:
|
||||
current_time = datetime.datetime.now()
|
||||
formatted_date = current_time.strftime("%Y-%m-%d")
|
||||
formatted_time = current_time.strftime("%H:%M:%S")
|
||||
f.write(f"--- 新调试会话开始于 {os.path.basename(LOG_FILE)} ---\n")
|
||||
f.write(f"--- 应用版本: {APP_NAME} ---\n")
|
||||
f.write(f"--- 日期: {formatted_date} 时间: {formatted_time} ---\n\n")
|
||||
logger.info(f"已创建日志文件: {os.path.abspath(LOG_FILE)}")
|
||||
|
||||
# 保存原始的 stdout 和 stderr
|
||||
self.original_stdout = sys.stdout
|
||||
self.original_stderr = sys.stderr
|
||||
|
||||
# 创建 Logger 实例
|
||||
self.logger = Logger(LOG_FILE, self.original_stdout)
|
||||
sys.stdout = self.logger
|
||||
sys.stderr = self.logger
|
||||
logger.info("--- Debug mode enabled ---")
|
||||
|
||||
logger.info(f"--- Debug mode enabled (log file: {os.path.abspath(LOG_FILE)}) ---")
|
||||
except (IOError, OSError) as e:
|
||||
QtWidgets.QMessageBox.critical(self.main_window, "错误", f"无法创建日志文件: {e}")
|
||||
self.logger = None
|
||||
|
||||
@@ -260,11 +260,19 @@ class DownloadManager:
|
||||
disabled_patch_games = [] # 存储检测到禁用补丁的游戏
|
||||
|
||||
for game_version, game_dir in game_dirs.items():
|
||||
# 检查游戏是否已安装补丁
|
||||
if self.main_window.installed_status.get(game_version, False):
|
||||
# 首先通过文件检查确认补丁是否已安装
|
||||
is_patch_installed = self.main_window.patch_manager.check_patch_installed(game_dir, game_version)
|
||||
# 同时考虑哈希检查结果
|
||||
hash_check_passed = self.main_window.installed_status.get(game_version, False)
|
||||
|
||||
# 如果补丁文件存在或哈希检查通过,认为已安装
|
||||
if is_patch_installed or hash_check_passed:
|
||||
if debug_mode:
|
||||
logger.info(f"DEBUG: {game_version} 已安装补丁,不需要再次安装")
|
||||
logger.info(f"DEBUG: 文件检查结果: {is_patch_installed}, 哈希检查结果: {hash_check_passed}")
|
||||
already_installed_games.append(game_version)
|
||||
# 更新安装状态
|
||||
self.main_window.installed_status[game_version] = True
|
||||
else:
|
||||
# 检查是否存在被禁用的补丁
|
||||
is_disabled, disabled_path = self.main_window.patch_manager.check_patch_disabled(game_dir, game_version)
|
||||
@@ -275,6 +283,7 @@ class DownloadManager:
|
||||
else:
|
||||
if debug_mode:
|
||||
logger.info(f"DEBUG: {game_version} 未安装补丁,可以安装")
|
||||
logger.info(f"DEBUG: 文件检查结果: {is_patch_installed}, 哈希检查结果: {hash_check_passed}")
|
||||
installable_games.append(game_version)
|
||||
|
||||
status_message = ""
|
||||
@@ -748,9 +757,35 @@ class DownloadManager:
|
||||
|
||||
self.main_window.setEnabled(True)
|
||||
|
||||
# 分析错误类型
|
||||
error_type = "未知错误"
|
||||
suggestion = ""
|
||||
|
||||
if "SSL/TLS handshake failure" in error:
|
||||
error_type = "SSL/TLS连接失败"
|
||||
suggestion = "可能是由于网络连接不稳定或证书问题,建议:\n1. 检查网络连接\n2. 尝试使用其他网络\n3. 确保系统时间和日期正确\n4. 可能需要使用代理或VPN"
|
||||
elif "Connection timed out" in error or "read timed out" in error:
|
||||
error_type = "连接超时"
|
||||
suggestion = "下载服务器响应时间过长,建议:\n1. 检查网络连接\n2. 稍后重试\n3. 使用优化网络选项"
|
||||
elif "404" in error:
|
||||
error_type = "文件不存在"
|
||||
suggestion = "请求的文件不存在或已移除,请联系开发者"
|
||||
elif "403" in error:
|
||||
error_type = "访问被拒绝"
|
||||
suggestion = "服务器拒绝请求,可能需要使用优化网络选项"
|
||||
elif "No space left on device" in error or "空间不足" in error:
|
||||
error_type = "存储空间不足"
|
||||
suggestion = "请确保有足够的磁盘空间用于下载和解压文件"
|
||||
|
||||
msg_box = QtWidgets.QMessageBox(self.main_window)
|
||||
msg_box.setWindowTitle(f"下载失败 - {APP_NAME}")
|
||||
msg_box.setText(f"\n文件获取失败: {game_version}\n错误: {error}\n\n是否重试?")
|
||||
error_message = f"\n文件获取失败: {game_version}\n错误类型: {error_type}"
|
||||
|
||||
if suggestion:
|
||||
error_message += f"\n\n可能的解决方案:\n{suggestion}"
|
||||
|
||||
error_message += "\n\n是否重试?"
|
||||
msg_box.setText(error_message)
|
||||
|
||||
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||||
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
from utils.logger import setup_logger
|
||||
|
||||
class GameDetector:
|
||||
"""游戏检测器,用于识别游戏目录和版本"""
|
||||
@@ -14,6 +15,7 @@ class GameDetector:
|
||||
self.game_info = game_info
|
||||
self.debug_manager = debug_manager
|
||||
self.directory_cache = {} # 添加目录缓存
|
||||
self.logger = setup_logger("game_detector")
|
||||
|
||||
def _is_debug_mode(self):
|
||||
"""检查是否处于调试模式
|
||||
@@ -37,7 +39,7 @@ class GameDetector:
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 尝试识别游戏版本: {game_dir}")
|
||||
self.logger.debug(f"尝试识别游戏版本: {game_dir}")
|
||||
|
||||
# 先通过目录名称进行初步推测(这将作为递归搜索的提示)
|
||||
dir_name = os.path.basename(game_dir).lower()
|
||||
@@ -51,11 +53,11 @@ class GameDetector:
|
||||
vol_num = vol_match.group(1)
|
||||
potential_version = f"NEKOPARA Vol.{vol_num}"
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 从目录名推测游戏版本: {potential_version}, 卷号: {vol_num}")
|
||||
self.logger.debug(f"从目录名推测游戏版本: {potential_version}, 卷号: {vol_num}")
|
||||
elif "after" in dir_name:
|
||||
potential_version = "NEKOPARA After"
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 从目录名推测游戏版本: NEKOPARA After")
|
||||
self.logger.debug(f"从目录名推测游戏版本: NEKOPARA After")
|
||||
|
||||
# 检查是否为NEKOPARA游戏目录
|
||||
# 通过检查游戏可执行文件来识别游戏版本
|
||||
@@ -88,7 +90,7 @@ class GameDetector:
|
||||
exe_path = os.path.join(game_dir, exe_variant)
|
||||
if os.path.exists(exe_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 通过可执行文件确认游戏版本: {game_version}, 文件: {exe_variant}")
|
||||
self.logger.debug(f"通过可执行文件确认游戏版本: {game_version}, 文件: {exe_variant}")
|
||||
return game_version
|
||||
|
||||
# 如果没有直接匹配,尝试递归搜索
|
||||
@@ -111,17 +113,17 @@ class GameDetector:
|
||||
f"vol {vol_num}" in file_lower)) or
|
||||
(is_after and "after" in file_lower)):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 通过递归搜索确认游戏版本: {potential_version}, 文件: {file}")
|
||||
self.logger.debug(f"通过递归搜索确认游戏版本: {potential_version}, 文件: {file}")
|
||||
return potential_version
|
||||
|
||||
# 如果仍然没有找到,基于目录名的推测返回结果
|
||||
if potential_version:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 基于目录名返回推测的游戏版本: {potential_version}")
|
||||
self.logger.debug(f"基于目录名返回推测的游戏版本: {potential_version}")
|
||||
return potential_version
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 无法识别游戏版本: {game_dir}")
|
||||
self.logger.debug(f"无法识别游戏版本: {game_dir}")
|
||||
|
||||
return None
|
||||
|
||||
@@ -139,11 +141,11 @@ class GameDetector:
|
||||
# 检查缓存中是否已有该目录的识别结果
|
||||
if selected_folder in self.directory_cache:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 使用缓存的目录识别结果: {selected_folder}")
|
||||
self.logger.debug(f"使用缓存的目录识别结果: {selected_folder}")
|
||||
return self.directory_cache[selected_folder]
|
||||
|
||||
if debug_mode:
|
||||
print(f"--- 开始识别目录: {selected_folder} ---")
|
||||
self.logger.debug(f"--- 开始识别目录: {selected_folder} ---")
|
||||
|
||||
game_paths = {}
|
||||
|
||||
@@ -151,10 +153,10 @@ class GameDetector:
|
||||
try:
|
||||
all_dirs = [d for d in os.listdir(selected_folder) if os.path.isdir(os.path.join(selected_folder, d))]
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到以下子目录: {all_dirs}")
|
||||
self.logger.debug(f"找到以下子目录: {all_dirs}")
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 无法读取目录 {selected_folder}: {str(e)}")
|
||||
self.logger.debug(f"无法读取目录 {selected_folder}: {str(e)}")
|
||||
return {}
|
||||
|
||||
for game, info in self.game_info.items():
|
||||
@@ -162,7 +164,7 @@ class GameDetector:
|
||||
expected_exe = info["exe"] # 标准可执行文件名
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 搜索游戏 {game}, 预期目录: {expected_dir}, 预期可执行文件: {expected_exe}")
|
||||
self.logger.debug(f"搜索游戏 {game}, 预期目录: {expected_dir}, 预期可执行文件: {expected_exe}")
|
||||
|
||||
# 尝试不同的匹配方法
|
||||
found_dir = None
|
||||
@@ -171,7 +173,7 @@ class GameDetector:
|
||||
if expected_dir in all_dirs:
|
||||
found_dir = expected_dir
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 精确匹配成功: {expected_dir}")
|
||||
self.logger.debug(f"精确匹配成功: {expected_dir}")
|
||||
|
||||
# 2. 大小写不敏感匹配
|
||||
if not found_dir:
|
||||
@@ -179,7 +181,7 @@ class GameDetector:
|
||||
if expected_dir.lower() == dir_name.lower():
|
||||
found_dir = dir_name
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 大小写不敏感匹配成功: {dir_name}")
|
||||
self.logger.debug(f"大小写不敏感匹配成功: {dir_name}")
|
||||
break
|
||||
|
||||
# 3. 更模糊的匹配(允许特殊字符差异)
|
||||
@@ -193,7 +195,7 @@ class GameDetector:
|
||||
if pattern.match(dir_name):
|
||||
found_dir = dir_name
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 模糊匹配成功: {dir_name} 匹配模式 {pattern_text}")
|
||||
self.logger.debug(f"模糊匹配成功: {dir_name} 匹配模式 {pattern_text}")
|
||||
break
|
||||
|
||||
# 4. 如果还是没找到,尝试更宽松的匹配
|
||||
@@ -203,7 +205,7 @@ class GameDetector:
|
||||
if vol_match:
|
||||
vol_num = vol_match.group(1)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 提取卷号: {vol_num}")
|
||||
self.logger.debug(f"提取卷号: {vol_num}")
|
||||
|
||||
is_after = "after" in expected_dir.lower()
|
||||
|
||||
@@ -214,7 +216,7 @@ class GameDetector:
|
||||
if is_after and "after" in dir_lower:
|
||||
found_dir = dir_name
|
||||
if debug_mode:
|
||||
print(f"DEBUG: After特殊匹配成功: {dir_name}")
|
||||
self.logger.debug(f"After特殊匹配成功: {dir_name}")
|
||||
break
|
||||
|
||||
# 对于Vol特殊处理
|
||||
@@ -224,7 +226,7 @@ class GameDetector:
|
||||
if dir_vol_match and dir_vol_match.group(1) == vol_num:
|
||||
found_dir = dir_name
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 卷号匹配成功: {dir_name} 卷号 {vol_num}")
|
||||
self.logger.debug(f"卷号匹配成功: {dir_name} 卷号 {vol_num}")
|
||||
break
|
||||
|
||||
# 如果找到匹配的目录,验证exe文件是否存在
|
||||
@@ -267,7 +269,7 @@ class GameDetector:
|
||||
exe_exists = True
|
||||
found_exe = exe_variant
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 验证成功,找到游戏可执行文件: {exe_variant}")
|
||||
self.logger.debug(f"验证成功,找到游戏可执行文件: {exe_variant}")
|
||||
break
|
||||
|
||||
# 如果没有直接找到,尝试递归搜索当前目录下的所有可执行文件
|
||||
@@ -290,14 +292,14 @@ class GameDetector:
|
||||
exe_exists = True
|
||||
found_exe = os.path.relpath(exe_path, potential_path)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 通过递归搜索找到游戏可执行文件: {found_exe}")
|
||||
self.logger.debug(f"通过递归搜索找到游戏可执行文件: {found_exe}")
|
||||
break
|
||||
elif "After" in game and "after" in file_lower:
|
||||
exe_path = os.path.join(root, file)
|
||||
exe_exists = True
|
||||
found_exe = os.path.relpath(exe_path, potential_path)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 通过递归搜索找到After游戏可执行文件: {found_exe}")
|
||||
self.logger.debug(f"通过递归搜索找到After游戏可执行文件: {found_exe}")
|
||||
break
|
||||
if exe_exists:
|
||||
break
|
||||
@@ -306,14 +308,14 @@ class GameDetector:
|
||||
if exe_exists:
|
||||
game_paths[game] = potential_path
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 验证成功,将 {potential_path} 添加为 {game} 的目录")
|
||||
self.logger.debug(f"验证成功,将 {potential_path} 添加为 {game} 的目录")
|
||||
else:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 未找到任何可执行文件变体,游戏 {game} 在 {potential_path} 未找到")
|
||||
self.logger.debug(f"未找到任何可执行文件变体,游戏 {game} 在 {potential_path} 未找到")
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 最终识别的游戏目录: {game_paths}")
|
||||
print(f"--- 目录识别结束 ---")
|
||||
self.logger.debug(f"最终识别的游戏目录: {game_paths}")
|
||||
self.logger.debug(f"--- 目录识别结束 ---")
|
||||
|
||||
# 将识别结果存入缓存
|
||||
self.directory_cache[selected_folder] = game_paths
|
||||
@@ -324,4 +326,4 @@ class GameDetector:
|
||||
"""清除目录缓存"""
|
||||
self.directory_cache = {}
|
||||
if self._is_debug_mode():
|
||||
print("DEBUG: 已清除目录缓存")
|
||||
self.logger.debug("已清除目录缓存")
|
||||
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from utils.logger import setup_logger
|
||||
|
||||
class PatchManager:
|
||||
"""补丁管理器,用于处理补丁的安装和卸载"""
|
||||
@@ -17,6 +19,7 @@ class PatchManager:
|
||||
self.game_info = game_info
|
||||
self.debug_manager = debug_manager
|
||||
self.installed_status = {} # 游戏版本的安装状态
|
||||
self.logger = setup_logger("patch_manager")
|
||||
|
||||
def _is_debug_mode(self):
|
||||
"""检查是否处于调试模式
|
||||
@@ -70,7 +73,7 @@ class PatchManager:
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}")
|
||||
self.logger.debug(f"开始卸载 {game_version} 补丁,目录: {game_dir}")
|
||||
|
||||
if game_version not in self.game_info:
|
||||
if not silent:
|
||||
@@ -99,7 +102,7 @@ class PatchManager:
|
||||
]
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 查找以下可能的补丁文件路径: {patch_files_to_check}")
|
||||
self.logger.debug(f"查找以下可能的补丁文件路径: {patch_files_to_check}")
|
||||
|
||||
# 查找并删除补丁文件,包括启用和禁用的
|
||||
patch_file_found = False
|
||||
@@ -110,7 +113,7 @@ class PatchManager:
|
||||
os.remove(patch_path)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除补丁文件: {patch_path}")
|
||||
self.logger.debug(f"已删除补丁文件: {patch_path}")
|
||||
|
||||
# 检查被禁用的补丁文件(带.fain后缀)
|
||||
disabled_path = f"{patch_path}.fain"
|
||||
@@ -119,11 +122,11 @@ class PatchManager:
|
||||
os.remove(disabled_path)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除被禁用的补丁文件: {disabled_path}")
|
||||
self.logger.debug(f"已删除被禁用的补丁文件: {disabled_path}")
|
||||
|
||||
if not patch_file_found and debug_mode:
|
||||
print(f"DEBUG: 未找到补丁文件,检查了以下路径: {patch_files_to_check}")
|
||||
print(f"DEBUG: 也检查了禁用的补丁文件(.fain后缀)")
|
||||
self.logger.debug(f"未找到补丁文件,检查了以下路径: {patch_files_to_check}")
|
||||
self.logger.debug(f"也检查了禁用的补丁文件(.fain后缀)")
|
||||
|
||||
# 检查是否有额外的签名文件 (.sig)
|
||||
if game_version == "NEKOPARA After":
|
||||
@@ -134,7 +137,7 @@ class PatchManager:
|
||||
os.remove(sig_file_path)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除签名文件: {sig_file_path}")
|
||||
self.logger.debug(f"已删除签名文件: {sig_file_path}")
|
||||
|
||||
# 检查被禁用补丁的签名文件
|
||||
disabled_sig_path = f"{patch_path}.fain.sig"
|
||||
@@ -142,7 +145,7 @@ class PatchManager:
|
||||
os.remove(disabled_sig_path)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除被禁用补丁的签名文件: {disabled_sig_path}")
|
||||
self.logger.debug(f"已删除被禁用补丁的签名文件: {disabled_sig_path}")
|
||||
|
||||
# 删除patch文件夹
|
||||
patch_folders_to_check = [
|
||||
@@ -156,7 +159,7 @@ class PatchManager:
|
||||
shutil.rmtree(patch_folder)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除补丁文件夹: {patch_folder}")
|
||||
self.logger.debug(f"已删除补丁文件夹: {patch_folder}")
|
||||
|
||||
# 删除game/patch文件夹
|
||||
game_folders = ["game", "Game", "GAME"]
|
||||
@@ -169,7 +172,7 @@ class PatchManager:
|
||||
shutil.rmtree(game_patch_folder)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除game/patch文件夹: {game_patch_folder}")
|
||||
self.logger.debug(f"已删除game/patch文件夹: {game_patch_folder}")
|
||||
|
||||
# 删除配置文件
|
||||
config_files = ["config.json", "Config.json", "CONFIG.JSON"]
|
||||
@@ -185,7 +188,7 @@ class PatchManager:
|
||||
os.remove(config_path)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除配置文件: {config_path}")
|
||||
self.logger.debug(f"已删除配置文件: {config_path}")
|
||||
|
||||
# 删除脚本文件
|
||||
for script_file in script_files:
|
||||
@@ -194,7 +197,7 @@ class PatchManager:
|
||||
os.remove(script_path)
|
||||
files_removed += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已删除脚本文件: {script_path}")
|
||||
self.logger.debug(f"已删除脚本文件: {script_path}")
|
||||
|
||||
# 更新安装状态
|
||||
self.installed_status[game_version] = False
|
||||
@@ -228,9 +231,9 @@ class PatchManager:
|
||||
# 显示卸载失败消息
|
||||
error_message = f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n"
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 卸载错误 - {str(e)}")
|
||||
self.logger.debug(f"卸载错误 - {str(e)}")
|
||||
import traceback
|
||||
print(f"DEBUG: 错误详情:\n{traceback.format_exc()}")
|
||||
self.logger.debug(f"错误详情:\n{traceback.format_exc()}")
|
||||
|
||||
QMessageBox.critical(
|
||||
None,
|
||||
@@ -288,7 +291,7 @@ class PatchManager:
|
||||
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 卸载 {version} 时出错: {str(e)}")
|
||||
self.logger.debug(f"卸载 {version} 时出错: {str(e)}")
|
||||
fail_count += 1
|
||||
results.append({
|
||||
"version": version,
|
||||
@@ -359,13 +362,13 @@ class PatchManager:
|
||||
for patch_path in patch_files_to_check:
|
||||
if os.path.exists(patch_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到补丁文件: {patch_path}")
|
||||
self.logger.debug(f"找到补丁文件: {patch_path}")
|
||||
return True
|
||||
# 检查是否存在被禁用的补丁文件(带.fain后缀)
|
||||
disabled_path = f"{patch_path}.fain"
|
||||
if os.path.exists(disabled_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到被禁用的补丁文件: {disabled_path}")
|
||||
self.logger.debug(f"找到被禁用的补丁文件: {disabled_path}")
|
||||
return True
|
||||
|
||||
# 检查是否有补丁文件夹
|
||||
@@ -378,7 +381,7 @@ class PatchManager:
|
||||
for patch_folder in patch_folders_to_check:
|
||||
if os.path.exists(patch_folder):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到补丁文件夹: {patch_folder}")
|
||||
self.logger.debug(f"找到补丁文件夹: {patch_folder}")
|
||||
return True
|
||||
|
||||
# 检查game/patch文件夹
|
||||
@@ -390,7 +393,7 @@ class PatchManager:
|
||||
game_patch_folder = os.path.join(game_dir, game_folder, patch_folder)
|
||||
if os.path.exists(game_patch_folder):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到game/patch文件夹: {game_patch_folder}")
|
||||
self.logger.debug(f"找到game/patch文件夹: {game_patch_folder}")
|
||||
return True
|
||||
|
||||
# 检查配置文件
|
||||
@@ -405,7 +408,7 @@ class PatchManager:
|
||||
config_path = os.path.join(game_path, config_file)
|
||||
if os.path.exists(config_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到配置文件: {config_path}")
|
||||
self.logger.debug(f"找到配置文件: {config_path}")
|
||||
return True
|
||||
|
||||
# 检查脚本文件
|
||||
@@ -413,12 +416,12 @@ class PatchManager:
|
||||
script_path = os.path.join(game_path, script_file)
|
||||
if os.path.exists(script_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到脚本文件: {script_path}")
|
||||
self.logger.debug(f"找到脚本文件: {script_path}")
|
||||
return True
|
||||
|
||||
# 没有找到补丁文件或文件夹
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {game_version} 在 {game_dir} 中没有安装补丁")
|
||||
self.logger.debug(f"{game_version} 在 {game_dir} 中没有安装补丁")
|
||||
return False
|
||||
|
||||
def check_patch_disabled(self, game_dir, game_version):
|
||||
@@ -454,11 +457,11 @@ class PatchManager:
|
||||
for disabled_path in disabled_patch_files:
|
||||
if os.path.exists(disabled_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到禁用的补丁文件: {disabled_path}")
|
||||
self.logger.debug(f"找到禁用的补丁文件: {disabled_path}")
|
||||
return True, disabled_path
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {game_version} 在 {game_dir} 的补丁未被禁用")
|
||||
self.logger.debug(f"{game_version} 在 {game_dir} 的补丁未被禁用")
|
||||
|
||||
return False, None
|
||||
|
||||
@@ -477,11 +480,11 @@ class PatchManager:
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始切换补丁状态 - 游戏版本: {game_version}, 游戏目录: {game_dir}, 操作: {operation}")
|
||||
self.logger.debug(f"开始切换补丁状态 - 游戏版本: {game_version}, 游戏目录: {game_dir}, 操作: {operation}")
|
||||
|
||||
if game_version not in self.game_info:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 无法识别游戏版本: {game_version}")
|
||||
self.logger.debug(f"无法识别游戏版本: {game_version}")
|
||||
if not silent:
|
||||
QMessageBox.critical(
|
||||
None,
|
||||
@@ -494,11 +497,11 @@ class PatchManager:
|
||||
# 检查补丁是否已安装
|
||||
is_patch_installed = self.check_patch_installed(game_dir, game_version)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁安装状态检查结果: {is_patch_installed}")
|
||||
self.logger.debug(f"补丁安装状态检查结果: {is_patch_installed}")
|
||||
|
||||
if not is_patch_installed:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {game_version} 未安装补丁,无法进行禁用/启用操作")
|
||||
self.logger.debug(f"{game_version} 未安装补丁,无法进行禁用/启用操作")
|
||||
if not silent:
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
@@ -512,7 +515,7 @@ class PatchManager:
|
||||
# 检查当前状态
|
||||
is_disabled, disabled_path = self.check_patch_disabled(game_dir, game_version)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁禁用状态检查结果 - 是否禁用: {is_disabled}, 禁用路径: {disabled_path}")
|
||||
self.logger.debug(f"补丁禁用状态检查结果 - 是否禁用: {is_disabled}, 禁用路径: {disabled_path}")
|
||||
|
||||
# 获取可能的补丁文件路径
|
||||
install_path_base = os.path.basename(self.game_info[game_version]["install_path"])
|
||||
@@ -528,7 +531,7 @@ class PatchManager:
|
||||
]
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 将检查以下可能的补丁文件: {patch_files_to_check}")
|
||||
self.logger.debug(f"将检查以下可能的补丁文件: {patch_files_to_check}")
|
||||
|
||||
# 确定操作类型
|
||||
if operation:
|
||||
@@ -542,7 +545,7 @@ class PatchManager:
|
||||
action_needed = True # 未指定操作类型,始终执行切换
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 操作决策 - 操作类型: {operation}, 是否需要执行操作: {action_needed}")
|
||||
self.logger.debug(f"操作决策 - 操作类型: {operation}, 是否需要执行操作: {action_needed}")
|
||||
|
||||
if not action_needed:
|
||||
# 补丁已经是目标状态,无需操作
|
||||
@@ -552,7 +555,7 @@ class PatchManager:
|
||||
message = f"{game_version} 补丁已经是禁用状态"
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {message}, 无需操作")
|
||||
self.logger.debug(f"{message}, 无需操作")
|
||||
|
||||
if not silent:
|
||||
QMessageBox.information(
|
||||
@@ -569,17 +572,17 @@ class PatchManager:
|
||||
# 从禁用文件名去掉.fain后缀
|
||||
enabled_path = disabled_path[:-5] # 去掉.fain
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 正在启用补丁 - 从 {disabled_path} 重命名为 {enabled_path}")
|
||||
self.logger.debug(f"正在启用补丁 - 从 {disabled_path} 重命名为 {enabled_path}")
|
||||
os.rename(disabled_path, enabled_path)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已启用 {game_version} 的补丁,重命名文件成功")
|
||||
self.logger.debug(f"已启用 {game_version} 的补丁,重命名文件成功")
|
||||
action = "enable"
|
||||
message = f"{game_version} 补丁已启用"
|
||||
else:
|
||||
# 未找到禁用的补丁文件,但状态是禁用
|
||||
message = f"未找到禁用的补丁文件: {disabled_path}"
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {message}")
|
||||
self.logger.debug(f"{message}")
|
||||
return {"success": False, "message": message, "action": "none"}
|
||||
else:
|
||||
# 当前是启用状态,需要禁用
|
||||
@@ -589,24 +592,24 @@ class PatchManager:
|
||||
if os.path.exists(patch_path):
|
||||
active_patch_file = patch_path
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到活跃的补丁文件: {active_patch_file}")
|
||||
self.logger.debug(f"找到活跃的补丁文件: {active_patch_file}")
|
||||
break
|
||||
|
||||
if active_patch_file:
|
||||
# 给补丁文件添加.fain后缀禁用它
|
||||
disabled_path = f"{active_patch_file}.fain"
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 正在禁用补丁 - 从 {active_patch_file} 重命名为 {disabled_path}")
|
||||
self.logger.debug(f"正在禁用补丁 - 从 {active_patch_file} 重命名为 {disabled_path}")
|
||||
os.rename(active_patch_file, disabled_path)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已禁用 {game_version} 的补丁,重命名文件成功")
|
||||
self.logger.debug(f"已禁用 {game_version} 的补丁,重命名文件成功")
|
||||
action = "disable"
|
||||
message = f"{game_version} 补丁已禁用"
|
||||
else:
|
||||
# 未找到活跃的补丁文件,但状态是启用
|
||||
message = f"未找到启用的补丁文件,请检查游戏目录: {game_dir}"
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {message}")
|
||||
self.logger.debug(f"{message}")
|
||||
return {"success": False, "message": message, "action": "none"}
|
||||
|
||||
# 非静默模式下显示操作结果
|
||||
@@ -619,7 +622,7 @@ class PatchManager:
|
||||
)
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 切换补丁状态操作完成 - 结果: 成功, 操作: {action}, 消息: {message}")
|
||||
self.logger.debug(f"切换补丁状态操作完成 - 结果: 成功, 操作: {action}, 消息: {message}")
|
||||
|
||||
return {"success": True, "message": message, "action": action}
|
||||
|
||||
@@ -627,9 +630,9 @@ class PatchManager:
|
||||
error_message = f"切换 {game_version} 补丁状态时出错: {str(e)}"
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: {error_message}")
|
||||
self.logger.debug(f"{error_message}")
|
||||
import traceback
|
||||
print(f"DEBUG: 错误详情:\n{traceback.format_exc()}")
|
||||
self.logger.debug(f"错误详情:\n{traceback.format_exc()}")
|
||||
|
||||
if not silent:
|
||||
QMessageBox.critical(
|
||||
@@ -657,28 +660,28 @@ class PatchManager:
|
||||
results = []
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始批量切换补丁状态 - 操作: {operation}, 游戏数量: {len(game_dirs)}")
|
||||
print(f"DEBUG: 游戏列表: {list(game_dirs.keys())}")
|
||||
self.logger.debug(f"开始批量切换补丁状态 - 操作: {operation}, 游戏数量: {len(game_dirs)}")
|
||||
self.logger.debug(f"游戏列表: {list(game_dirs.keys())}")
|
||||
|
||||
for version, path in game_dirs.items():
|
||||
try:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 处理游戏 {version}, 目录: {path}")
|
||||
self.logger.debug(f"处理游戏 {version}, 目录: {path}")
|
||||
|
||||
# 在批量模式下使用静默操作
|
||||
result = self.toggle_patch(path, version, operation=operation, silent=True)
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 游戏 {version} 操作结果: {result}")
|
||||
self.logger.debug(f"游戏 {version} 操作结果: {result}")
|
||||
|
||||
if result["success"]:
|
||||
success_count += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 游戏 {version} 操作成功,操作类型: {result['action']}")
|
||||
self.logger.debug(f"游戏 {version} 操作成功,操作类型: {result['action']}")
|
||||
else:
|
||||
fail_count += 1
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 游戏 {version} 操作失败,原因: {result['message']}")
|
||||
self.logger.debug(f"游戏 {version} 操作失败,原因: {result['message']}")
|
||||
|
||||
results.append({
|
||||
"version": version,
|
||||
@@ -689,9 +692,9 @@ class PatchManager:
|
||||
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 切换 {version} 补丁状态时出错: {str(e)}")
|
||||
self.logger.debug(f"切换 {version} 补丁状态时出错: {str(e)}")
|
||||
import traceback
|
||||
print(f"DEBUG: 错误详情:\n{traceback.format_exc()}")
|
||||
self.logger.debug(f"错误详情:\n{traceback.format_exc()}")
|
||||
|
||||
fail_count += 1
|
||||
results.append({
|
||||
@@ -702,7 +705,7 @@ class PatchManager:
|
||||
})
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 批量切换补丁状态完成 - 成功: {success_count}, 失败: {fail_count}")
|
||||
self.logger.debug(f"批量切换补丁状态完成 - 成功: {success_count}, 失败: {fail_count}")
|
||||
|
||||
return success_count, fail_count, results
|
||||
|
||||
|
||||
@@ -647,16 +647,113 @@ class UIManager:
|
||||
msg_box.exec()
|
||||
|
||||
def open_log_file(self):
|
||||
"""打开log.txt文件"""
|
||||
"""打开当前日志文件"""
|
||||
try:
|
||||
# 使用操作系统默认程序打开日志文件
|
||||
if os.name == 'nt': # Windows
|
||||
os.startfile(LOG_FILE)
|
||||
else: # macOS 和 Linux
|
||||
import subprocess
|
||||
subprocess.call(['xdg-open', LOG_FILE])
|
||||
# 检查日志文件是否存在
|
||||
if os.path.exists(LOG_FILE):
|
||||
# 获取日志文件大小
|
||||
file_size = os.path.getsize(LOG_FILE)
|
||||
if file_size == 0:
|
||||
msg_box = self._create_message_box("提示", f"\n当前日志文件 {os.path.basename(LOG_FILE)} 存在但为空。\n\n日志文件位置:{os.path.abspath(LOG_FILE)}")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
# 根据文件大小决定是使用文本查看器还是直接打开
|
||||
if file_size > 1024 * 1024: # 大于1MB
|
||||
# 文件较大,显示警告
|
||||
msg_box = self._create_message_box(
|
||||
"警告",
|
||||
f"\n日志文件较大 ({file_size / 1024 / 1024:.2f} MB),是否仍要打开?\n\n日志文件位置:{os.path.abspath(LOG_FILE)}",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
if msg_box.exec() != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
|
||||
# 使用操作系统默认程序打开日志文件
|
||||
if os.name == 'nt': # Windows
|
||||
os.startfile(LOG_FILE)
|
||||
else: # macOS 和 Linux
|
||||
import subprocess
|
||||
subprocess.call(['xdg-open', LOG_FILE])
|
||||
else:
|
||||
# 文件不存在,显示信息
|
||||
# 搜索log文件夹下所有可能的日志文件
|
||||
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
log_dir = os.path.join(root_dir, "log")
|
||||
|
||||
# 如果log文件夹不存在,尝试创建它
|
||||
if not os.path.exists(log_dir):
|
||||
try:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
msg_box = self._create_message_box(
|
||||
"信息",
|
||||
f"\n日志文件夹不存在,已创建新的日志文件夹:\n{log_dir}\n\n请在启用调试模式后重试。"
|
||||
)
|
||||
msg_box.exec()
|
||||
return
|
||||
except Exception as e:
|
||||
msg_box = self._create_message_box(
|
||||
"错误",
|
||||
f"\n创建日志文件夹失败:\n\n{str(e)}"
|
||||
)
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
# 搜索log文件夹中的日志文件
|
||||
try:
|
||||
log_files = [f for f in os.listdir(log_dir) if f.startswith("log-") and f.endswith(".txt")]
|
||||
except Exception as e:
|
||||
msg_box = self._create_message_box(
|
||||
"错误",
|
||||
f"\n无法读取日志文件夹:\n\n{str(e)}"
|
||||
)
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
if log_files:
|
||||
# 按照修改时间排序,获取最新的日志文件
|
||||
log_files.sort(key=lambda x: os.path.getmtime(os.path.join(log_dir, x)), reverse=True)
|
||||
latest_log = os.path.join(log_dir, log_files[0])
|
||||
|
||||
# 获取最新日志文件的创建时间信息
|
||||
try:
|
||||
log_datetime = "-".join(os.path.basename(latest_log)[4:-4].split("-")[:2])
|
||||
log_date = log_datetime.split("-")[0]
|
||||
log_time = log_datetime.split("-")[1] if "-" in log_datetime else "未知时间"
|
||||
date_info = f"日期: {log_date[:4]}-{log_date[4:6]}-{log_date[6:]} "
|
||||
time_info = f"时间: {log_time[:2]}:{log_time[2:4]}:{log_time[4:]}"
|
||||
except:
|
||||
date_info = "日期未知 "
|
||||
time_info = "时间未知"
|
||||
|
||||
msg_box = self._create_message_box(
|
||||
"信息",
|
||||
f"\n当前日志文件 {os.path.basename(LOG_FILE)} 不存在。\n\n"
|
||||
f"发现最新的日志文件: {os.path.basename(latest_log)}\n"
|
||||
f"({date_info}{time_info})\n\n"
|
||||
f"是否打开此文件?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
|
||||
if msg_box.exec() == QMessageBox.StandardButton.Yes:
|
||||
if os.name == 'nt': # Windows
|
||||
os.startfile(latest_log)
|
||||
else: # macOS 和 Linux
|
||||
import subprocess
|
||||
subprocess.call(['xdg-open', latest_log])
|
||||
return
|
||||
|
||||
# 如果没有找到任何日志文件或用户选择不打开最新的日志文件
|
||||
msg_box = self._create_message_box(
|
||||
"信息",
|
||||
f"\n没有找到有效的日志文件。\n\n"
|
||||
f"预期的日志文件夹:{log_dir}\n\n"
|
||||
f"请确认调试模式已启用,并执行一些操作后再查看日志。"
|
||||
)
|
||||
msg_box.exec()
|
||||
|
||||
except Exception as e:
|
||||
msg_box = self._create_message_box("错误", f"\n打开log.txt文件失败:\n\n{str(e)}\n")
|
||||
msg_box = self._create_message_box("错误", f"\n处理日志文件时出错:\n\n{str(e)}\n\n文件位置:{os.path.abspath(LOG_FILE)}")
|
||||
msg_box.exec()
|
||||
|
||||
def restore_hosts_backup(self):
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import os
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
# 配置信息
|
||||
app_data = {
|
||||
"APP_VERSION": "1.4.0",
|
||||
"APP_VERSION": "1.3.2",
|
||||
"APP_NAME": "FRAISEMOE Addons Installer NEXT",
|
||||
"TEMP": "TEMP",
|
||||
"CACHE": "FRAISEMOE",
|
||||
@@ -55,7 +56,13 @@ APP_NAME = app_data["APP_NAME"]
|
||||
TEMP = os.getenv(app_data["TEMP"]) or app_data["TEMP"]
|
||||
CACHE = os.path.join(TEMP, app_data["CACHE"])
|
||||
CONFIG_FILE = os.path.join(CACHE, "config.json")
|
||||
LOG_FILE = "log.txt"
|
||||
|
||||
# 将log文件放在程序根目录下的log文件夹中,使用日期+时间戳格式命名
|
||||
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
log_dir = os.path.join(root_dir, "log")
|
||||
current_datetime = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
LOG_FILE = os.path.join(log_dir, f"log-{current_datetime}.txt")
|
||||
|
||||
PLUGIN = os.path.join(CACHE, app_data["PLUGIN"])
|
||||
CONFIG_URL = decode_base64(app_data["CONFIG_URL"])
|
||||
UA = app_data["UA_TEMPLATE"].format(APP_VERSION)
|
||||
@@ -63,11 +70,11 @@ GAME_INFO = app_data["game_info"]
|
||||
BLOCK_SIZE = 67108864
|
||||
HASH_SIZE = 134217728
|
||||
PLUGIN_HASH = {
|
||||
"vol1": GAME_INFO["NEKOPARA Vol.1"]["hash"],
|
||||
"vol2": GAME_INFO["NEKOPARA Vol.2"]["hash"],
|
||||
"vol3": GAME_INFO["NEKOPARA Vol.3"]["hash"],
|
||||
"vol4": GAME_INFO["NEKOPARA Vol.4"]["hash"],
|
||||
"after": GAME_INFO["NEKOPARA After"]["hash"]
|
||||
"NEKOPARA Vol.1": GAME_INFO["NEKOPARA Vol.1"]["hash"],
|
||||
"NEKOPARA Vol.2": GAME_INFO["NEKOPARA Vol.2"]["hash"],
|
||||
"NEKOPARA Vol.3": GAME_INFO["NEKOPARA Vol.3"]["hash"],
|
||||
"NEKOPARA Vol.4": GAME_INFO["NEKOPARA Vol.4"]["hash"],
|
||||
"NEKOPARA After": GAME_INFO["NEKOPARA After"]["hash"]
|
||||
}
|
||||
PROCESS_INFO = {info["exe"]: game for game, info in GAME_INFO.items()}
|
||||
|
||||
|
||||
@@ -114,13 +114,6 @@ class MainWindow(QMainWindow):
|
||||
if hasattr(self.ui, 'minimize_btn'):
|
||||
self.ui.minimize_btn.clicked.connect(self.showMinimized)
|
||||
|
||||
# 检查管理员权限和进程
|
||||
self.admin_privileges.request_admin_privileges()
|
||||
self.admin_privileges.check_and_terminate_processes()
|
||||
|
||||
# 备份hosts文件
|
||||
self.download_manager.hosts_manager.backup()
|
||||
|
||||
# 创建缓存目录
|
||||
if not os.path.exists(PLUGIN):
|
||||
try:
|
||||
@@ -143,10 +136,44 @@ class MainWindow(QMainWindow):
|
||||
self.install_button_enabled = False
|
||||
self.last_error_message = ""
|
||||
|
||||
# 检查管理员权限和进程
|
||||
try:
|
||||
# 检查管理员权限
|
||||
self.admin_privileges.request_admin_privileges()
|
||||
# 检查并终止相关进程
|
||||
self.admin_privileges.check_and_terminate_processes()
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("权限检查或进程检查被用户中断")
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
f"警告 - {APP_NAME}",
|
||||
"\n操作被中断,请重新启动应用。\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.error(f"权限检查或进程检查时发生错误: {e}")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n权限检查或进程检查时发生错误,请重新启动应用。\n\n【错误信息】:{e}\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# 备份hosts文件
|
||||
self.download_manager.hosts_manager.backup()
|
||||
|
||||
# 根据初始配置决定是否开启Debug模式
|
||||
if "debug_mode" in self.config and self.config["debug_mode"]:
|
||||
# 先启用日志系统
|
||||
self.debug_manager.start_logging()
|
||||
logger.info("通过配置启动调试模式")
|
||||
# 检查UI设置
|
||||
if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action:
|
||||
if self.ui_manager.debug_action.isChecked():
|
||||
self.debug_manager.start_logging()
|
||||
# 如果通过UI启用了调试模式,确保日志系统已启动
|
||||
if not self.debug_manager.logger:
|
||||
self.debug_manager.start_logging()
|
||||
logger.info("通过UI启动调试模式")
|
||||
|
||||
# 设置UI,包括窗口图标和菜单
|
||||
self.ui_manager.setup_ui()
|
||||
|
||||
1
source/ui_manager.py
Normal file
1
source/ui_manager.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -175,6 +175,12 @@ class HashManager:
|
||||
|
||||
try:
|
||||
expected_hash = plugin_hash.get(game_version, "")
|
||||
if not expected_hash:
|
||||
if debug_mode:
|
||||
logger.debug(f"DEBUG: 哈希预检查 - {game_version} 没有预期哈希值,跳过哈希检查")
|
||||
# 当没有预期哈希值时,保持当前状态不变
|
||||
continue
|
||||
|
||||
file_hash = self.hash_calculate(install_path)
|
||||
|
||||
if debug_mode:
|
||||
@@ -186,8 +192,12 @@ class HashManager:
|
||||
|
||||
if file_hash == expected_hash:
|
||||
status_copy[game_version] = True
|
||||
if debug_mode:
|
||||
logger.debug(f"DEBUG: 哈希预检查 - {game_version} 哈希匹配成功")
|
||||
else:
|
||||
status_copy[game_version] = False
|
||||
if debug_mode:
|
||||
logger.debug(f"DEBUG: 哈希预检查 - {game_version} 哈希不匹配")
|
||||
except Exception as e:
|
||||
status_copy[game_version] = False
|
||||
if debug_mode:
|
||||
@@ -270,65 +280,98 @@ class AdminPrivileges:
|
||||
"\n需要管理员权限运行此程序\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
reply = msg_box.exec()
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
try:
|
||||
ctypes.windll.shell32.ShellExecuteW(
|
||||
None, "runas", sys.executable, " ".join(sys.argv), None, 1
|
||||
)
|
||||
except Exception as e:
|
||||
try:
|
||||
reply = msg_box.exec()
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
try:
|
||||
ctypes.windll.shell32.ShellExecuteW(
|
||||
None, "runas", sys.executable, " ".join(sys.argv), None, 1
|
||||
)
|
||||
except Exception as e:
|
||||
msg_box = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n请求管理员权限失败\n\n【错误信息】:{e}\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
else:
|
||||
msg_box = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n请求管理员权限失败\n\n【错误信息】:{e}\n",
|
||||
f"权限检测 - {APP_NAME}",
|
||||
"\n无法获取管理员权限,程序将退出\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("管理员权限请求被用户中断")
|
||||
msg_box = msgbox_frame(
|
||||
f"权限检测 - {APP_NAME}",
|
||||
"\n无法获取管理员权限,程序将退出\n",
|
||||
"\n操作被中断,程序将退出\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.error(f"管理员权限请求时发生错误: {e}")
|
||||
msg_box = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n请求管理员权限时发生未知错误\n\n【错误信息】:{e}\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
|
||||
def check_and_terminate_processes(self):
|
||||
for proc in psutil.process_iter(["pid", "name"]):
|
||||
proc_name = proc.info["name"].lower() if proc.info["name"] else ""
|
||||
try:
|
||||
for proc in psutil.process_iter(["pid", "name"]):
|
||||
proc_name = proc.info["name"].lower() if proc.info["name"] else ""
|
||||
|
||||
# 检查进程名是否匹配任何需要终止的游戏进程
|
||||
for exe in self.required_exes:
|
||||
if exe.lower() == proc_name:
|
||||
# 获取不带.nocrack的游戏名称用于显示
|
||||
display_name = exe.replace(".nocrack", "")
|
||||
# 检查进程名是否匹配任何需要终止的游戏进程
|
||||
for exe in self.required_exes:
|
||||
if exe.lower() == proc_name:
|
||||
# 获取不带.nocrack的游戏名称用于显示
|
||||
display_name = exe.replace(".nocrack", "")
|
||||
|
||||
msg_box = msgbox_frame(
|
||||
f"进程检测 - {APP_NAME}",
|
||||
f"\n检测到游戏正在运行: {display_name} \n\n是否终止?\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
reply = msg_box.exec()
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=3)
|
||||
except psutil.AccessDenied:
|
||||
msg_box = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n无法关闭游戏: {display_name} \n\n请手动关闭后重启应用\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
else:
|
||||
msg_box = msgbox_frame(
|
||||
f"进程检测 - {APP_NAME}",
|
||||
f"\n未关闭的游戏: {display_name} \n\n请手动关闭后重启应用\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
f"\n检测到游戏正在运行: {display_name} \n\n是否终止?\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
try:
|
||||
reply = msg_box.exec()
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=3)
|
||||
except psutil.AccessDenied:
|
||||
msg_box = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n无法关闭游戏: {display_name} \n\n请手动关闭后重启应用\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
else:
|
||||
msg_box = msgbox_frame(
|
||||
f"进程检测 - {APP_NAME}",
|
||||
f"\n未关闭的游戏: {display_name} \n\n请手动关闭后重启应用\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.warning(f"进程 {display_name} 终止操作被用户中断")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"进程 {display_name} 终止操作时发生错误: {e}")
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("进程检查被用户中断")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"进程检查时发生错误: {e}")
|
||||
raise
|
||||
|
||||
class HostsManager:
|
||||
def __init__(self):
|
||||
|
||||
@@ -9,26 +9,58 @@ class URLCensorFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
# 先使用原始的format方法格式化日志
|
||||
formatted_message = super().format(record)
|
||||
# 然后对格式化后的消息进行URL审查
|
||||
return censor_url(formatted_message)
|
||||
# 临时禁用URL隐藏,直接返回原始消息
|
||||
return formatted_message
|
||||
# 然后对格式化后的消息进行URL审查(已禁用)
|
||||
# return censor_url(formatted_message)
|
||||
|
||||
class Logger:
|
||||
def __init__(self, filename, stream):
|
||||
self.terminal = stream
|
||||
self.log = open(filename, "w", encoding="utf-8")
|
||||
try:
|
||||
# 确保目录存在
|
||||
log_dir = os.path.dirname(filename)
|
||||
if log_dir and not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
print(f"已创建日志目录: {log_dir}")
|
||||
|
||||
# 以追加模式打开,避免覆盖现有内容
|
||||
self.log = open(filename, "a", encoding="utf-8", errors="replace")
|
||||
self.log.write("\n\n--- New logging session started ---\n\n")
|
||||
except (IOError, OSError) as e:
|
||||
# 如果打开文件失败,记录错误并使用空的写入操作
|
||||
print(f"Error opening log file {filename}: {e}")
|
||||
self.log = None
|
||||
|
||||
def write(self, message):
|
||||
censored_message = censor_url(message)
|
||||
self.terminal.write(censored_message)
|
||||
self.log.write(censored_message)
|
||||
self.flush()
|
||||
try:
|
||||
# 临时禁用URL隐藏
|
||||
# censored_message = censor_url(message)
|
||||
censored_message = message # 直接使用原始消息
|
||||
self.terminal.write(censored_message)
|
||||
if self.log:
|
||||
self.log.write(censored_message)
|
||||
self.flush()
|
||||
except Exception as e:
|
||||
# 发生错误时记录到控制台
|
||||
self.terminal.write(f"Error writing to log: {e}\n")
|
||||
|
||||
def flush(self):
|
||||
self.terminal.flush()
|
||||
self.log.flush()
|
||||
try:
|
||||
self.terminal.flush()
|
||||
if self.log:
|
||||
self.log.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
self.log.close()
|
||||
try:
|
||||
if self.log:
|
||||
self.log.write("\n--- Logging session ended ---\n")
|
||||
self.log.close()
|
||||
self.log = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def setup_logger(name):
|
||||
"""设置并返回一个命名的logger
|
||||
@@ -39,6 +71,9 @@ def setup_logger(name):
|
||||
Returns:
|
||||
logging.Logger: 配置好的logger对象
|
||||
"""
|
||||
# 导入LOG_FILE
|
||||
from data.config import LOG_FILE
|
||||
|
||||
# 创建logger
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
@@ -53,10 +88,24 @@ def setup_logger(name):
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_file = os.path.join(log_dir, f"{name}.log")
|
||||
|
||||
# 创建文件处理器
|
||||
# 创建文件处理器 - 模块日志
|
||||
file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
# 创建主日志文件处理器 - 所有日志合并到主LOG_FILE
|
||||
try:
|
||||
# 确保主日志文件目录存在
|
||||
log_file_dir = os.path.dirname(LOG_FILE)
|
||||
if log_file_dir and not os.path.exists(log_file_dir):
|
||||
os.makedirs(log_file_dir, exist_ok=True)
|
||||
print(f"已创建主日志目录: {log_file_dir}")
|
||||
|
||||
main_file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8", mode="w")
|
||||
main_file_handler.setLevel(logging.DEBUG)
|
||||
except (IOError, OSError) as e:
|
||||
print(f"无法创建主日志文件处理器: {e}")
|
||||
main_file_handler = None
|
||||
|
||||
# 创建控制台处理器
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
@@ -65,9 +114,13 @@ def setup_logger(name):
|
||||
formatter = URLCensorFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
console_handler.setFormatter(formatter)
|
||||
if main_file_handler:
|
||||
main_file_handler.setFormatter(formatter)
|
||||
|
||||
# 添加处理器到logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
if main_file_handler:
|
||||
logger.addHandler(main_file_handler)
|
||||
|
||||
return logger
|
||||
@@ -9,9 +9,25 @@ def censor_url(text):
|
||||
Returns:
|
||||
str: 处理后的文本,URL被完全隐藏
|
||||
"""
|
||||
# 临时禁用URL隐藏功能,直接返回原始文本以便调试
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
|
||||
return text # 直接返回原始文本,不做任何隐藏
|
||||
|
||||
# 以下是原始代码,现在被注释掉
|
||||
'''
|
||||
# 匹配URL并替换为固定文本
|
||||
url_pattern = re.compile(r'https?://[^\s/$.?#].[^\s]*')
|
||||
return url_pattern.sub('***URL protection***', text)
|
||||
censored = url_pattern.sub('***URL protection***', text)
|
||||
|
||||
# 额外处理带referer参数的情况
|
||||
referer_pattern = re.compile(r'--referer\s+(\S+)')
|
||||
censored = referer_pattern.sub('--referer ***URL protection***', censored)
|
||||
|
||||
# 处理Origin头
|
||||
origin_pattern = re.compile(r'Origin:\s+(\S+)')
|
||||
censored = origin_pattern.sub('Origin: ***URL protection***', censored)
|
||||
|
||||
return censored
|
||||
'''
|
||||
@@ -33,16 +33,10 @@ class ConfigFetchThread(QThread):
|
||||
logger.debug(f"DEBUG: Response Status Code: {response.status_code}")
|
||||
logger.debug(f"DEBUG: Response Headers: {response.headers}")
|
||||
|
||||
# 解析并隐藏响应中的敏感URL
|
||||
try:
|
||||
response_data = response.json()
|
||||
# 创建安全版本用于日志输出
|
||||
safe_response = self._create_safe_config_for_logging(response_data)
|
||||
logger.debug(f"DEBUG: Response Text: {json.dumps(safe_response, indent=2)}")
|
||||
except:
|
||||
# 如果不是JSON,直接打印文本
|
||||
censored_text = censor_url(response.text)
|
||||
logger.debug(f"DEBUG: Response Text: {censored_text}")
|
||||
# 记录实际响应内容,但隐藏URL等敏感信息(临时禁用)
|
||||
# censored_text = censor_url(response.text)
|
||||
censored_text = response.text # 直接使用原始文本
|
||||
logger.debug(f"DEBUG: Response Text: {censored_text}")
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@@ -8,15 +8,11 @@ from PySide6.QtCore import (Qt, Signal, QThread, QTimer)
|
||||
from PySide6.QtWidgets import (QLabel, QProgressBar, QVBoxLayout, QDialog, QHBoxLayout)
|
||||
from utils import resource_path
|
||||
from data.config import APP_NAME, UA
|
||||
from utils.logger import setup_logger
|
||||
import signal
|
||||
import ctypes
|
||||
import time
|
||||
from utils.url_censor import censor_url
|
||||
|
||||
# 初始化logger
|
||||
logger = setup_logger("download")
|
||||
|
||||
# Windows API常量和函数
|
||||
if sys.platform == 'win32':
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
PROCESS_ALL_ACCESS = 0x1F0FFF
|
||||
@@ -34,6 +30,7 @@ if sys.platform == 'win32':
|
||||
('dwFlags', ctypes.c_ulong)
|
||||
]
|
||||
|
||||
# 下载线程类
|
||||
class DownloadThread(QThread):
|
||||
progress = Signal(dict)
|
||||
finished = Signal(bool, str)
|
||||
@@ -52,9 +49,10 @@ class DownloadThread(QThread):
|
||||
if self.process and self.process.poll() is None:
|
||||
self._is_running = False
|
||||
try:
|
||||
# 使用 taskkill 强制终止进程及其子进程,并隐藏窗口
|
||||
subprocess.run(['taskkill', '/F', '/T', '/PID', str(self.process.pid)], check=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
||||
logger.error(f"停止下载进程时出错: {e}")
|
||||
print(f"停止下载进程时出错: {e}")
|
||||
|
||||
def _get_process_threads(self, pid):
|
||||
"""获取进程的所有线程ID"""
|
||||
@@ -83,11 +81,13 @@ class DownloadThread(QThread):
|
||||
if not self._is_paused and self.process and self.process.poll() is None:
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
# 获取所有线程
|
||||
self.threads = self._get_process_threads(self.process.pid)
|
||||
if not self.threads:
|
||||
logger.warning("未找到可暂停的线程")
|
||||
print("未找到可暂停的线程")
|
||||
return False
|
||||
|
||||
# 暂停所有线程
|
||||
for thread_id in self.threads:
|
||||
h_thread = kernel32.OpenThread(THREAD_SUSPEND_RESUME, False, thread_id)
|
||||
if h_thread:
|
||||
@@ -95,15 +95,16 @@ class DownloadThread(QThread):
|
||||
kernel32.CloseHandle(h_thread)
|
||||
|
||||
self._is_paused = True
|
||||
logger.info(f"下载进程已暂停: PID {self.process.pid}, 线程数: {len(self.threads)}")
|
||||
print(f"下载进程已暂停: PID {self.process.pid}, 线程数: {len(self.threads)}")
|
||||
return True
|
||||
else:
|
||||
# 在Unix系统上使用SIGSTOP
|
||||
os.kill(self.process.pid, signal.SIGSTOP)
|
||||
self._is_paused = True
|
||||
logger.info(f"下载进程已暂停: PID {self.process.pid}")
|
||||
print(f"下载进程已暂停: PID {self.process.pid}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"暂停下载进程时出错: {e}")
|
||||
print(f"暂停下载进程时出错: {e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
@@ -112,6 +113,7 @@ class DownloadThread(QThread):
|
||||
if self._is_paused and self.process and self.process.poll() is None:
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
# 恢复所有线程
|
||||
for thread_id in self.threads:
|
||||
h_thread = kernel32.OpenThread(THREAD_SUSPEND_RESUME, False, thread_id)
|
||||
if h_thread:
|
||||
@@ -119,15 +121,16 @@ class DownloadThread(QThread):
|
||||
kernel32.CloseHandle(h_thread)
|
||||
|
||||
self._is_paused = False
|
||||
logger.info(f"下载进程已恢复: PID {self.process.pid}, 线程数: {len(self.threads)}")
|
||||
print(f"下载进程已恢复: PID {self.process.pid}, 线程数: {len(self.threads)}")
|
||||
return True
|
||||
else:
|
||||
# 在Unix系统上使用SIGCONT
|
||||
os.kill(self.process.pid, signal.SIGCONT)
|
||||
self._is_paused = False
|
||||
logger.info(f"下载进程已恢复: PID {self.process.pid}")
|
||||
print(f"下载进程已恢复: PID {self.process.pid}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"恢复下载进程时出错: {e}")
|
||||
print(f"恢复下载进程时出错: {e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
@@ -152,16 +155,21 @@ class DownloadThread(QThread):
|
||||
aria2c_path,
|
||||
]
|
||||
|
||||
thread_count = 64 # 默认值
|
||||
# 获取主窗口的下载管理器对象
|
||||
thread_count = 64 # 默认值
|
||||
if hasattr(self.parent(), 'download_manager'):
|
||||
# 从下载管理器获取线程数设置
|
||||
thread_count = self.parent().download_manager.get_download_thread_count()
|
||||
|
||||
# 检查是否启用IPv6支持
|
||||
ipv6_enabled = False
|
||||
if hasattr(self.parent(), 'config'):
|
||||
ipv6_enabled = self.parent().config.get("ipv6_enabled", False)
|
||||
|
||||
logger.info(f"IPv6支持状态: {ipv6_enabled}")
|
||||
# 打印IPv6状态
|
||||
print(f"IPv6支持状态: {ipv6_enabled}")
|
||||
|
||||
# 将所有的优化参数应用于每个下载任务
|
||||
command.extend([
|
||||
'--dir', download_dir,
|
||||
'--out', file_name,
|
||||
@@ -188,39 +196,36 @@ class DownloadThread(QThread):
|
||||
'--auto-file-renaming=false',
|
||||
'--allow-overwrite=true',
|
||||
'--split=128',
|
||||
f'--max-connection-per-server={thread_count}',
|
||||
'--min-split-size=1M',
|
||||
'--optimize-concurrent-downloads=true',
|
||||
'--file-allocation=none',
|
||||
'--async-dns=true',
|
||||
f'--max-connection-per-server={thread_count}', # 使用动态的线程数
|
||||
'--min-split-size=1M', # 减小最小分片大小
|
||||
'--optimize-concurrent-downloads=true', # 优化并发下载
|
||||
'--file-allocation=none', # 禁用文件预分配加快开始
|
||||
'--async-dns=true', # 使用异步DNS
|
||||
])
|
||||
|
||||
# 根据IPv6设置决定是否禁用IPv6
|
||||
if not ipv6_enabled:
|
||||
command.append('--disable-ipv6=true')
|
||||
logger.info("已禁用IPv6支持")
|
||||
print("已禁用IPv6支持")
|
||||
else:
|
||||
logger.info("已启用IPv6支持")
|
||||
print("已启用IPv6支持")
|
||||
|
||||
# 证书验证现在总是需要,因为我们依赖hosts文件
|
||||
command.append('--check-certificate=false')
|
||||
|
||||
command.append(self.url)
|
||||
|
||||
# 创建一个安全的命令副本,隐藏URL
|
||||
safe_command = command.copy()
|
||||
if len(safe_command) > 0:
|
||||
# 替换最后一个参数(URL)为安全版本
|
||||
url = safe_command[-1]
|
||||
if isinstance(url, str) and url.startswith("http"):
|
||||
safe_command[-1] = "***URL protection***"
|
||||
|
||||
logger.info(f"即将执行的 Aria2c 命令: {' '.join(safe_command)}")
|
||||
# 打印将要执行的命令,用于调试
|
||||
print(f"即将执行的 Aria2c 命令: {' '.join(command)}")
|
||||
|
||||
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
|
||||
# 正则表达式用于解析aria2c的输出
|
||||
# 例如: #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\]]+)')
|
||||
|
||||
# 添加限流计时器,防止更新过于频繁导致UI卡顿
|
||||
last_update_time = 0
|
||||
update_interval = 0.2 # 限制UI更新频率,每0.2秒最多更新一次
|
||||
|
||||
@@ -233,13 +238,12 @@ class DownloadThread(QThread):
|
||||
else:
|
||||
break
|
||||
|
||||
# 处理输出行,隐藏可能包含的URL
|
||||
censored_line = censor_url(line)
|
||||
full_output.append(censored_line)
|
||||
logger.debug(censored_line.strip())
|
||||
full_output.append(line)
|
||||
print(line.strip()) # 在控制台输出实时日志
|
||||
|
||||
match = progress_pattern.search(line)
|
||||
if match:
|
||||
# 检查是否达到更新间隔
|
||||
current_time = time.time()
|
||||
if current_time - last_update_time >= update_interval:
|
||||
percent = int(match.group(1))
|
||||
@@ -247,6 +251,7 @@ class DownloadThread(QThread):
|
||||
speed = match.group(3)
|
||||
eta = match.group(4)
|
||||
|
||||
# 直接发送进度信号,不使用invokeMethod
|
||||
self.progress.emit({
|
||||
"game": self.game_version,
|
||||
"percent": percent,
|
||||
@@ -260,6 +265,7 @@ class DownloadThread(QThread):
|
||||
return_code = self.process.wait()
|
||||
|
||||
if not self._is_running:
|
||||
# 如果是手动停止的
|
||||
self.finished.emit(False, "下载已手动停止。")
|
||||
return
|
||||
|
||||
@@ -280,6 +286,7 @@ class DownloadThread(QThread):
|
||||
if self._is_running:
|
||||
self.finished.emit(False, f"\n下载时发生未知错误\n\n【错误信息】: {e}\n")
|
||||
|
||||
# 下载进度窗口类
|
||||
class ProgressWindow(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(ProgressWindow, self).__init__(parent)
|
||||
@@ -294,14 +301,18 @@ class ProgressWindow(QDialog):
|
||||
self.progress_bar.setValue(0)
|
||||
self.stats_label = QLabel("速度: - | 线程: - | 剩余时间: -")
|
||||
|
||||
# 创建按钮布局
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
# 创建暂停/恢复按钮
|
||||
self.pause_resume_button = QtWidgets.QPushButton("暂停下载")
|
||||
self.pause_resume_button.setToolTip("暂停或恢复下载")
|
||||
|
||||
# 创建停止按钮
|
||||
self.stop_button = QtWidgets.QPushButton("取消下载")
|
||||
self.stop_button.setToolTip("取消整个下载过程")
|
||||
|
||||
# 添加按钮到按钮布局
|
||||
button_layout.addWidget(self.pause_resume_button)
|
||||
button_layout.addWidget(self.stop_button)
|
||||
|
||||
@@ -311,7 +322,9 @@ class ProgressWindow(QDialog):
|
||||
layout.addLayout(button_layout)
|
||||
self.setLayout(layout)
|
||||
|
||||
# 设置暂停/恢复状态
|
||||
self.is_paused = False
|
||||
# 添加最后进度记录,用于优化UI更新
|
||||
self._last_percent = -1
|
||||
|
||||
def update_pause_button_state(self, is_paused):
|
||||
@@ -333,12 +346,16 @@ class ProgressWindow(QDialog):
|
||||
threads = data.get("threads", "-")
|
||||
eta = data.get("eta", "-")
|
||||
|
||||
# 清除ETA值中可能存在的"]"符号
|
||||
if isinstance(eta, str):
|
||||
eta = eta.replace("]", "")
|
||||
|
||||
# 优化UI更新
|
||||
if hasattr(self, '_last_percent') and self._last_percent == percent and percent < 100:
|
||||
# 如果百分比没变,只更新速度和ETA信息
|
||||
self.stats_label.setText(f"速度: {speed} | 线程: {threads} | 剩余时间: {eta}")
|
||||
else:
|
||||
# 百分比变化或初次更新,更新所有信息
|
||||
self._last_percent = percent
|
||||
self.game_label.setText(f"正在下载 {game_version} 的补丁")
|
||||
self.progress_bar.setValue(int(percent))
|
||||
@@ -351,4 +368,5 @@ class ProgressWindow(QDialog):
|
||||
QTimer.singleShot(1500, self.accept)
|
||||
|
||||
def closeEvent(self, event):
|
||||
# 覆盖默认的关闭事件,防止用户通过其他方式关闭窗口
|
||||
event.ignore()
|
||||
@@ -28,6 +28,16 @@ class IpOptimizer:
|
||||
最优的 IP 地址字符串,如果找不到则返回 None。
|
||||
"""
|
||||
try:
|
||||
# 解析URL,获取协议和主机名
|
||||
parsed_url = urlparse(url)
|
||||
protocol = parsed_url.scheme
|
||||
hostname = parsed_url.netloc
|
||||
|
||||
# 如果是HTTPS,可能需要特殊处理
|
||||
is_https = protocol.lower() == 'https'
|
||||
|
||||
logger.info(f"协议: {protocol}, 主机名: {hostname}, 是否HTTPS: {is_https}")
|
||||
|
||||
cst_path = resource_path("cfst.exe")
|
||||
if not os.path.exists(cst_path):
|
||||
logger.error(f"错误: cfst.exe 未在资源路径中找到。")
|
||||
@@ -107,7 +117,9 @@ class IpOptimizer:
|
||||
timeout_counter = 0
|
||||
|
||||
# 处理输出行,隐藏可能包含的URL
|
||||
cleaned_line = censor_url(line.strip())
|
||||
# 临时禁用URL隐藏
|
||||
# cleaned_line = censor_url(line.strip())
|
||||
cleaned_line = line.strip() # 直接使用原始输出
|
||||
if cleaned_line:
|
||||
logger.debug(cleaned_line)
|
||||
|
||||
@@ -163,6 +175,16 @@ class IpOptimizer:
|
||||
最优的 IPv6 地址字符串,如果找不到则返回 None。
|
||||
"""
|
||||
try:
|
||||
# 解析URL,获取协议和主机名
|
||||
parsed_url = urlparse(url)
|
||||
protocol = parsed_url.scheme
|
||||
hostname = parsed_url.netloc
|
||||
|
||||
# 如果是HTTPS,可能需要特殊处理
|
||||
is_https = protocol.lower() == 'https'
|
||||
|
||||
logger.info(f"IPv6优选 - 协议: {protocol}, 主机名: {hostname}, 是否HTTPS: {is_https}")
|
||||
|
||||
cst_path = resource_path("cfst.exe")
|
||||
if not os.path.exists(cst_path):
|
||||
logger.error(f"错误: cfst.exe 未在资源路径中找到。")
|
||||
@@ -245,7 +267,9 @@ class IpOptimizer:
|
||||
timeout_counter = 0
|
||||
|
||||
# 处理输出行,隐藏可能包含的URL
|
||||
cleaned_line = censor_url(line.strip())
|
||||
# 临时禁用URL隐藏
|
||||
# cleaned_line = censor_url(line.strip())
|
||||
cleaned_line = line.strip() # 直接使用原始输出
|
||||
if cleaned_line:
|
||||
logger.debug(cleaned_line)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user