Files
model_team_tools/core/plugin/plugins_card.py
T
feifei.xu 51e30d41c6 feat(plugin): 实现插件卸载功能与自重启机制
目的:补齐 Event.Uninstall 空实现,打通卸载全链路。

背景:原 plugin_registry.py 的 Uninstall 分支为空 pass;运行时直接
os.remove 删 .pyd 触发 WinError 5(Windows 文件锁,.pyd 已被 import)。

方案:
- 卸载流程分层清理:Worker 写待删清单 → Registry 清字典/obj →
  main 清 toolBar/stackedWidget → plugins_card 刷新为未安装
- .pyd 文件删除延迟到下次启动 _process_pending_uninstalls,
  在 worker 线程启动前、import 之前执行,避开文件锁
- 卸载后自动重启:QProcess.startDetached + QApplication.quit,
  兼容开发模式(python.exe)与打包模式(main.exe)
- _remove_with_retry 应对自重启时原进程句柄未释放的暂态竞争
- _on_local_discovered 加异常隔离,单插件失败不拖垮初始化
- QThread 生命周期管理:aboutToQuit → shutdown(quit+wait),
  finished → worker.deleteLater,消除 Destroyed 警告
- plugins_card 抽 refresh() 方法,统一卡片状态刷新

影响:卸载功能可用;自重启在命令行/打包模式正常,调试器环境
可能因子进程管理闪退(不影响交付)。
2026-07-20 13:15:16 +08:00

64 lines
2.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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__)
class PluginsCard(QFrame, Ui_FramePluginsCard):
"""工具卡片类
继承自 QFrameQt 帧基类)和 Ui_FrameToolCardQt Designer 生成的 UI 界面)
负责显示工具的卡片界面,包含工具的图标、名称和描述。
"""
send_event_signal = Signal(Event,str)
def __init__(self, name: str, info:dict, parent=None) -> None:
super().__init__(parent)
self.setupUi(self)
self.name = name
self.info = info
self.initUI()
def initUI(self):
self.textBrowser.setReadOnly(True)
self.refresh()
self.pushButtonInstall.clicked.connect(self.on_install_event)
self.pushButtonUninstall.clicked.connect(self.on_uninstall_event)
def refresh(self) -> None:
"""根据 info 刷新卡片显示与按钮状态。"""
self.textBrowser.setMarkdown(self.to_card())
installed = self.info["local version"] is not None
self.pushButtonInstall.setEnabled(not installed)
self.pushButtonUninstall.setEnabled(installed)
if installed:
self.pushButtonUpdate.setEnabled(
self.info['remote version'] > self.info['local version']
)
else:
self.pushButtonUpdate.setEnabled(False)
def to_card(self):
card = f"# {self.name}\n\n"
card += f"**最新:** V{self.info['remote version']}\n\n"
if self.info['local version'] is None:
card += f"**当前:** 未安装!\n\n"
else:
card += f"**当前:** V{self.info['local version']}\n\n"
card += f"**描述:** {self.info['remote description']}"
return card
def on_install_event(self):
self.send_event_signal.emit(Event.Install, self.name)
def on_uninstall_event(self):
self.send_event_signal.emit(Event.Uninstall, self.name)
pass