diff --git a/.gitignore b/.gitignore index 6e5bcd8..b40428f 100644 --- a/.gitignore +++ b/.gitignore @@ -179,3 +179,4 @@ vol.1.7z vol.2.7z vol.3.7z vol.4.7z +log/ diff --git a/source/Main.py b/source/Main.py index c089e3c..353eae0 100644 --- a/source/Main.py +++ b/source/Main.py @@ -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: diff --git a/source/core/debug_manager.py b/source/core/debug_manager.py index d400041..cc2a924 100644 --- a/source/core/debug_manager.py +++ b/source/core/debug_manager.py @@ -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 diff --git a/source/core/download_manager.py b/source/core/download_manager.py index 6ec22b8..0fdfe82 100644 --- a/source/core/download_manager.py +++ b/source/core/download_manager.py @@ -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) diff --git a/source/core/game_detector.py b/source/core/game_detector.py index 692508e..21b9e7b 100644 --- a/source/core/game_detector.py +++ b/source/core/game_detector.py @@ -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: 已清除目录缓存") \ No newline at end of file + self.logger.debug("已清除目录缓存") \ No newline at end of file diff --git a/source/core/patch_manager.py b/source/core/patch_manager.py index 67000e3..692f87e 100644 --- a/source/core/patch_manager.py +++ b/source/core/patch_manager.py @@ -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 diff --git a/source/core/ui_manager.py b/source/core/ui_manager.py index 6989a41..dd780de 100644 --- a/source/core/ui_manager.py +++ b/source/core/ui_manager.py @@ -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): diff --git a/source/data/config.py b/source/data/config.py index c8eab72..c0ae378 100644 --- a/source/data/config.py +++ b/source/data/config.py @@ -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()} diff --git a/source/main_window.py b/source/main_window.py index 9d40ff4..a5d7e7c 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -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() diff --git a/source/ui_manager.py b/source/ui_manager.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/source/ui_manager.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/utils/helpers.py b/source/utils/helpers.py index 940cffc..430d042 100644 --- a/source/utils/helpers.py +++ b/source/utils/helpers.py @@ -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 "" - - # 检查进程名是否匹配任何需要终止的游戏进程 - 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: + 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", "") + 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): diff --git a/source/utils/logger.py b/source/utils/logger.py index 6a0974b..bceccd8 100644 --- a/source/utils/logger.py +++ b/source/utils/logger.py @@ -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 \ No newline at end of file diff --git a/source/utils/url_censor.py b/source/utils/url_censor.py index b7fce05..3116d22 100644 --- a/source/utils/url_censor.py +++ b/source/utils/url_censor.py @@ -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) \ No newline at end of file + 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 + ''' \ No newline at end of file diff --git a/source/workers/config_fetch_thread.py b/source/workers/config_fetch_thread.py index 8296e35..6b42205 100644 --- a/source/workers/config_fetch_thread.py +++ b/source/workers/config_fetch_thread.py @@ -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() diff --git a/source/workers/download.py b/source/workers/download.py index 7ac7031..4eada7c 100644 --- a/source/workers/download.py +++ b/source/workers/download.py @@ -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,10 +49,11 @@ 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""" if sys.platform != 'win32': @@ -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,22 +121,23 @@ 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 def is_paused(self): """返回当前下载是否处于暂停状态""" return self._is_paused - + def run(self): try: if not self._is_running: @@ -144,24 +147,29 @@ class DownloadThread(QThread): aria2c_path = resource_path("aria2c-fast_x64.exe") download_dir = os.path.dirname(self._7z_path) file_name = os.path.basename(self._7z_path) - + parsed_url = urlparse(self.url) referer = f"{parsed_url.scheme}://{parsed_url.netloc}/" - + command = [ 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, @@ -179,7 +187,7 @@ class DownloadThread(QThread): '--header', 'Sec-Fetch-Site: same-origin', '--http-accept-gzip=true', '--console-log-level=notice', - '--summary-interval=1', + '--summary-interval=1', '--log-level=notice', '--max-tries=3', '--retry-wait=2', @@ -187,40 +195,37 @@ class DownloadThread(QThread): '--timeout=60', '--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', + '--split=128', + 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, @@ -258,8 +263,9 @@ class DownloadThread(QThread): last_update_time = current_time 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() \ No newline at end of file diff --git a/source/workers/ip_optimizer.py b/source/workers/ip_optimizer.py index e7742b5..f6bd443 100644 --- a/source/workers/ip_optimizer.py +++ b/source/workers/ip_optimizer.py @@ -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)