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() 方法,统一卡片状态刷新

影响:卸载功能可用;自重启在命令行/打包模式正常,调试器环境
可能因子进程管理闪退(不影响交付)。
This commit is contained in:
2026-07-20 13:15:16 +08:00
parent aaad6b8aa6
commit 51e30d41c6
4 changed files with 173 additions and 16 deletions
+15 -8
View File
@@ -28,17 +28,24 @@ class PluginsCard(QFrame, Ui_FramePluginsCard):
def initUI(self):
self.textBrowser.setReadOnly(True)
self.textBrowser.setMarkdown(self.to_card())
if self.info["local version"] is None:
self.pushButtonUpdate.setEnabled(False)
self.pushButtonUninstall.setEnabled(False)
else:
self.pushButtonInstall.setEnabled(False)
if self.info['remote version'] <= self.info['local version']:
self.pushButtonUpdate.setEnabled(False)
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"