docs: 完善README文档与架构图,修复全量注释与日志问题
- README: 补充项目概述、技术栈、架构图(Mermaid)、插件生命周期时序图 - build.py: 补充模块和函数 docstring - core/__init__.py: 修正模块说明(工具箱→插件系统) - log.py: 修复过时注释,清理死代码,抑制第三方库日志刷屏 - plugin_registry.py: 修正矛盾注释,补充缺失日志,隐藏远程路径 - plugins.py: 修正过时注释,清理死代码和无用导入 - plugins_card.py: 修正错误类名注释,补充全量 docstring - main.py: 修正错误注释,补充启动/退出/插件加载日志
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@ Core 模块 - 核心功能模块包
|
||||
|
||||
本包包含应用程序的核心功能组件:
|
||||
- 日志系统(logging 包)
|
||||
- 工具箱系统(tools 包)
|
||||
- 插件系统(plugin 包)
|
||||
|
||||
提供统一的接口导出,方便其他模块调用。
|
||||
|
||||
|
||||
+8
-13
@@ -20,7 +20,7 @@ from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from PySide6.QtWidgets import QFrame, QTextEdit
|
||||
from PySide6.QtWidgets import QFrame
|
||||
|
||||
from .log_ui import Ui_FrameLog
|
||||
|
||||
@@ -115,6 +115,10 @@ class FrameLog(QFrame, Ui_FrameLog):
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# 抑制第三方库 DEBUG 日志刷屏
|
||||
logging.getLogger("PySide6").setLevel(logging.WARNING)
|
||||
logging.getLogger("qt_material").setLevel(logging.WARNING)
|
||||
|
||||
# 创建日志格式化器:时间 [级别] 消息
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s [%(levelname)s] %(message)s",
|
||||
@@ -190,20 +194,11 @@ class FrameLog(QFrame, Ui_FrameLog):
|
||||
self.log_signal.connect(self._update_log_display)
|
||||
|
||||
def _update_log_display(self, message: str) -> None:
|
||||
"""
|
||||
更新 UI 的日志显示组件
|
||||
"""更新 UI 的日志显示组件。
|
||||
|
||||
将日志消息追加到 TextEdit 组件中显示
|
||||
将日志消息追加到 TextEdit 组件中显示。
|
||||
|
||||
Args:
|
||||
message: 格式化后的日志消息字符串
|
||||
|
||||
实现细节:
|
||||
- 使用 findChild 查找 TextEdit 组件
|
||||
- 使用 append 方法追加日志消息,自动换行
|
||||
"""
|
||||
self.textEdit.append(message)
|
||||
|
||||
# text_edit = self.findChild(QTextEdit, "textEdit")
|
||||
# if text_edit:
|
||||
# text_edit.append(message)
|
||||
self.textEdit.append(message)
|
||||
@@ -40,8 +40,9 @@ def _load_module_from_path(module_name: str, file_path: Path):
|
||||
|
||||
用 spec_from_file_location 直接从 .pyd 文件构建模块,避免
|
||||
importlib.import_module 命中同名旧缓存。注意:.pyd 的入口函数
|
||||
PyInit_<name> 与文件名绑定,module_name 必须等于文件 stem,
|
||||
不能用别名。
|
||||
PyInit_<name> 与文件名绑定,module_name 必须等于 _pyd_module_name()
|
||||
提取的模块名(第一个点号之前的部分),不能直接用完整 stem。
|
||||
例如 mil.cp311-win_amd64.pyd 的 module_name 必须是 "mil"。
|
||||
"""
|
||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
||||
if spec is None or spec.loader is None:
|
||||
@@ -91,9 +92,9 @@ class PluginWorker(QObject):
|
||||
def do_discover_remote(self, plugins_dir: str) -> None:
|
||||
"""扫描远程 *.zip,读 zip 内 manifest.json 取插件元数据。"""
|
||||
if not os.path.exists(plugins_dir):
|
||||
logger.error(f"服务器链接错误!路径不存在: {plugins_dir}")
|
||||
logger.error(f"远程插件路径不可达")
|
||||
return
|
||||
logger.info(f"开始扫描远程插件 zip: {plugins_dir}")
|
||||
logger.info(f"开始扫描远程插件 zip")
|
||||
self.remote_discovered.emit(
|
||||
self._scan_remote_zips(Path(plugins_dir))
|
||||
)
|
||||
@@ -125,7 +126,7 @@ class PluginWorker(QObject):
|
||||
tmp_zip.unlink()
|
||||
self.install_finished.emit(True, f"安装成功: {local_dir}")
|
||||
except Exception as e:
|
||||
logger.error(f"{remote_zip}安装失败{e.args}")
|
||||
logger.error(f"{remote_zip} 安装失败: {e}")
|
||||
self.install_finished.emit(False, str(e))
|
||||
|
||||
@Slot(str, str)
|
||||
@@ -166,6 +167,7 @@ class PluginWorker(QObject):
|
||||
def _ensure_dir(plugins_dir: Path) -> bool:
|
||||
if not os.path.exists(plugins_dir):
|
||||
os.mkdir(plugins_dir)
|
||||
logger.info(f"插件目录已创建: {plugins_dir}")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -231,18 +233,21 @@ class PluginRegistry(QObject):
|
||||
主线程。所有跨线程通信经 Qt 信号槽(Queued)。
|
||||
"""
|
||||
|
||||
# --- 对外信号:通知主窗口 UI 更新 ---
|
||||
plugins_loader_signal = Signal(dict)
|
||||
update_plugins_card_signal = Signal(dict)
|
||||
uninstall_completed_signal = Signal(str)
|
||||
install_progress = Signal(str, int, int) # name, copied_bytes, total_bytes
|
||||
install_finished = Signal(bool, str)
|
||||
|
||||
# --- 内部信号:主线程 → Worker 线程(跨线程请求) ---
|
||||
_discover_local_requested = Signal(str)
|
||||
_discover_remote_requested = Signal(str)
|
||||
_install_requested = Signal(str, str)
|
||||
_uninstall_requested = Signal(str, str)
|
||||
_update_requested = Signal(str, str, str)
|
||||
|
||||
# 自重启后延迟启动,等待旧进程释放 .pyd 文件锁(毫秒)
|
||||
_PENDING_CLEANUP_DELAY = 1500
|
||||
|
||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
@@ -284,6 +289,7 @@ class PluginRegistry(QObject):
|
||||
"""
|
||||
self._process_pending_uninstalls()
|
||||
self._thread.start()
|
||||
logger.info("插件系统初始化完成,开始发现插件")
|
||||
QTimer.singleShot(0, self._start_discovery)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
@@ -292,8 +298,10 @@ class PluginRegistry(QObject):
|
||||
必须在 QThread 析构前调用,否则触发
|
||||
"QThread: Destroyed while thread is still running" 警告。
|
||||
"""
|
||||
logger.info("PluginRegistry 正在关闭...")
|
||||
self._thread.quit()
|
||||
self._thread.wait(3000)
|
||||
logger.info("PluginRegistry 已关闭")
|
||||
|
||||
@staticmethod
|
||||
def _process_pending_uninstalls() -> None:
|
||||
@@ -309,6 +317,7 @@ class PluginRegistry(QObject):
|
||||
"""
|
||||
if not UNINSTALL_PENDING_FILE.exists():
|
||||
return
|
||||
logger.info("检测到待处理卸载/更新清单,开始清理...")
|
||||
lines = UNINSTALL_PENDING_FILE.read_text(encoding='utf-8').splitlines()
|
||||
failed: list = []
|
||||
for line in lines:
|
||||
@@ -354,11 +363,18 @@ class PluginRegistry(QObject):
|
||||
UNINSTALL_PENDING_FILE.unlink()
|
||||
|
||||
def _start_discovery(self) -> None:
|
||||
"""启动插件发现:扫描本地 pyd 和远程 zip。"""
|
||||
self._discover_local_requested.emit(str(LOCAL_PLUGINS_PATH))
|
||||
self._discover_remote_requested.emit(str(REMOTE_PLUGINS_PATH))
|
||||
|
||||
@Slot(list)
|
||||
def _on_local_discovered(self, results: list) -> None:
|
||||
"""处理本地扫描结果:import_module + create_plugin,注册到 plugins 字典。
|
||||
|
||||
本地扫描在远程扫描之前完成,因此先发射 plugins_loader_signal
|
||||
让主窗口把已安装插件加入工具栏,再发射 update_plugins_card_signal
|
||||
刷新卡片列表(安装后 local version 已更新)。
|
||||
"""
|
||||
for item in results:
|
||||
name = item["name"]
|
||||
try:
|
||||
@@ -367,6 +383,7 @@ class PluginRegistry(QObject):
|
||||
except Exception as e:
|
||||
logger.error(f"本地插件 {name} 实例化失败,跳过: {e}")
|
||||
continue
|
||||
logger.info(f"本地插件已加载: {name} V{item['version']}")
|
||||
if name not in self.plugins:
|
||||
self.plugins[name] = {}
|
||||
self.plugins[name]["obj"] = obj
|
||||
@@ -378,6 +395,7 @@ class PluginRegistry(QObject):
|
||||
|
||||
@Slot(list)
|
||||
def _on_remote_discovered(self, results: list) -> None:
|
||||
"""处理远程扫描结果:填充远程版本/描述/路径,刷新卡片列表。"""
|
||||
for item in results:
|
||||
name = item["name"]
|
||||
if name not in self.plugins:
|
||||
@@ -392,6 +410,7 @@ class PluginRegistry(QObject):
|
||||
|
||||
@Slot(bool, str)
|
||||
def _on_install_finished(self, success: bool, message: str) -> None:
|
||||
"""安装完成回调:成功则把新解压目录加入 sys.path 并重新扫描本地。"""
|
||||
if success:
|
||||
logger.info(message)
|
||||
for sub in LOCAL_PLUGINS_PATH.iterdir():
|
||||
@@ -417,6 +436,7 @@ class PluginRegistry(QObject):
|
||||
|
||||
@Slot(str, bool, str)
|
||||
def _on_uninstall_finished(self, name: str, success: bool, message: str) -> None:
|
||||
"""卸载/更新完成回调:清除 plugins 字典中对应项的 obj 和版本,通知主窗口。"""
|
||||
if not success:
|
||||
logger.error(message)
|
||||
return
|
||||
@@ -442,8 +462,10 @@ class PluginRegistry(QObject):
|
||||
remote_path = self.plugins[name]['remote path']
|
||||
self._install_requested.emit(local_dir, remote_path)
|
||||
elif event == Event.Uninstall:
|
||||
logger.info(f"[{name}] 开始卸载...")
|
||||
self._uninstall_requested.emit(name, local_dir)
|
||||
elif event == Event.Update:
|
||||
self._installing_name = name
|
||||
logger.info(f"[{name}] 开始更新...")
|
||||
remote_path = self.plugins[name]['remote path']
|
||||
self._update_requested.emit(name, local_dir, remote_path)
|
||||
|
||||
+19
-75
@@ -1,64 +1,47 @@
|
||||
"""
|
||||
工具箱模块
|
||||
插件管理模块
|
||||
|
||||
提供工具箱主窗口,包含各种实用工具的入口。
|
||||
提供插件管理主窗口,包含插件卡片列表和安装/更新/卸载入口。
|
||||
使用 PySide6 构建的窗口框架。
|
||||
|
||||
主要功能:
|
||||
- 工具箱主窗口界面
|
||||
- 窗口居中显示功能
|
||||
- 工具分类和导航
|
||||
- 插件管理窗口界面
|
||||
- 插件卡片列表渲染与状态刷新
|
||||
- 插件事件的转发与处理
|
||||
|
||||
Author: Model Team
|
||||
Version: 0.0.1
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import threading
|
||||
import importlib
|
||||
|
||||
from qt_material import apply_stylesheet
|
||||
|
||||
from pathlib import Path
|
||||
from .plugins_ui import Ui_FormPlugins
|
||||
from PySide6.QtGui import QIcon
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QListWidgetItem
|
||||
from PySide6.QtCore import QSize, Signal
|
||||
from PySide6.QtCore import QSize
|
||||
|
||||
from .plugins_card import PluginsCard
|
||||
from .plugin_registry import PluginRegistry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LOCAL_PLUGINS_PATH = Path("./plugins")
|
||||
REMOTE_PLUGINS_PATH = Path('Y:/SE/xufeifei/plugins')
|
||||
|
||||
class Plugins(QWidget, Ui_FormPlugins):
|
||||
"""工具箱主窗口类
|
||||
|
||||
继承自 QWidget(Qt 窗口基类)和 Ui_FormTools(Qt Designer 生成的 UI 界面)
|
||||
负责显示工具箱的主界面,提供工具的分类和导航功能。
|
||||
|
||||
窗口特性:
|
||||
- 默认尺寸: 400x293 像素
|
||||
- 支持窗口居中显示
|
||||
- 可作为独立窗口或嵌入其他窗口使用
|
||||
|
||||
Attributes:
|
||||
parent: 父窗口对象,默认为 None(顶级窗口)
|
||||
"""插件管理窗口。
|
||||
|
||||
继承自 QWidget 和 Ui_FormPlugins(Qt Designer 生成的 UI 界面),
|
||||
负责展示插件卡片列表,处理插件安装/更新/卸载事件的转发。
|
||||
|
||||
可作为独立窗口或嵌入其他窗口使用。
|
||||
"""
|
||||
update_tool_card = Signal(dict)
|
||||
|
||||
def __init__(self, plugin_registry: PluginRegistry, parent: QWidget = None) -> None:
|
||||
"""初始化插件管理窗口。
|
||||
|
||||
def __init__(self, plugin_registry:PluginRegistry, parent: QWidget = None) -> None:
|
||||
"""初始化工具箱窗口
|
||||
|
||||
创建工具箱窗口实例,初始化 UI 界面。
|
||||
|
||||
Args:
|
||||
parent: 父窗口对象,用于建立父子关系。
|
||||
默认为 None,表示这是顶级窗口。
|
||||
plugin_registry: 插件注册中心实例,用于事件转发
|
||||
parent: 父窗口对象,默认为 None(顶级窗口)
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
@@ -72,16 +55,10 @@ class Plugins(QWidget, Ui_FormPlugins):
|
||||
self.plugin_registry.uninstall_completed_signal.connect(self.on_plugin_uninstalled)
|
||||
|
||||
def initUI(self) -> None:
|
||||
"""
|
||||
初始化用户界面
|
||||
|
||||
配置窗口标题、图标、大小和居中显示。
|
||||
"""
|
||||
"""初始化窗口标题和图标。"""
|
||||
self.setWindowTitle("工具箱")
|
||||
self.setWindowIcon(QIcon('resources/tools.ico'))
|
||||
|
||||
# self.update_tool_card.connect(self.on_update_tool_card)
|
||||
|
||||
def load_stylesheet(self) -> None:
|
||||
"""
|
||||
加载 Qt 样式表和自定义样式文件
|
||||
@@ -103,40 +80,7 @@ class Plugins(QWidget, Ui_FormPlugins):
|
||||
except Exception as e:
|
||||
logger.warning(f"样式加载失败,使用默认主题: {e}")
|
||||
|
||||
# def start_load_tools(self):
|
||||
# self.sub_thread = threading.Thread(target= self.load_tool_list,args=(self.tools,))
|
||||
# self.sub_thread.start()
|
||||
|
||||
# def load_tool_list(self, tools:dict):
|
||||
# if not os.path.exists(REMOTE_PLUGINS_PATH):
|
||||
# logger.error("服务器链接错误!")
|
||||
# return
|
||||
# #读取服务器工具信息
|
||||
# sys.path.append(str(REMOTE_PLUGINS_PATH))
|
||||
# for tool in Path(REMOTE_PLUGINS_PATH).glob("*.pyd"):
|
||||
# tool_name = tool.stem
|
||||
# try:
|
||||
# module = importlib.import_module(tool_name)
|
||||
|
||||
# name = module.read_tool_name()
|
||||
# version = module.read_tool_version()
|
||||
# description = module.read_tool_description()
|
||||
# if name in tools.keys():
|
||||
# pass
|
||||
# else:
|
||||
# tools[name] = {}
|
||||
# tools[name]["name"] = tool_name
|
||||
# tools[name]["local version"] = None
|
||||
# tools[name]["local description"] = None
|
||||
# tools[name]["remote version"] = version
|
||||
# tools[name]["remote description"] = description
|
||||
# tools[name]['remote path'] = REMOTE_PLUGINS_PATH
|
||||
# except Exception as e:
|
||||
# logger.error(f"服务器{tool_name}无法加载:{str(e)}")
|
||||
# sys.path.remove(str(REMOTE_PLUGINS_PATH))
|
||||
# self.update_tool_card.emit(tools)
|
||||
|
||||
def on_update_plugins_card(self, plugins:dict):
|
||||
def on_update_plugins_card(self, plugins: dict) -> None:
|
||||
for name in plugins.keys():
|
||||
if name in self.cards:
|
||||
# 卡片已存在,只刷新状态(避免重复创建叠加)
|
||||
|
||||
+34
-18
@@ -1,15 +1,8 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from .plugin_registry import Event
|
||||
from PySide6.QtWidgets import QFrame
|
||||
from PySide6.QtCore import Signal
|
||||
from .plugins_card_ui import Ui_FramePluginsCard
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_version(version: str) -> tuple:
|
||||
"""将版本字符串解析为可比较的元组,如 '1.10.0' -> (1, 10, 0)。
|
||||
@@ -34,13 +27,27 @@ def parse_version(version: str) -> tuple:
|
||||
|
||||
|
||||
class PluginsCard(QFrame, Ui_FramePluginsCard):
|
||||
"""工具卡片类
|
||||
"""插件卡片组件。
|
||||
|
||||
继承自 QFrame(Qt 帧基类)和 Ui_FrameToolCard(Qt Designer 生成的 UI 界面)
|
||||
负责显示工具的卡片界面,包含工具的图标、名称和描述。
|
||||
继承自 QFrame 和 Ui_FramePluginsCard(Qt Designer 生成的 UI 界面),
|
||||
负责展示单个插件的图标、名称、版本和描述,并提供安装/更新/卸载按钮。
|
||||
|
||||
Attributes:
|
||||
name: 插件名称
|
||||
info: 插件元数据字典,含 local version / remote version / description 等
|
||||
"""
|
||||
send_event_signal = Signal(Event,str)
|
||||
def __init__(self, name: str, info:dict, parent=None) -> None:
|
||||
|
||||
# 按钮点击信号,携带事件类型和插件名称
|
||||
send_event_signal = Signal(Event, str)
|
||||
|
||||
def __init__(self, name: str, info: dict, parent=None) -> None:
|
||||
"""初始化插件卡片。
|
||||
|
||||
Args:
|
||||
name: 插件名称
|
||||
info: 插件元数据字典
|
||||
parent: 父组件,默认为 None
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
@@ -49,7 +56,8 @@ class PluginsCard(QFrame, Ui_FramePluginsCard):
|
||||
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
def initUI(self) -> None:
|
||||
"""初始化卡片 UI:刷新显示内容并绑定按钮事件。"""
|
||||
self.refresh()
|
||||
self.textBrowser.setReadOnly(True)
|
||||
self.pushButtonUpdate.clicked.connect(self.on_update_event)
|
||||
@@ -69,7 +77,12 @@ class PluginsCard(QFrame, Ui_FramePluginsCard):
|
||||
else:
|
||||
self.pushButtonUpdate.setEnabled(False)
|
||||
|
||||
def to_card(self):
|
||||
def to_card(self) -> str:
|
||||
"""将插件信息渲染为 Markdown 卡片文本。
|
||||
|
||||
Returns:
|
||||
格式化的 Markdown 字符串,包含名称、版本和描述。
|
||||
"""
|
||||
remote_ver = self.info.get('remote version')
|
||||
local_ver = self.info.get('local version')
|
||||
desc = self.info.get('remote description') or self.info.get('local description') or '暂无'
|
||||
@@ -83,11 +96,14 @@ class PluginsCard(QFrame, Ui_FramePluginsCard):
|
||||
card += f"**描述:** {desc}"
|
||||
return card
|
||||
|
||||
def on_install_event(self):
|
||||
def on_install_event(self) -> None:
|
||||
"""触发安装事件。"""
|
||||
self.send_event_signal.emit(Event.Install, self.name)
|
||||
|
||||
def on_uninstall_event(self):
|
||||
def on_uninstall_event(self) -> None:
|
||||
"""触发卸载事件。"""
|
||||
self.send_event_signal.emit(Event.Uninstall, self.name)
|
||||
|
||||
def on_update_event(self):
|
||||
|
||||
def on_update_event(self) -> None:
|
||||
"""触发更新事件。"""
|
||||
self.send_event_signal.emit(Event.Update, self.name)
|
||||
Reference in New Issue
Block a user