From c941c0344699769f3b2ffac449284601fa22921e Mon Sep 17 00:00:00 2001 From: hyb-oyqq <1512383570@qq.com> Date: Thu, 31 Jul 2025 14:38:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=E9=9A=90?= =?UTF-8?q?=E7=A7=81=E5=8D=8F=E8=AE=AE=E7=AE=A1=E7=90=86=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=97=A5=E5=BF=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构 PrivacyManager 类,增加隐私协议版本检查和用户同意状态管理 - 在主窗口初始化时获取云端配置,提高效率 - 添加日志功能,记录应用启动、隐私协议加载等关键事件 - 优化错误处理和用户提示信息 --- PRIVACY.md | 19 ++-- source/Main.py | 24 ++++- source/core/debug_manager.py | 6 ++ source/core/privacy_manager.py | 82 ++++++++++++---- source/core/ui_manager.py | 166 ++++++++++++++++++++++++++++++--- source/data/privacy_policy.py | 82 ++++++++++++---- source/main_window.py | 6 +- source/utils/logger.py | 47 +++++++++- 8 files changed, 367 insertions(+), 65 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index f8e947f..20c1d8f 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -9,13 +9,11 @@ 本应用在运行过程中可能会收集或处理以下信息: ### 2.1 系统信息 -- 操作系统版本信息:作为 User-Agent 的一部分,用于与服务器通信 - 程序版本号:用于检查更新和兼容性 ### 2.2 网络相关信息 -- IP 地址:在使用 Cloudflare 加速功能时,会进行 IP 优选 -- 域名解析信息:用于优化下载速度 -- 下载统计信息:用于监控下载进度和速度 +- **IP 地址、ISP 及地理位置**: 应用启动时,为获取云端配置,您的 IP 地址会被服务器记录。服务器可能会根据您的 IP 地址推断您的互联网服务提供商(ISP)和地理位置,这些信息仅用于用户数量、区域分布的统计和软件使用情况分析。当您使用 Cloudflare 加速功能时,您的 IP 地址也会被用于节点优选。 +- **下载统计信息**:用于监控下载进度和速度 ### 2.3 文件信息 - 游戏安装路径:用于识别已安装的游戏和安装补丁 @@ -31,8 +29,9 @@ - 下载加速:通过 Cloudflare 优化下载速度 ### 3.2 服务改进 -- 提供更新:检查应用版本并推送更新 -- 错误报告:收集错误信息以改进应用体验 +- **应用更新**:检查应用版本并推送更新。 +- **使用情况分析**:通过统计IP地址、ISP和地理位置等信息,分析用户下载次数与软件使用情况,以帮助我们改进服务。您的所有信息都仅用于软件使用统计,不会用于其他特殊目的。 +- **错误报告**:收集错误信息以改进应用体验。 ## 4. 数据存储 @@ -56,12 +55,12 @@ 本应用使用以下第三方服务: ### 6.1 Cloudflare -- 当您选择使用 Cloudflare 加速功能时,本应用会使用 CloudflareSpeedTest 工具测试并选择最优 IP 地址 -- 此过程会向 Cloudflare 服务器发送请求以测试连接速度 +- 本应用使用第三方开源项目 [CloudflareSpeedTest (CFST)](https://github.com/XIU2/CloudflareSpeedTest/) 为您提供 Cloudflare 加速功能。该优选服务由 CFST 项目提供,本项目及作者不负责其功能的实际维护。 +- 启用此功能时,CFST 将向 Cloudflare 的所有节点发送请求以测试延迟,此过程不可避免地会将您的 IP 地址提交至 Cloudflare。我们建议您遵循并查阅 Cloudflare 的相关用户协议和隐私政策。 ### 6.2 云端配置服务 -- 本应用从云端服务器获取配置信息(如下载链接等) -- 在此过程中,您的 IP 地址和 User-Agent 信息会被传输到云端服务器 +- 本应用启动时会从云端服务器获取配置信息(如下载链接等)。在此过程中,服务器会获取并统计您的IP地址、地理位置及ISP等信息,以用于软件使用情况分析。 +- 为确保通信安全和服务的稳定性,云端服务器设置了严格的 User-Agent 校验,仅允许本应用内置的特定 User-Agent 发出请求。非本应用指定的 User-Agent 将无法访问服务。 ## 7. 用户控制 diff --git a/source/Main.py b/source/Main.py index 04d6269..266bc6c 100644 --- a/source/Main.py +++ b/source/Main.py @@ -1,21 +1,35 @@ import sys -from PySide6.QtWidgets import QApplication +from PySide6.QtWidgets import QApplication, QMessageBox from main_window import MainWindow from core.privacy_manager import PrivacyManager +from utils.logger import setup_logger if __name__ == "__main__": + # 初始化日志 + logger = setup_logger("main") + logger.info("应用启动") + app = QApplication(sys.argv) # 初始化隐私协议管理器 - privacy_manager = PrivacyManager() + try: + privacy_manager = PrivacyManager() + except Exception as e: + logger.error(f"初始化隐私协议管理器失败: {e}") + QMessageBox.critical( + None, + "隐私协议加载错误", + f"无法加载隐私协议管理器,程序将退出。\n\n错误信息:{e}" + ) + sys.exit(1) - # 显示隐私协议对话框(仅在首次运行或用户未同意时显示) + # 显示隐私协议对话框 if not privacy_manager.show_privacy_dialog(): - print("用户未同意隐私协议,程序退出") + logger.info("用户未同意隐私协议,程序退出") sys.exit(0) # 如果用户不同意隐私协议,退出程序 # 用户已同意隐私协议,继续启动程序 - print("隐私协议已同意,启动主程序") + logger.info("隐私协议已同意,启动主程序") window = MainWindow() window.show() sys.exit(app.exec()) \ No newline at end of file diff --git a/source/core/debug_manager.py b/source/core/debug_manager.py index 7e75aba..94d8cd1 100644 --- a/source/core/debug_manager.py +++ b/source/core/debug_manager.py @@ -41,8 +41,14 @@ class DebugManager: Args: checked: 是否启用调试模式 """ + print(f"Toggle debug mode: {checked}") self.main_window.config["debug_mode"] = checked self.main_window.save_config(self.main_window.config) + + # 更新打开log文件按钮状态 + if hasattr(self, 'ui_manager') and hasattr(self.ui_manager, 'open_log_action'): + self.ui_manager.open_log_action.setEnabled(checked) + if checked: self.start_logging() else: diff --git a/source/core/privacy_manager.py b/source/core/privacy_manager.py index 160622c..2d16115 100644 --- a/source/core/privacy_manager.py +++ b/source/core/privacy_manager.py @@ -6,36 +6,77 @@ import json from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QCheckBox, QLabel, QMessageBox from PySide6.QtCore import Qt -from data.privacy_policy import PRIVACY_POLICY_BRIEF -from data.config import CACHE, APP_NAME +from data.privacy_policy import PRIVACY_POLICY_BRIEF, get_local_privacy_policy, PRIVACY_POLICY_VERSION +from data.config import CACHE, APP_NAME, APP_VERSION from utils import msgbox_frame +from utils.logger import setup_logger class PrivacyManager: """隐私协议管理器,负责显示隐私协议对话框并处理用户选择""" def __init__(self): """初始化隐私协议管理器""" + # 初始化日志 + self.logger = setup_logger("privacy_manager") + self.logger.info("正在初始化隐私协议管理器") # 确保缓存目录存在 os.makedirs(CACHE, exist_ok=True) self.config_file = os.path.join(CACHE, "privacy_config.json") - self.privacy_accepted = self._load_privacy_config() + self.privacy_config = self._load_privacy_config() + + # 获取隐私协议内容和版本 + self.logger.info("读取本地隐私协议文件") + self.privacy_content, self.current_privacy_version, error = get_local_privacy_policy() + if error: + self.logger.warning(f"读取本地隐私协议文件警告: {error}") + # 使用默认版本作为备用 + self.current_privacy_version = PRIVACY_POLICY_VERSION + self.logger.info(f"隐私协议版本: {self.current_privacy_version}") + + # 检查隐私协议版本和用户同意状态 + self.privacy_accepted = self._check_privacy_acceptance() def _load_privacy_config(self): """加载隐私协议配置 Returns: - bool: 用户是否已同意隐私协议 + dict: 隐私协议配置信息 """ if os.path.exists(self.config_file): try: with open(self.config_file, "r", encoding="utf-8") as f: config = json.load(f) - return config.get("privacy_accepted", False) + return config except (json.JSONDecodeError, IOError) as e: - print(f"读取隐私配置失败: {e}") - # 如果读取失败,返回False,强制显示隐私协议 - return False - return False + self.logger.error(f"读取隐私配置失败: {e}") + # 如果读取失败,返回空配置,强制显示隐私协议 + return {"privacy_accepted": False} + return {"privacy_accepted": False} + + def _check_privacy_acceptance(self): + """检查隐私协议是否需要重新同意 + + 如果隐私协议版本变更,则需要重新同意 + + Returns: + bool: 是否已有有效的隐私协议同意 + """ + # 获取存储的版本信息 + stored_privacy_version = self.privacy_config.get("privacy_version", "0.0.0") + stored_app_version = self.privacy_config.get("app_version", "0.0.0") + privacy_accepted = self.privacy_config.get("privacy_accepted", False) + + self.logger.info(f"存储的隐私协议版本: {stored_privacy_version}, 当前版本: {self.current_privacy_version}") + self.logger.info(f"存储的应用版本: {stored_app_version}, 当前版本: {APP_VERSION}") + self.logger.info(f"隐私协议接受状态: {privacy_accepted}") + + # 如果隐私协议版本变更,需要重新同意 + if stored_privacy_version != self.current_privacy_version: + self.logger.info("隐私协议版本已变更,需要重新同意") + return False + + # 返回当前的同意状态 + return privacy_accepted def _save_privacy_config(self, accepted): """保存隐私协议配置 @@ -50,18 +91,24 @@ class PrivacyManager: # 确保目录存在 os.makedirs(os.path.dirname(self.config_file), exist_ok=True) - # 写入配置文件 + # 写入配置文件,包含应用版本和隐私协议版本 with open(self.config_file, "w", encoding="utf-8") as f: json.dump({ "privacy_accepted": accepted, - "version": "1.0" # 添加版本号,便于将来升级隐私协议时使用 + "privacy_version": self.current_privacy_version, # 保存当前隐私协议版本 + "app_version": APP_VERSION # 保存当前应用版本 }, f, indent=2) # 更新实例变量 self.privacy_accepted = accepted + self.privacy_config = { + "privacy_accepted": accepted, + "privacy_version": self.current_privacy_version, + "app_version": APP_VERSION + } return True except IOError as e: - print(f"保存隐私协议配置失败: {e}") + self.logger.error(f"保存隐私协议配置失败: {e}") # 显示保存失败的提示 QMessageBox.warning( None, @@ -78,10 +125,10 @@ class PrivacyManager: """ # 如果用户已经同意了隐私协议,直接返回True不显示对话框 if self.privacy_accepted: - print("用户已同意隐私协议,无需再次显示") + self.logger.info("用户已同意当前版本的隐私协议,无需再次显示") return True - print("首次运行或用户未同意隐私协议,显示隐私对话框") + self.logger.info("首次运行或隐私协议版本变更,显示隐私对话框") # 创建隐私协议对话框 dialog = QDialog() @@ -92,13 +139,14 @@ class PrivacyManager: # 创建布局 layout = QVBoxLayout(dialog) - # 添加标题 - title_label = QLabel("请阅读并同意以下隐私政策") + # 添加标题和版本信息 + title_label = QLabel(f"请阅读并同意以下隐私政策 (更新日期: {self.current_privacy_version})") title_label.setStyleSheet("font-size: 14px; font-weight: bold;") layout.addWidget(title_label) # 添加隐私协议文本框 text_browser = QTextBrowser() + # 这里使用PRIVACY_POLICY_BRIEF而不是self.privacy_content,保持UI简洁 text_browser.setMarkdown(PRIVACY_POLICY_BRIEF) text_browser.setOpenExternalLinks(True) layout.addWidget(text_browser) @@ -118,7 +166,7 @@ class PrivacyManager: # 连接选择框状态变化 - 修复勾选后按钮不亮起的问题 def on_checkbox_state_changed(state): - print(f"复选框状态变更为: {state}") + self.logger.debug(f"复选框状态变更为: {state}") agree_button.setEnabled(state == 2) # Qt.Checked 在 PySide6 中值为 2 checkbox.stateChanged.connect(on_checkbox_state_changed) diff --git a/source/core/ui_manager.py b/source/core/ui_manager.py index c853b21..a992f49 100644 --- a/source/core/ui_manager.py +++ b/source/core/ui_manager.py @@ -5,7 +5,7 @@ import webbrowser import os from utils import load_base64_image, msgbox_frame, resource_path -from data.config import APP_NAME, APP_VERSION +from data.config import APP_NAME, APP_VERSION, LOG_FILE class UIManager: def __init__(self, main_window): @@ -269,9 +269,32 @@ class UIManager: menu_style = self._get_menu_style(font_family) self.dev_menu.setStyleSheet(menu_style) - # 创建Debug模式选项并添加到开发者选项子菜单中 - self.debug_action = QAction("Debug模式", self.main_window, checkable=True) - self.debug_action.setFont(menu_font) # 设置相同的字体 + # 创建Debug子菜单 + self.debug_submenu = QMenu("Debug模式", self.main_window) + self.debug_submenu.setFont(menu_font) + self.debug_submenu.setStyleSheet(menu_style) + + # 创建hosts文件选项子菜单 + self.hosts_submenu = QMenu("hosts文件选项", self.main_window) + self.hosts_submenu.setFont(menu_font) + self.hosts_submenu.setStyleSheet(menu_style) + + # 添加hosts子选项 + self.restore_hosts_action = QAction("还原软件备份的hosts文件", self.main_window) + self.restore_hosts_action.setFont(menu_font) + self.restore_hosts_action.triggered.connect(self.restore_hosts_backup) + + self.clean_hosts_action = QAction("手动删除软件添加的hosts条目", self.main_window) + self.clean_hosts_action.setFont(menu_font) + self.clean_hosts_action.triggered.connect(self.clean_hosts_entries) + + # 添加到hosts子菜单 + self.hosts_submenu.addAction(self.restore_hosts_action) + self.hosts_submenu.addAction(self.clean_hosts_action) + + # 创建Debug开关选项 + self.debug_action = QAction("Debug开关", self.main_window, checkable=True) + self.debug_action.setFont(menu_font) # 安全地获取config属性 config = getattr(self.main_window, 'config', {}) @@ -285,24 +308,33 @@ class UIManager: if hasattr(self.main_window, 'toggle_debug_mode'): self.debug_action.triggered.connect(self.main_window.toggle_debug_mode) - # 创建狂暴下载选项(无功能) - self.turbo_download_action = QAction("狂暴下载", self.main_window) - self.turbo_download_action.setFont(menu_font) # 设置自定义字体 - self.turbo_download_action.setEnabled(False) # 禁用按钮 + # 创建打开log文件选项 + self.open_log_action = QAction("打开log.txt", self.main_window) + self.open_log_action.setFont(menu_font) + # 初始状态根据debug模式设置启用状态 + self.open_log_action.setEnabled(debug_mode) - # 添加到开发者选项子菜单 - self.dev_menu.addAction(self.debug_action) - self.dev_menu.addAction(self.turbo_download_action) + # 连接打开log文件的事件 + self.open_log_action.triggered.connect(self.open_log_file) - # 为未来功能预留的"切换下载源"按钮 - self.switch_source_action = QAction("切换下载源", self.main_window) + # 添加到Debug子菜单 + self.debug_submenu.addAction(self.debug_action) + self.debug_submenu.addAction(self.open_log_action) + + # 为未来功能预留的"修改下载源"按钮 - 现在点击时显示"正在开发中" + self.switch_source_action = QAction("修改下载源", self.main_window) self.switch_source_action.setFont(menu_font) # 设置自定义字体 - self.switch_source_action.setEnabled(False) # 暂时禁用 + self.switch_source_action.setEnabled(True) # 启用但显示"正在开发中" + self.switch_source_action.triggered.connect(self.show_under_development) # 添加到主菜单 self.ui.menu.addAction(self.switch_source_action) self.ui.menu.addSeparator() self.ui.menu.addMenu(self.dev_menu) # 添加开发者选项子菜单 + + # 添加Debug子菜单到开发者选项菜单 + self.dev_menu.addMenu(self.debug_submenu) + self.dev_menu.addMenu(self.hosts_submenu) # 添加hosts文件选项子菜单 def show_menu(self, menu, button): """显示菜单 @@ -409,6 +441,112 @@ class UIManager: ) error_msg.exec() + def show_under_development(self): + """显示功能正在开发中的提示""" + msg_box = msgbox_frame( + f"提示 - {APP_NAME}", + "\n该功能正在开发中,敬请期待!\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + + def open_log_file(self): + """打开log.txt文件""" + if os.path.exists(LOG_FILE): + try: + # 使用操作系统默认程序打开日志文件 + if os.name == 'nt': # Windows + os.startfile(LOG_FILE) + else: # macOS 和 Linux + import subprocess + subprocess.call(['xdg-open', LOG_FILE]) + except Exception as e: + msg_box = msgbox_frame( + f"错误 - {APP_NAME}", + f"\n打开log.txt文件失败:\n\n{str(e)}\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + else: + msg_box = msgbox_frame( + f"提示 - {APP_NAME}", + "\nlog.txt文件不存在,请确保Debug模式已开启并生成日志。\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + + def restore_hosts_backup(self): + """还原软件备份的hosts文件""" + if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'): + try: + # 调用恢复hosts文件的方法 + result = self.main_window.download_manager.hosts_manager.restore() + + if result: + msg_box = msgbox_frame( + f"成功 - {APP_NAME}", + "\nhosts文件已成功还原为备份版本。\n", + QMessageBox.StandardButton.Ok, + ) + else: + msg_box = msgbox_frame( + f"警告 - {APP_NAME}", + "\n还原hosts文件失败或没有找到备份文件。\n", + QMessageBox.StandardButton.Ok, + ) + + msg_box.exec() + except Exception as e: + msg_box = msgbox_frame( + f"错误 - {APP_NAME}", + f"\n还原hosts文件时发生错误:\n\n{str(e)}\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + else: + msg_box = msgbox_frame( + f"错误 - {APP_NAME}", + "\n无法访问hosts管理器。\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + + def clean_hosts_entries(self): + """手动删除软件添加的hosts条目""" + if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'): + try: + # 调用清理hosts条目的方法 + result = self.main_window.download_manager.hosts_manager.check_and_clean_all_entries() + + if result: + msg_box = msgbox_frame( + f"成功 - {APP_NAME}", + "\n已成功清理软件添加的hosts条目。\n", + QMessageBox.StandardButton.Ok, + ) + else: + msg_box = msgbox_frame( + f"提示 - {APP_NAME}", + "\n未发现软件添加的hosts条目或清理操作失败。\n", + QMessageBox.StandardButton.Ok, + ) + + msg_box.exec() + except Exception as e: + msg_box = msgbox_frame( + f"错误 - {APP_NAME}", + f"\n清理hosts条目时发生错误:\n\n{str(e)}\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + else: + msg_box = msgbox_frame( + f"错误 - {APP_NAME}", + "\n无法访问hosts管理器。\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + def show_about_dialog(self): """显示关于对话框""" about_text = f""" diff --git a/source/data/privacy_policy.py b/source/data/privacy_policy.py index 243b11d..94497de 100644 --- a/source/data/privacy_policy.py +++ b/source/data/privacy_policy.py @@ -1,6 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os +import re +import sys +from datetime import datetime + # 隐私协议的缩略版内容 PRIVACY_POLICY_BRIEF = """ # FRAISEMOE Addons Installer NEXT 隐私政策摘要 @@ -8,19 +13,19 @@ PRIVACY_POLICY_BRIEF = """ 本应用在运行过程中会收集和处理以下信息: ## 收集的信息 -- 系统信息:操作系统版本、程序版本号 -- 网络信息:IP 地址(Cloudflare 加速时)、域名解析、下载统计 -- 文件信息:游戏安装路径、文件哈希值 +- **系统信息**:程序版本号。 +- **网络信息**:IP 地址、ISP、地理位置(用于使用统计)、下载统计。 +- **文件信息**:游戏安装路径、文件哈希值。 ## 系统修改 -- 使用 Cloudflare 加速时会临时修改系统 hosts 文件 -- 修改前会自动备份,程序退出时自动恢复 +- 使用 Cloudflare 加速时会临时修改系统 hosts 文件。 +- 修改前会自动备份,程序退出时自动恢复。 ## 第三方服务 -- Cloudflare 服务:用于测试和优化下载速度 -- 云端配置服务:获取配置信息和下载链接 +- **Cloudflare 服务**:通过开源项目 CloudflareSpeedTest (CFST) 提供,用于优化下载速度。此过程会将您的 IP 提交至 Cloudflare 节点。 +- **云端配置服务**:获取配置信息。服务器会记录您的 IP、ISP 及地理位置用于统计。 -完整的隐私政策可在程序中访问github仓库查看。 +完整的隐私政策可在本程序的 GitHub 仓库中查看。 """ # 隐私协议的英文版缩略版内容 @@ -30,17 +35,60 @@ PRIVACY_POLICY_BRIEF_EN = """ This application collects and processes the following information: ## Information Collected -- System info: OS version, application version -- Network info: IP address (for Cloudflare acceleration), DNS resolution, download statistics -- File info: Game installation paths, file hash values +- **System info**: Application version. +- **Network info**: IP address, ISP, geographic location (for usage statistics), download statistics. +- **File info**: Game installation paths, file hash values. ## System Modifications -- Temporarily modifies system hosts file when using Cloudflare acceleration -- Automatically backs up before modification and restores upon exit +- Temporarily modifies system hosts file when using Cloudflare acceleration. +- Automatically backs up before modification and restores upon exit. ## Third-party Services -- Cloudflare services: Used for testing and optimizing download speeds -- Cloud configuration services: For obtaining configuration information and download links +- **Cloudflare services**: Provided via the open-source project CloudflareSpeedTest (CFST) to optimize download speeds. This process submits your IP to Cloudflare nodes. +- **Cloud configuration services**: For obtaining configuration information. The server logs your IP, ISP, and location for statistical purposes. -The complete privacy policy can be found in the github repository of the program. -""" \ No newline at end of file +The complete privacy policy can be found in the program's GitHub repository. +""" + +# 默认隐私协议版本 - 本地版本的日期 +PRIVACY_POLICY_VERSION = "2025.07.31" + +def get_local_privacy_policy(): + """获取本地打包的隐私协议文件 + + Returns: + tuple: (隐私协议内容, 版本号, 错误信息) + """ + # 尝试不同的可能路径 + possible_paths = [ + "PRIVACY.md", # 相对于可执行文件 + os.path.join(os.path.dirname(sys.executable), "PRIVACY.md"), # 可执行文件目录 + os.path.join(os.path.dirname(__file__), "PRIVACY.md"), # 当前模块目录 + ] + + for path in possible_paths: + try: + if os.path.exists(path): + with open(path, "r", encoding="utf-8") as f: + content = f.read() + + # 提取更新日期 + date_pattern = r'最后更新日期:(\d{4}年\d{1,2}月\d{1,2}日)' + match = re.search(date_pattern, content) + + if match: + date_str = match.group(1) + try: + date_obj = datetime.strptime(date_str, '%Y年%m月%d日') + date_version = date_obj.strftime('%Y.%m.%d') + print(f"成功读取本地隐私协议文件: {path}, 版本: {date_version}") + return content, date_version, "" + except ValueError: + print(f"本地隐私协议日期格式解析错误: {path}") + else: + print(f"本地隐私协议未找到更新日期: {path}") + except Exception as e: + print(f"读取本地隐私协议失败 {path}: {str(e)}") + + # 所有路径都尝试失败,使用默认版本 + return PRIVACY_POLICY_BRIEF, PRIVACY_POLICY_VERSION, "无法读取本地隐私协议文件" \ No newline at end of file diff --git a/source/main_window.py b/source/main_window.py index 907dc3b..cf3ddb8 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -152,6 +152,7 @@ class MainWindow(QMainWindow): self.animator.animation_finished.connect(self.on_animations_finished) self.animator.start_animations() + # 在动画开始时获取云端配置 self.fetch_cloud_config() def on_animations_finished(self): @@ -186,7 +187,7 @@ class MainWindow(QMainWindow): self.install_button_enabled = enabled def fetch_cloud_config(self): - """获取云端配置""" + """获取云端配置(异步方式)""" self.config_manager.fetch_cloud_config( lambda url, headers, debug_mode, parent=None: ConfigFetchThread(url, headers, debug_mode, self), self.on_config_fetched @@ -223,6 +224,9 @@ class MainWindow(QMainWindow): self.cloud_config = self.config_manager.get_cloud_config() self.config_valid = self.config_manager.is_config_valid() self.last_error_message = self.config_manager.get_last_error() + + # 重新启用窗口,恢复用户交互 + self.setEnabled(True) def toggle_debug_mode(self, checked): """切换调试模式 diff --git a/source/utils/logger.py b/source/utils/logger.py index b41a1b6..236d2ee 100644 --- a/source/utils/logger.py +++ b/source/utils/logger.py @@ -1,4 +1,7 @@ from .helpers import censor_url +import logging +import os +from data.config import CACHE class Logger: def __init__(self, filename, stream): @@ -16,4 +19,46 @@ class Logger: self.log.flush() def close(self): - self.log.close() \ No newline at end of file + self.log.close() + +def setup_logger(name): + """设置并返回一个命名的logger + + Args: + name: logger的名称 + + Returns: + logging.Logger: 配置好的logger对象 + """ + # 创建logger + logger = logging.getLogger(name) + + # 避免重复添加处理器 + if logger.hasHandlers(): + return logger + + logger.setLevel(logging.DEBUG) + + # 确保日志目录存在 + log_dir = os.path.join(CACHE, "logs") + 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) + + # 创建控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + + # 创建格式器并添加到处理器 + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # 添加处理器到logger + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger \ No newline at end of file