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:
2026-07-21 17:04:34 +08:00
parent 0efaf2946b
commit 1b6796ec43
8 changed files with 282 additions and 138 deletions
+27 -5
View File
@@ -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)