feat(core): 优化主窗口和管理器功能

- 在主窗口中重构初始化逻辑,增强UI组件的管理和信号连接,提升代码可读性。
- 添加资源验证和加载测试功能,确保关键资源文件的完整性和可用性。
- 更新下载管理器和离线模式管理器,优化线程管理和状态更新,提升用户体验。
- 增强日志记录,确保在关键操作中提供详细的调试信息,便于后续排查和用户反馈。
- 删除不再使用的进度窗口创建函数,改为由UIManager管理,提升代码整洁性。
This commit is contained in:
hyb-oyqq
2025-08-11 17:42:52 +08:00
parent dc433a2ac9
commit 68bbafc564
12 changed files with 842 additions and 551 deletions

View File

@@ -101,24 +101,48 @@ class ProgressHashVerifyDialog(QDialog):
self.status_label.setText(status)
def resource_path(relative_path):
"""获取资源的绝对路径,适用于开发环境和Nuitka打包环境"""
if getattr(sys, 'frozen', False):
# Nuitka/PyInstaller创建的临时文件夹并将路径存储在_MEIPASS中或与可执行文件同目录
if hasattr(sys, '_MEIPASS'):
base_path = sys._MEIPASS
"""获取资源的绝对路径,适用于开发环境和打包环境"""
try:
if getattr(sys, 'frozen', False):
# 打包环境 - 可执行文件所在目录
if hasattr(sys, '_MEIPASS'):
# PyInstaller打包的临时目录
base_path = sys._MEIPASS
else:
# 其他打包方式,直接使用可执行文件目录
base_path = os.path.dirname(sys.executable)
# 对于离线补丁文件,需要在可执行文件所在目录查找
if relative_path.lower() in ["vol.1.7z", "vol.2.7z", "vol.3.7z", "vol.4.7z", "after.7z"]:
exe_dir = os.path.dirname(sys.executable)
patch_path = os.path.join(exe_dir, relative_path)
if os.path.exists(patch_path):
logger.debug(f"找到离线补丁文件: {patch_path}")
return patch_path
else:
base_path = os.path.dirname(sys.executable)
else:
# 在开发环境中运行
base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
# 在开发环境中运行
base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
# 处理特殊的可执行文件和数据文件路径
if relative_path in ("aria2c-fast_x64.exe", "cfst.exe"):
return os.path.join(base_path, 'bin', relative_path)
result_path = os.path.join(base_path, 'bin', relative_path)
elif relative_path in ("ip.txt", "ipv6.txt"):
return os.path.join(base_path, 'data', relative_path)
result_path = os.path.join(base_path, 'data', relative_path)
else:
# 标准资源路径
result_path = os.path.join(base_path, relative_path)
# 记录资源路径并验证是否存在
if not os.path.exists(result_path) and relative_path: # 只在非空路径时检查
logger.warning(f"资源文件不存在: {result_path}")
elif relative_path: # 避免记录空路径
logger.debug(f"已找到资源文件: {result_path}")
return os.path.join(base_path, relative_path)
return result_path
except Exception as e:
logger.error(f"资源路径解析错误 ({relative_path}): {e}")
# 出错时仍返回一个基本路径
return os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", relative_path)
def load_base64_image(base64_str):
pixmap = QPixmap()
@@ -134,9 +158,57 @@ def load_image_from_file(file_path):
Returns:
QPixmap: 加载的图像
"""
if os.path.exists(file_path):
return QPixmap(file_path)
return QPixmap()
try:
if os.path.exists(file_path):
logger.info(f"加载图片: {file_path}")
pixmap = QPixmap(file_path)
if pixmap.isNull():
logger.error(f"图片加载失败(pixmap为空): {file_path}")
# 检查文件大小和是否可读
try:
file_size = os.path.getsize(file_path)
logger.debug(f"图片文件大小: {file_size} 字节")
if file_size == 0:
logger.error(f"图片文件大小为0字节: {file_path}")
# 尝试打开文件测试可读性
with open(file_path, 'rb') as f:
# 只读取前几个字节测试可访问性
f.read(10)
logger.debug(f"图片文件可以正常打开和读取")
# 检查文件扩展名是否正确
ext = os.path.splitext(file_path)[1].lower()
if ext not in ['.png', '.jpg', '.jpeg', '.bmp', '.ico']:
logger.warning(f"图片文件扩展名可能不受支持: {ext}")
except Exception as file_error:
logger.error(f"图片文件访问错误: {file_error}")
return QPixmap()
else:
logger.debug(f"图片加载成功: {file_path}, 大小: {pixmap.width()}x{pixmap.height()}")
return pixmap
else:
logger.warning(f"图片文件不存在: {file_path}")
# 尝试列出父目录下的文件
try:
parent_dir = os.path.dirname(file_path)
if os.path.exists(parent_dir):
files = os.listdir(parent_dir)
logger.debug(f"目录 {parent_dir} 中的文件: {files}")
else:
logger.debug(f"目录不存在: {parent_dir}")
except Exception as dir_error:
logger.error(f"无法列出目录内容: {dir_error}")
return QPixmap()
except Exception as e:
logger.error(f"加载图片时发生异常: {e}")
logger.error(f"异常详情: {traceback.format_exc()}")
return QPixmap()
def msgbox_frame(title, text, buttons=QtWidgets.QMessageBox.StandardButton.NoButton):
msg_box = QtWidgets.QMessageBox()

View File

@@ -1,7 +1,13 @@
from .url_censor import censor_url
import logging
import os
from config.config import CACHE
import logging
import datetime
import sys
import glob
import time
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from config.config import LOG_DIR, LOG_FILE, LOG_LEVEL, LOG_MAX_SIZE, LOG_BACKUP_COUNT, LOG_RETENTION_DAYS
from .url_censor import censor_url
class URLCensorFormatter(logging.Formatter):
"""自定义的日志格式化器用于隐藏日志消息中的URL"""
@@ -44,7 +50,7 @@ class Logger:
except Exception as e:
# 发生错误时记录到控制台
self.terminal.write(f"Error writing to log: {e}\n")
def flush(self):
try:
self.terminal.flush()
@@ -62,63 +68,90 @@ class Logger:
except Exception:
pass
def cleanup_old_logs(retention_days=7):
"""清理超过指定天数的旧日志文件
Args:
retention_days: 日志保留天数默认7天
"""
try:
now = time.time()
cutoff = now - (retention_days * 86400) # 86400秒 = 1天
# 获取所有日志文件
log_files = glob.glob(os.path.join(LOG_DIR, "log-*.txt"))
for log_file in log_files:
# 检查文件修改时间
if os.path.getmtime(log_file) < cutoff:
try:
os.remove(log_file)
print(f"已删除过期日志: {log_file}")
except Exception as e:
print(f"删除日志文件失败 {log_file}: {e}")
except Exception as e:
print(f"清理旧日志文件时出错: {e}")
def setup_logger(name):
"""设置并返回一个命名的logger
使用统一的日志文件,添加日志轮转功能,实现自动清理过期日志
Args:
name: logger的名称
Returns:
logging.Logger: 配置好的logger对象
"""
# 导入LOG_FILE
from config.config import LOG_FILE
# 创建logger
logger = logging.getLogger(name)
# 避免重复添加处理器
if logger.hasHandlers():
return logger
logger.setLevel(logging.DEBUG)
# 根据配置设置日志级别
log_level = getattr(logging, LOG_LEVEL.upper(), logging.DEBUG)
logger.setLevel(log_level)
# 确保日志目录存在
log_dir = os.path.join(CACHE, "logs")
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, f"{name}.log")
os.makedirs(LOG_DIR, exist_ok=True)
# 创建文件处理器 - 模块日志
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
# 清理过期日志文件
cleanup_old_logs(LOG_RETENTION_DAYS)
# 创建主日志文件处理器 - 所有日志合并到主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)
# 使用RotatingFileHandler实现日志轮转
main_file_handler = RotatingFileHandler(
LOG_FILE,
maxBytes=LOG_MAX_SIZE,
backupCount=LOG_BACKUP_COUNT,
encoding="utf-8"
)
main_file_handler.setLevel(log_level)
except (IOError, OSError) as e:
print(f"无法创建主日志文件处理器: {e}")
main_file_handler = None
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setLevel(logging.INFO) # 控制台只显示INFO以上级别
# 创建更详细的格式器,包括模块名、文件名和行号
formatter = URLCensorFormatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s')
# 创建格式器并添加到处理器
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)